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