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}