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}