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