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}