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}