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