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}