syslog_rs/a_sync/syslog_async.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::marker::PhantomData;
15
16use crate::
17{
18 a_sync::
19 {
20 syslog_async_internal::{AsyncMutexGuard, AsyncSyslogInternal}
21 },
22 error::SyRes,
23 formatters::{SyslogFormatter},
24 syslog_provider::*,
25 LogFacility,
26 LogStat,
27 Priority
28};
29
30use crate::a_sync::syslog_async_internal::AsyncMutex;
31
32#[cfg(feature = "build_async_interface")]
33use crate::a_sync::syslog_async_internal::AsyncSyslogInternalIO;
34
35#[cfg(feature = "async_embedded")]
36use crate::a_sync::{syslog_async_internal::AsyncSyslogInternalIO, DefaultAsyncMutex};
37
38#[cfg(feature = "async_embedded")]
39use crate::formatters::DefaultSyslogFormatter;
40
41#[cfg(feature = "async_embedded")]
42use crate::a_sync::DefaultIOs;
43
44use super::{syslog_trait::AsyncSyslogApi};
45
46
47/// A main instance of the Syslog client.
48///
49/// * `D` - a [SyslogDestination] instance which is either:
50/// [SyslogLocal], [SyslogFile], [SyslogNet], [SyslogTls] or other. By
51/// default a `SyslogLocal` is selected.
52///
53/// * 'F' - a [SyslogFormatter] formatter which should format the message for the
54/// [SyslogDestination] (`D`). By deafult, the [DefaultSyslogFormatter] is used which
55/// automatically selects the formatter which is used by syslog on the current system.
56/// If current crate does not have a build-in formatter, then a new one should be created.
57///
58/// * 'IO' - a [AsyncSyslogInternalIO] an additional IO like writing to syscons or stderr. And
59/// thread operations like sleep. By default a [DefaultIOs] is used which automatically picks
60/// correct instance.
61///
62/// * 'MUX` - a [AsyncMutex] implementation above the mutex instance. By default, a [DefaultAsyncMutex]
63/// is used which autoselects the correct mutex implementation.
64#[cfg(feature = "async_embedded")]
65#[derive(Debug)]
66pub struct AsyncSyslog<D = SyslogLocal, F = DefaultSyslogFormatter, IO = DefaultIOs, MUX = DefaultAsyncMutex<F, D, IO>>
67 (MUX, PhantomData<D>, PhantomData<F>, PhantomData<IO>)
68where
69 D: AsyncSyslogDestination,
70 F: SyslogFormatter + Sync,
71 MUX: AsyncMutex<F, D, AsyncSyslogInternal<F, D, IO>>,
72 IO: AsyncSyslogInternalIO;
73
74/// A main instance of the Syslog client.
75///
76/// * `D` - a [SyslogDestination] instance which is either:
77/// [SyslogLocal], [SyslogFile], [SyslogNet], [SyslogTls] or other. The caller
78/// should implement the [SyslogDestination] and pass the instance to the crate.
79///
80/// * 'F' - a [SyslogFormatter] formatter which should format the message for the
81/// [SyslogDestination] (`D`). The caller should select the correct syslog formatter
82/// from provided or use autotype [DefaultSyslogFormatter] or create own.
83///
84/// * 'IO' - a [AsyncSyslogInternalIO] an additional IO like writing to syscons or stderr. And
85/// thread operations like sleep. A caller should implement the [AsyncSyslogInternalIO]
86/// based on the async executer is used.
87///
88/// * 'MUX` - a [AsyncMutex] implementation above the mutex instance. The caller should implement
89/// this trait and pass the implementation to the struct.
90#[cfg(feature = "build_async_interface")]
91#[derive(Debug)]
92pub struct AsyncSyslog<D, F, IO, MUX>
93 (MUX, PhantomData<D>, PhantomData<F>, PhantomData<IO>)
94where
95 D: AsyncSyslogDestination,
96 F: SyslogFormatter + Sync,
97 MUX: AsyncMutex<F, D, AsyncSyslogInternal<F, D, IO>>,
98 IO: AsyncSyslogInternalIO;
99
100#[cfg(feature = "async_embedded")]
101impl AsyncSyslog
102{
103 /// Opens a default async connection to the local syslog server with default formatter.
104 ///
105 /// # Arguments
106 ///
107 /// * `ident` - A program name which will appear on the logs. If none, will be determined
108 /// automatically.
109 ///
110 /// * `logstat` - [LogStat] an instance config.
111 ///
112 /// * `facility` - [LogFacility] a syslog facility.
113 ///
114 /// * `net_tap` -a [SyslogLocal] instance with configuration.
115 ///
116 /// # Returns
117 ///
118 /// A [SyRes] is returned ([Result]) with:
119 ///
120 /// * [Result::Ok] - with instance
121 ///
122 /// * [Result::Err] - with error description.
123 pub async
124 fn openlog(ident: Option<&str>, logstat: LogStat, facility: LogFacility, net_tap: SyslogLocal) -> SyRes<Self>
125 {
126 let mut syslog =
127 AsyncSyslogInternal::<DefaultSyslogFormatter, SyslogLocal, DefaultIOs>::new(ident, logstat, facility, net_tap)?;
128
129 if logstat.contains(LogStat::LOG_NDELAY) == true
130 {
131 syslog.connectlog().await?;
132 }
133
134 let mux_syslog = DefaultAsyncMutex::a_new(syslog);
135
136 return Ok(
137 Self(mux_syslog, PhantomData::<SyslogLocal>, PhantomData::<DefaultSyslogFormatter>, PhantomData::<DefaultIOs>)
138 );
139 }
140}
141
142/// A shared implementation.
143impl<F, D, IO, MUX> AsyncSyslog<D, F, IO, MUX>
144where
145 F: SyslogFormatter + Sync,
146 D: AsyncSyslogDestination,
147 MUX: AsyncMutex<F, D, AsyncSyslogInternal<F, D, IO>>,
148 IO: AsyncSyslogInternalIO
149{
150 /// Opens a special connection to the destination syslog server with specific formatter.
151 ///
152 /// All struct generic should be specified before calling this function.
153 ///
154 /// # Arguments
155 ///
156 /// * `ident` - A program name which will appear on the logs. If none, will be determined
157 /// automatically.
158 ///
159 /// * `logstat` - [LogStat] an instance config.
160 ///
161 /// * `facility` - [LogFacility] a syslog facility.
162 ///
163 /// * `net_tap` - a destination server. A specific `D` instance which contains infomation
164 /// about the destination server. See `syslog_provider.rs`.
165 ///
166 /// # Returns
167 ///
168 /// A [SyRes] is returned ([Result]) with:
169 ///
170 /// * [Result::Ok] - with instance
171 ///
172 /// * [Result::Err] - with error description.
173 pub async
174 fn openlog_with(ident: Option<&str>, logstat: LogStat, facility: LogFacility, net_tap: D) -> SyRes<AsyncSyslog<D, F, IO, MUX>>
175 {
176 let mut syslog =
177 AsyncSyslogInternal::<F, D, IO>::new(ident, logstat, facility, net_tap)?;
178
179 if logstat.contains(LogStat::LOG_NDELAY) == true
180 {
181 syslog.connectlog().await?;
182 }
183
184 let mux_syslog = MUX::a_new(syslog);
185
186 return Ok( Self(mux_syslog, PhantomData::<D>, PhantomData::<F>, PhantomData::<IO>) );
187 }
188
189 /// Sets the logmask to filter out the syslog calls.
190 /// This function blocks until the previous mask is received.
191 ///
192 /// See macroses [LOG_MASK] and [LOG_UPTO] to generate mask
193 ///
194 /// # Example
195 ///
196 /// LOG_MASK!(Priority::LOG_EMERG) | LOG_MASK!(Priority::LOG_ERROR)
197 ///
198 /// or
199 ///
200 /// ~(LOG_MASK!(Priority::LOG_INFO))
201 /// LOG_UPTO!(Priority::LOG_ERROR)
202 #[inline]
203 pub async
204 fn setlogmask(&self, logmask: i32) -> i32
205 {
206 return
207 self
208 .0
209 .a_lock()
210 .await
211 .guard_mut()
212 .set_logmask(logmask);
213 }
214
215 /// Changes the identity i.e program name which will appear on the logs.
216 ///
217 /// Can return error if mutex is poisoned.
218 pub async
219 fn change_identity(&self, ident: &str)
220 {
221 return
222 self
223 .0
224 .a_lock()
225 .await
226 .guard_mut()
227 .change_identity(ident);
228 }
229
230 /// Closes connection to the syslog server
231 pub async
232 fn closelog(&self) -> SyRes<()>
233 {
234 return
235 self
236 .0
237 .a_lock()
238 .await
239 .guard_mut()
240 .closelog()
241 .await;
242 }
243
244 /// Similar to libc, syslog() sends data to syslog server.
245 ///
246 /// # Arguments
247 ///
248 /// * `pri` - a priority [Priority]
249 ///
250 /// * `fmt` - a program's message to be sent as payload.
251 #[inline]
252 pub async
253 fn syslog(&self, pri: Priority, fmt: String)
254 {
255 self.0.a_lock().await.guard_mut().vsyslog1(pri, fmt.into()).await;
256 }
257
258 /// Sends message to syslog (same as `syslog`).
259 #[inline]
260 pub async
261 fn vsyslog(&self, pri: Priority, fmt: &'static str)
262 {
263 self.0.a_lock().await.guard_mut().vsyslog1(pri, fmt.into()).await;
264 }
265
266 /// Sends the specificly formatted message i.e RFC5424 allows to send additional data
267 /// like STRUCTURED-DATA, SD-ID, SD-PARAM.
268 #[inline]
269 pub async
270 fn esyslog(&self, pri: Priority, fmt: F)
271 {
272 self.0.a_lock().await.guard_mut().vsyslog1(pri, fmt).await;
273 }
274
275 /// Performs the reconnection to the syslog server or file re-open.
276 ///
277 /// # Returns
278 ///
279 /// A [Result] is retured as [SyRes].
280 ///
281 /// * [Result::Ok] - with empty inner type.
282 ///
283 /// * [Result::Err] - an error code and description
284 pub async
285 fn reconnect(&self) -> SyRes<()>
286 {
287 return self.0.a_lock().await.guard_mut().reconnect().await;
288 }
289
290 /// Updates the inner instance destionation i.e path to file
291 /// or server address. The type of destination can not be changed.
292 ///
293 /// This function disconnects from syslog server if previously was
294 /// connected (and reconnects if was connected previously).
295 ///
296 /// # Arguments
297 ///
298 /// * `new_tap` - a consumed instance of type `D` [SyslogDestination]
299 ///
300 /// # Returns
301 ///
302 /// A [SyRes] is returned. An error may be returned if:
303 ///
304 /// * connection to server was failed
305 ///
306 /// * incorrect type
307 ///
308 /// * disconnect frm server failed
309 pub async
310 fn update_tap(&self, new_tap: D) -> SyRes<()>
311 {
312 return self.0.a_lock().await.guard_mut().update_tap_data(new_tap).await;
313 }
314}
315
316
317
318#[cfg(test)]
319mod tests
320{
321
322
323 use super::*;
324
325 #[cfg(feature = "build_async_smol")]
326 #[test]
327 fn test_smol() -> smol::io::Result<()>
328 {
329 smol::block_on(
330 async
331 {
332 use std::{sync::Arc, time::{Duration, Instant}};
333
334 use smol::Timer;
335
336 let log =
337 AsyncSyslog::openlog(
338 Some("smol_test1"),
339 LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID,
340 LogFacility::LOG_DAEMON,
341 SyslogLocal::new()
342 )
343 .await;
344
345 assert_eq!(log.is_ok(), true, "{}", log.err().unwrap());
346
347 let log = Arc::new(log.unwrap());
348 let c1_log = log.clone();
349 let c2_log = log.clone();
350
351 smol::spawn(async move
352 {
353 for i in 0..5
354 {
355 use std::time::Duration;
356
357 use smol::Timer;
358
359 let cc_c1_log = c1_log.clone();
360 Timer::after(Duration::from_nanos(200)).await;
361 smol::spawn( async move
362 {
363 use std::time::Instant;
364
365 let m = format!("ASYNC a message from thread 1 #{}[]", i);
366 let now = Instant::now();
367 cc_c1_log.syslog(Priority::LOG_DEBUG, m).await;
368 let elapsed = now.elapsed();
369 println!("t1: {:?}", elapsed);
370 }).await;
371 }
372 }
373 )
374 .await;
375
376 smol::spawn(async move
377 {
378 for i in 0..5
379 {
380 use std::time::Duration;
381
382 use smol::Timer;
383
384 let cc_c2_log = c2_log.clone();
385 Timer::after(Duration::from_nanos(201)).await;
386 smol::spawn( async move
387 {
388 use std::time::Instant;
389
390 let m = format!("ASYNC きるさお命泉ぶねりよ日子金れっ {}", i);
391 let now = Instant::now();
392 cc_c2_log.syslog(Priority::LOG_DEBUG, m.into()).await;
393 let elapsed = now.elapsed();
394 println!("t2: {:?}", elapsed);
395 }).await;
396 }
397 }).await;
398
399 let m = format!("ASYNC A message from main, きるさお命泉ぶねりよ日子金れっ");
400 let now = Instant::now();
401 log.syslog(Priority::LOG_DEBUG, m).await;
402 let elapsed = now.elapsed();
403 println!("main: {:?}", elapsed);
404
405 log.change_identity("smol_test1new").await;
406
407 let m = format!("ASYNC A message from main new ident, きるさお命泉ぶねりよ日子金れっ");
408
409 log.syslog(Priority::LOG_DEBUG, m).await;
410
411 Timer::after(Duration::from_secs(1)).await;
412
413 log.closelog().await.unwrap();
414
415 Timer::after(Duration::from_nanos(201)).await;
416
417 Ok(())
418 }
419 )
420 }
421
422 #[cfg(feature = "build_async_tokio")]
423 #[tokio::test]
424 async fn test_multithreading()
425 {
426 use tokio::time::Instant;
427 use std::sync::Arc;
428 use tokio::time::{sleep, Duration};
429
430 let log =
431 AsyncSyslog::openlog(
432 Some("asynctest1"),
433 LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID,
434 LogFacility::LOG_DAEMON,
435 SyslogLocal::new()
436 ).await;
437
438 assert_eq!(log.is_ok(), true, "{}", log.err().unwrap());
439
440 let log = Arc::new(log.unwrap());
441 let c1_log = log.clone();
442 let c2_log = log.clone();
443
444 tokio::spawn( async move
445 {
446 for i in 0..5
447 {
448 let cc_c1_log = c1_log.clone();
449 sleep(Duration::from_nanos(200)).await;
450 tokio::spawn( async move
451 {
452 let m = format!("ASYNC a message from thread 1 #{}[]", i);
453 let now = Instant::now();
454 cc_c1_log.syslog(Priority::LOG_DEBUG, m).await;
455 let elapsed = now.elapsed();
456 println!("t1: {:?}", elapsed);
457 });
458 }
459 }
460 );
461
462 tokio::spawn(async move
463 {
464 for i in 0..5
465 {
466 let cc_c2_log = c2_log.clone();
467 sleep(Duration::from_nanos(201)).await;
468 tokio::spawn( async move
469 {
470 let m = format!("ASYNC きるさお命泉ぶねりよ日子金れっ {}", i);
471 let now = Instant::now();
472 cc_c2_log.syslog(Priority::LOG_DEBUG, m).await;
473 let elapsed = now.elapsed();
474 println!("t2: {:?}", elapsed);
475 });
476 }
477 });
478
479 let m = format!("ASYNC A message from main, きるさお命泉ぶねりよ日子金れっ");
480 let now = Instant::now();
481 log.syslog(Priority::LOG_DEBUG, m).await;
482 let elapsed = now.elapsed();
483 println!("main: {:?}", elapsed);
484
485
486 sleep(Duration::from_secs(1)).await;
487
488 log.change_identity("asynctest1new").await;
489
490 let m = format!("ASYNC A message from main new ident, きるさお命泉ぶねりよ日子金れっ");
491
492 log.syslog(Priority::LOG_DEBUG, m).await;
493
494 log.closelog().await.unwrap();
495
496 sleep(Duration::from_nanos(201)).await;
497
498 return;
499 }
500}