syslog_rs/sync/
syslog_threadlocal.rs

1/*-
2 * syslog-rs - a syslog client translated from libc to rust
3 * 
4 * Copyright 2025 Aleksandr Morozov
5 * 
6 * The syslog-rs crate can be redistributed and/or modified
7 * under the terms of either of the following licenses:
8 *
9 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
10 *
11 *   2. The MIT License (MIT)
12 *                     
13 *   3. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
14 */
15
16use std::{cell::RefCell, marker::PhantomData};
17
18use crate::
19{
20    error::SyRes, 
21    formatters::{DefaultSyslogFormatter, SyslogFormatter}, 
22    sync::{syslog_sync_internal::SyslogSocketLockless, LogItems, SyStream}, 
23    LogFacility, 
24    LogStat, 
25    Priority, 
26    SyStreamApi, 
27    SyslogApi, 
28    SyslogDestination,
29};
30
31#[cfg(target_family = "unix")]
32use crate::SyslogLocal;
33
34#[cfg(target_family = "windows")]
35use crate::WindowsEvent;
36
37#[cfg(target_family = "unix")]
38pub type DefaultLocalSyslogDestination = SyslogLocal;
39#[cfg(target_family = "windows")]
40pub type DefaultLocalSyslogDestination = WindowsEvent;
41
42/// A threal local syslog which is completely lockless! It can be used in signle threaded
43/// applications or with the [thread_local] functionality.
44/// 
45/// ```ignore
46/// thread_local! 
47/// {
48///     // Could add pub to make it public to whatever Foo already is public to.
49///     static SYSLOG: RefCell<SingleSyslog> = 
50///         RefCell::new(SingleSyslog::openlog_with(Some("test"), LogStat::LOG_PID , 
51///             LogFacility::LOG_DAEMON, SyslogLocal::new()).unwrap());
52/// }
53/// ```
54/// 
55/// A stream is availble via [SyStreamApi].
56/// 
57/// ```ignore
58/// let _ = write!(SYSLOG.stream(Priority::LOG_DEBUG), "test {} 123 stream test ", d);
59/// ```
60/// 
61/// The instances will be completly separated and have own FD.
62/// 
63/// # Generics
64/// 
65/// * `D` - a [SyslogDestination] instance which is either:
66///     [SyslogLocal], [crate::syslog_provider::SyslogFile], [crate::syslog_provider::SyslogNet], 
67///     [crate::syslog_provider::SyslogTls]. By default a `SyslogLocal` is selected.
68/// 
69/// * `F` - a [SyslogFormatter] which sets the instance which would 
70///     format the message.
71/// 
72#[derive(Debug)]
73pub struct SingleSyslog<F = DefaultSyslogFormatter, D = DefaultLocalSyslogDestination>
74where 
75    F: SyslogFormatter, 
76    D: SyslogDestination,   
77{
78    /// An identification i.e program name, thread name
79    log_items: RefCell<LogItems>,
80
81    /// A stream (unixdatagram, udp, tcp)
82    stream: RefCell<SyslogSocketLockless<D>>,
83
84    _p: PhantomData<F>,
85
86    _p_not_ss: PhantomData<*const ()>
87}
88
89impl SingleSyslog
90{
91    /// Opens a default connection to the local syslog server with default formatter.
92    /// 
93    /// In order to access the syslog API, use the [SyslogApi].
94    /// 
95    /// # Arguments
96    /// 
97    /// * `ident` - A program name which will appear on the logs. If none, will be determined
98    ///     automatically.
99    /// 
100    /// * `logstat` - [LogStat] an instance config.
101    /// 
102    /// * `facility` - [LogFacility] a syslog facility.
103    /// 
104    /// * `net_tap_prov` - a [SyslogLocal] instance with configuration.
105    /// 
106    /// # Returns
107    /// 
108    /// A [SyRes] is returned ([Result]) with: 
109    /// 
110    /// * [Result::Ok] - with instance
111    /// 
112    /// * [Result::Err] - with error description.
113    pub 
114    fn openlog(ident: Option<&str>, logstat: LogStat, facility: LogFacility, net_tap_prov: DefaultLocalSyslogDestination) -> SyRes<Self> 
115    {
116        let log_items = 
117            LogItems::new(ident, 0xff, logstat, facility);
118
119        let stream = 
120            SyslogSocketLockless::<DefaultLocalSyslogDestination>::new(logstat, net_tap_prov)?;
121        
122        return Ok(
123            Self
124            {
125                log_items: RefCell::new(log_items),
126                stream: RefCell::new(stream),
127                _p: PhantomData,
128                _p_not_ss: PhantomData
129            }
130        );
131    }
132}
133
134impl<F, D> SingleSyslog<F, D>
135where F: SyslogFormatter, D: SyslogDestination
136{
137    /// Opens a default connection to the local syslog server with default formatter.
138    /// 
139    /// # Arguments
140    /// 
141    /// * `ident` - A program name which will appear on the logs. If none, will be determined
142    ///     automatically.
143    /// 
144    /// * `logstat` - [LogStat] an instance config.
145    /// 
146    /// * `facility` - [LogFacility] a syslog facility.
147    /// 
148    /// * `net_tap_prov` - a [SyslogLocal] instance with configuration.
149    /// 
150    /// # Returns
151    /// 
152    /// A [SyRes] is returned ([Result]) with: 
153    /// 
154    /// * [Result::Ok] - with instance
155    /// 
156    /// * [Result::Err] - with error description.
157    pub 
158    fn openlog_with(ident: Option<&str>, logstat: LogStat, facility: LogFacility, net_tap_prov: D) -> SyRes<Self> 
159    {
160        let log_items = 
161            LogItems::new(ident, 0xff, logstat, facility);
162
163        let stream = 
164            SyslogSocketLockless::<D>::new(logstat, net_tap_prov)?;
165        
166        return Ok(
167            Self
168            {
169                log_items: RefCell::new(log_items),
170                stream: RefCell::new(stream),
171                _p: PhantomData,
172                _p_not_ss: PhantomData
173            }
174        );
175    }
176}
177
178impl<F, D> SyslogApi<F, D> for SingleSyslog<F, D>
179where F: SyslogFormatter, D: SyslogDestination
180{
181        /// Connects the current instance to the syslog server (destination).
182    #[inline]
183    fn connectlog(&self) -> SyRes<()>
184    {
185        return 
186            self
187                .stream
188                .borrow_mut()
189                .connectlog();
190    }
191
192    /// Sets the logmask to filter out the syslog calls.
193    /// 
194    /// See macroses [LOG_MASK] and [LOG_UPTO] to generate mask
195    ///
196    /// # Example
197    ///
198    /// LOG_MASK!(Priority::LOG_EMERG) | LOG_MASK!(Priority::LOG_ERROR)
199    ///
200    /// or
201    ///
202    /// ~(LOG_MASK!(Priority::LOG_INFO))
203    /// LOG_UPTO!(Priority::LOG_ERROR)
204    #[inline]
205    fn setlogmask(&self, logmask: i32) -> SyRes<i32>
206    {
207        return Ok(
208            self
209                .log_items
210                .borrow_mut()
211                .set_logmask(logmask)
212        );
213    }
214
215    /// Closes connection to the syslog server (destination).
216    #[inline]
217    fn closelog(&self) -> SyRes<()> 
218    {
219        return 
220            self
221                .stream
222                .borrow_mut()
223                .disconnectlog();
224    }
225
226    /// Similar to libc, syslog() sends data to syslog server.
227    /// 
228    /// # Arguments
229    ///
230    /// * `pri` - a priority [Priority]
231    ///
232    /// * `fmt` - a formatter [SyslogFormatter] message. In C exists a functions with
233    ///     variable argumets amount. In Rust you should create your
234    ///     own macros like format!() or use format!()]. The [String] and ref `'static` 
235    ///     [str] can be passed directly.
236    #[inline]
237    fn syslog(&self, pri: Priority, fmt: F) 
238    {
239        let Some((formatted_msg, logstat)) = 
240            self.log_items.borrow().vsyslog1_msg::<F, D>(pri, &fmt)
241            else { return };
242
243        self.stream.borrow_mut().vsyslog1(logstat, formatted_msg);
244
245        return;
246    }
247
248    /// This function can be used to update the facility name, for example
249    /// after fork().
250    /// 
251    /// # Arguments
252    /// 
253    /// * `ident` - an [Option] optional new identity (up to 48 UTF8 chars)
254    ///     If set to [Option::None] would request the program name from OS.
255    #[inline]
256    fn change_identity(&self, ident: Option<&str>) -> SyRes<()>
257    {
258        self.log_items.borrow_mut().set_identity(ident);
259
260        return Ok(());
261    }
262
263    /// Re-opens the connection to the syslog server. Can be used to 
264    /// rotate logs(handle SIGHUP).
265    /// 
266    /// # Returns
267    /// 
268    /// A [Result] is retured as [SyRes].
269    /// 
270    /// * [Result::Ok] - with empty inner type.
271    /// 
272    /// * [Result::Err] - an error code and description 
273    #[inline]
274    fn reconnect(&self) -> SyRes<()>
275    {
276        return
277            self
278                .stream
279                .borrow_mut()
280                .reconnectlog();
281    }
282
283    /// Updates the instance's socket. `tap_data` [TapTypeData] should be of
284    /// the same variant (type) as current.
285    #[inline]
286    fn update_tap_data(&self, tap_data: D) -> SyRes<()>
287    {
288        return 
289            self
290                .stream
291                .borrow_mut()
292                .update_tap_data(tap_data.clone());
293    }
294}
295
296impl<'stream, F: SyslogFormatter, D: SyslogDestination> SyStreamApi<'stream, F, D, SingleSyslog<F, D>> 
297for SingleSyslog<F, D>
298{
299    fn stream(&'stream self, pri: Priority) -> SyStream<'stream, D, F, SingleSyslog<F, D>> 
300    {
301        return 
302            SyStream 
303            { 
304                inner: self, 
305                pri: pri, 
306                _p: PhantomData, 
307                _p1: PhantomData 
308            };
309    }
310}
311
312
313#[cfg(target_family = "unix")]
314#[cfg(test)]
315mod tests
316{
317    use std::{cell::RefCell, time::Instant};
318
319    use crate::{sync::syslog_threadlocal::SingleSyslog, LogFacility, LogStat, Priority, SyStreamApi, SyslogApi, SyslogLocal};
320
321    #[test]
322    fn test_thread_single1()
323    {
324        thread_local! 
325        {
326            // Could add pub to make it public to whatever Foo already is public to.
327            static SYSLOG: RefCell<SingleSyslog> = 
328                RefCell::new(SingleSyslog::openlog_with(Some("test"), LogStat::LOG_PID , 
329                    LogFacility::LOG_DAEMON, SyslogLocal::new()).unwrap());
330        }
331
332        let thread = 
333            std::thread::spawn(move ||
334                {
335                    for i in 0..10
336                    {
337                        let fmms = format!("test message {} from thread {:?}", i, std::thread::current().name());
338                        let s = Instant::now();
339
340                        SYSLOG
341                            .with_borrow_mut(|syslog| 
342                                syslog
343                                    .syslog(
344                                        Priority::LOG_ERR, 
345                                        fmms.into()
346                                    )
347                            );
348
349                        let e = s.elapsed();
350
351                        println!("{:?}", e);
352                    }
353                }
354            );
355
356
357        SYSLOG
358            .with_borrow_mut(|syslog| 
359                syslog
360                    .syslog(
361                        Priority::LOG_DEBUG, 
362                        format!("main test message from thread {:?}", std::thread::current().name()).into()
363                    )
364            );
365
366
367        thread.join().unwrap();
368
369        return;
370
371    }
372
373    #[test]
374    fn test_single_stream_test()
375    {
376        use std::fmt::Write;
377
378        let log = 
379                SingleSyslog::openlog(
380                    Some("test1"), 
381                    LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
382                    LogFacility::LOG_DAEMON,
383                SyslogLocal::new());
384
385        assert_eq!(log.is_ok(), true, "{}", log.err().unwrap());
386
387        let log = log.unwrap();
388        
389        write!(log.stream(Priority::LOG_DEBUG), "test stream singlesyslog").unwrap();
390
391        for i in 0..3
392        {
393            let s = Instant::now();
394            write!(log.stream(Priority::LOG_DEBUG), "test stream singlesyslog {}", i).unwrap();
395            let e = s.elapsed();
396
397            println!("{:?}", e);
398        }
399
400    }
401}
402
403#[cfg(target_family = "windows")]
404#[cfg(test)]
405mod tests
406{
407    use std::{cell::RefCell, time::Instant};
408
409    use crate::{sync::syslog_threadlocal::SingleSyslog, LogFacility, LogStat, Priority, SyStreamApi, SyslogApi, WindowsEvent};
410
411    #[test]
412    fn test_thread_signalling()
413    {
414        thread_local! 
415        {
416            // Could add pub to make it public to whatever Foo already is public to.
417            static SYSLOG: RefCell<SingleSyslog> = 
418                RefCell::new(SingleSyslog::openlog_with(Some("test"), LogStat::LOG_PID , 
419                    LogFacility::LOG_DAEMON, WindowsEvent::new()).unwrap());
420        }
421
422        let thread = 
423            std::thread::spawn(move ||
424                {
425                    for i in 0..10
426                    {
427                        let fmms = format!("test message {} from thread {:?}", i, std::thread::current().name());
428                        let s = Instant::now();
429
430                        SYSLOG
431                            .with_borrow_mut(|syslog| 
432                                syslog
433                                    .syslog(
434                                        Priority::LOG_ERR, 
435                                        fmms.into()
436                                    )
437                            );
438
439                        let e = s.elapsed();
440
441                        println!("{:?}", e);
442                    }
443                }
444            );
445
446
447        SYSLOG
448            .with_borrow_mut(|syslog| 
449                syslog
450                    .syslog(
451                        Priority::LOG_DEBUG, 
452                        format!("main test message from thread {:?}", std::thread::current().name()).into()
453                    )
454            );
455
456
457        thread.join().unwrap();
458
459        return;
460
461    }
462
463    #[test]
464    fn test_single_stream_test()
465    {
466        use std::fmt::Write;
467
468        let log = 
469                SingleSyslog::openlog(
470                    Some("test1"), 
471                    LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
472                    LogFacility::LOG_DAEMON,
473                WindowsEvent::new());
474
475        assert_eq!(log.is_ok(), true, "{}", log.err().unwrap());
476
477        let log = log.unwrap();
478        
479        write!(log.stream(Priority::LOG_DEBUG), "test stream singlesyslog").unwrap();
480
481        for i in 0..3
482        {
483            let s = Instant::now();
484            write!(log.stream(Priority::LOG_DEBUG), "test stream singlesyslog {}", i).unwrap();
485            let e = s.elapsed();
486
487            println!("{:?}", e);
488        }
489
490    }
491}
492