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    SyslogLocal
30};
31
32/// A threal local syslog which is completely lockless! It can be used in signle threaded
33/// applications or with the [thread_local] functionality.
34/// 
35/// ```ignore
36/// thread_local! 
37/// {
38///     // Could add pub to make it public to whatever Foo already is public to.
39///     static SYSLOG: RefCell<SingleSyslog> = 
40///         RefCell::new(SingleSyslog::openlog_with(Some("test"), LogStat::LOG_PID , 
41///             LogFacility::LOG_DAEMON, SyslogLocal::new()).unwrap());
42/// }
43/// ```
44/// 
45/// A stream is availble via [SyStreamApi].
46/// 
47/// ```ignore
48/// let _ = write!(SYSLOG.stream(Priority::LOG_DEBUG), "test {} 123 stream test ", d);
49/// ```
50/// 
51/// The instances will be completly separated and have own FD.
52/// 
53/// # Generics
54/// 
55/// * `D` - a [SyslogDestination] instance which is either:
56///     [SyslogLocal], [crate::syslog_provider::SyslogFile], [crate::syslog_provider::SyslogNet], 
57///     [crate::syslog_provider::SyslogTls]. By default a `SyslogLocal` is selected.
58/// 
59/// * `F` - a [SyslogFormatter] which sets the instance which would 
60///     format the message.
61/// 
62#[derive(Debug)]
63pub struct SingleSyslog<F = DefaultSyslogFormatter, D = SyslogLocal>
64where 
65    F: SyslogFormatter, 
66    D: SyslogDestination,   
67{
68    /// An identification i.e program name, thread name
69    log_items: RefCell<LogItems>,
70
71    /// A stream (unixdatagram, udp, tcp)
72    stream: RefCell<SyslogSocketLockless<D>>,
73
74    _p: PhantomData<F>,
75
76    _p_not_ss: PhantomData<*const ()>
77}
78
79impl SingleSyslog
80{
81    /// Opens a default connection to the local syslog server with default formatter.
82    /// 
83    /// In order to access the syslog API, use the [SyslogApi].
84    /// 
85    /// # Arguments
86    /// 
87    /// * `ident` - A program name which will appear on the logs. If none, will be determined
88    ///     automatically.
89    /// 
90    /// * `logstat` - [LogStat] an instance config.
91    /// 
92    /// * `facility` - [LogFacility] a syslog facility.
93    /// 
94    /// * `net_tap_prov` - a [SyslogLocal] instance with configuration.
95    /// 
96    /// # Returns
97    /// 
98    /// A [SyRes] is returned ([Result]) with: 
99    /// 
100    /// * [Result::Ok] - with instance
101    /// 
102    /// * [Result::Err] - with error description.
103    pub 
104    fn openlog(ident: Option<&str>, logstat: LogStat, facility: LogFacility, net_tap_prov: SyslogLocal) -> SyRes<Self> 
105    {
106        let log_items = 
107            LogItems::new(ident, 0xff, logstat, facility);
108
109        let stream = 
110            SyslogSocketLockless::<SyslogLocal>::new(logstat, net_tap_prov)?;
111        
112        return Ok(
113            Self
114            {
115                log_items: RefCell::new(log_items),
116                stream: RefCell::new(stream),
117                _p: PhantomData,
118                _p_not_ss: PhantomData
119            }
120        );
121    }
122}
123
124impl<F, D> SingleSyslog<F, D>
125where F: SyslogFormatter, D: SyslogDestination
126{
127    /// Opens a default connection to the local syslog server with default formatter.
128    /// 
129    /// # Arguments
130    /// 
131    /// * `ident` - A program name which will appear on the logs. If none, will be determined
132    ///     automatically.
133    /// 
134    /// * `logstat` - [LogStat] an instance config.
135    /// 
136    /// * `facility` - [LogFacility] a syslog facility.
137    /// 
138    /// * `net_tap_prov` - a [SyslogLocal] instance with configuration.
139    /// 
140    /// # Returns
141    /// 
142    /// A [SyRes] is returned ([Result]) with: 
143    /// 
144    /// * [Result::Ok] - with instance
145    /// 
146    /// * [Result::Err] - with error description.
147    pub 
148    fn openlog_with(ident: Option<&str>, logstat: LogStat, facility: LogFacility, net_tap_prov: D) -> SyRes<Self> 
149    {
150        let log_items = 
151            LogItems::new(ident, 0xff, logstat, facility);
152
153        let stream = 
154            SyslogSocketLockless::<D>::new(logstat, net_tap_prov)?;
155        
156        return Ok(
157            Self
158            {
159                log_items: RefCell::new(log_items),
160                stream: RefCell::new(stream),
161                _p: PhantomData,
162                _p_not_ss: PhantomData
163            }
164        );
165    }
166}
167
168impl<F, D> SyslogApi<F, D> for SingleSyslog<F, D>
169where F: SyslogFormatter, D: SyslogDestination
170{
171        /// Connects the current instance to the syslog server (destination).
172    #[inline]
173    fn connectlog(&self) -> SyRes<()>
174    {
175        return 
176            self
177                .stream
178                .borrow_mut()
179                .connectlog();
180    }
181
182    /// Sets the logmask to filter out the syslog calls.
183    /// 
184    /// See macroses [LOG_MASK] and [LOG_UPTO] to generate mask
185    ///
186    /// # Example
187    ///
188    /// LOG_MASK!(Priority::LOG_EMERG) | LOG_MASK!(Priority::LOG_ERROR)
189    ///
190    /// or
191    ///
192    /// ~(LOG_MASK!(Priority::LOG_INFO))
193    /// LOG_UPTO!(Priority::LOG_ERROR)
194    #[inline]
195    fn setlogmask(&self, logmask: i32) -> SyRes<i32>
196    {
197        return Ok(
198            self
199                .log_items
200                .borrow_mut()
201                .set_logmask(logmask)
202        );
203    }
204
205    /// Closes connection to the syslog server (destination).
206    #[inline]
207    fn closelog(&self) -> SyRes<()> 
208    {
209        return 
210            self
211                .stream
212                .borrow_mut()
213                .disconnectlog();
214    }
215
216    /// Similar to libc, syslog() sends data to syslog server.
217    /// 
218    /// # Arguments
219    ///
220    /// * `pri` - a priority [Priority]
221    ///
222    /// * `fmt` - a formatter [SyslogFormatter] message. In C exists a functions with
223    ///     variable argumets amount. In Rust you should create your
224    ///     own macros like format!() or use format!()]. The [String] and ref `'static` 
225    ///     [str] can be passed directly.
226    #[inline]
227    fn syslog(&self, pri: Priority, fmt: F) 
228    {
229        let Some((formatted_msg, logstat)) = 
230            self.log_items.borrow().vsyslog1_msg::<F, D>(pri, &fmt)
231            else { return };
232
233        self.stream.borrow_mut().vsyslog1(logstat, formatted_msg);
234
235        return;
236    }
237
238    /// This function can be used to update the facility name, for example
239    /// after fork().
240    /// 
241    /// # Arguments
242    /// 
243    /// * `ident` - an [Option] optional new identity (up to 48 UTF8 chars)
244    ///     If set to [Option::None] would request the program name from OS.
245    #[inline]
246    fn change_identity(&self, ident: Option<&str>) -> SyRes<()>
247    {
248        self.log_items.borrow_mut().set_identity(ident);
249
250        return Ok(());
251    }
252
253    /// Re-opens the connection to the syslog server. Can be used to 
254    /// rotate logs(handle SIGHUP).
255    /// 
256    /// # Returns
257    /// 
258    /// A [Result] is retured as [SyRes].
259    /// 
260    /// * [Result::Ok] - with empty inner type.
261    /// 
262    /// * [Result::Err] - an error code and description 
263    #[inline]
264    fn reconnect(&self) -> SyRes<()>
265    {
266        return
267            self
268                .stream
269                .borrow_mut()
270                .reconnectlog();
271    }
272
273    /// Updates the instance's socket. `tap_data` [TapTypeData] should be of
274    /// the same variant (type) as current.
275    #[inline]
276    fn update_tap_data(&self, tap_data: D) -> SyRes<()>
277    {
278        return 
279            self
280                .stream
281                .borrow_mut()
282                .update_tap_data(tap_data.clone());
283    }
284}
285
286impl<'stream, F: SyslogFormatter, D: SyslogDestination> SyStreamApi<'stream, F, D, SingleSyslog<F, D>> 
287for SingleSyslog<F, D>
288{
289    fn stream(&'stream self, pri: Priority) -> SyStream<'stream, D, F, SingleSyslog<F, D>> 
290    {
291        return 
292            SyStream 
293            { 
294                inner: self, 
295                pri: pri, 
296                _p: PhantomData, 
297                _p1: PhantomData 
298            };
299    }
300}
301
302#[cfg(test)]
303mod tests
304{
305    use std::{cell::RefCell, time::Instant};
306
307    use crate::{sync::syslog_threadlocal::SingleSyslog, LogFacility, LogStat, Priority, SyStreamApi, SyslogApi, SyslogLocal};
308
309    #[test]
310    fn test_thread_signalling()
311    {
312        thread_local! 
313        {
314            // Could add pub to make it public to whatever Foo already is public to.
315            static SYSLOG: RefCell<SingleSyslog> = 
316                RefCell::new(SingleSyslog::openlog_with(Some("test"), LogStat::LOG_PID , 
317                    LogFacility::LOG_DAEMON, SyslogLocal::new()).unwrap());
318        }
319
320        let thread = 
321            std::thread::spawn(move ||
322                {
323                    for i in 0..10
324                    {
325                        let fmms = format!("test message {} from thread {:?}", i, std::thread::current().name());
326                        let s = Instant::now();
327
328                        SYSLOG
329                            .with_borrow_mut(|syslog| 
330                                syslog
331                                    .syslog(
332                                        Priority::LOG_ERR, 
333                                        fmms.into()
334                                    )
335                            );
336
337                        let e = s.elapsed();
338
339                        println!("{:?}", e);
340                    }
341                }
342            );
343
344
345        SYSLOG
346            .with_borrow_mut(|syslog| 
347                syslog
348                    .syslog(
349                        Priority::LOG_DEBUG, 
350                        format!("main test message from thread {:?}", std::thread::current().name()).into()
351                    )
352            );
353
354
355        thread.join().unwrap();
356
357        return;
358
359    }
360
361    #[test]
362    fn test_single_stream_test()
363    {
364        use std::fmt::Write;
365
366        let log = 
367                SingleSyslog::openlog(
368                    Some("test1"), 
369                    LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
370                    LogFacility::LOG_DAEMON,
371                SyslogLocal::new());
372
373        assert_eq!(log.is_ok(), true, "{}", log.err().unwrap());
374
375        let log = log.unwrap();
376        
377        write!(log.stream(Priority::LOG_DEBUG), "test stream singlesyslog").unwrap();
378
379        for i in 0..3
380        {
381            let s = Instant::now();
382            write!(log.stream(Priority::LOG_DEBUG), "test stream singlesyslog {}", i).unwrap();
383            let e = s.elapsed();
384
385            println!("{:?}", e);
386        }
387
388    }
389}