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