syslog-rs 3.0.0

A native Rust implementation of the glibc/libc syslog.
Documentation

syslog-rs

KPI logo

This is NOT an Open Source software! This is Source Available or Sources Disclosed software!! If this is a concern, please don't use this crate.

Temporary Issues tracker: A public crate's Issues Tracker.

v 3.0.0

This crate is dual-licensed with MPL-2.0 and EUPL-1.2.

An implementation of the syslog from glibc/libc like it was designed in in both system libraries. The API is almost compatible with what is in libc/glibc. Supports both sync and async and custom formatters.

  • GNU/Linux RFC3164 (UTF-8 by default)
  • *BSD and OSX RFC5424 (BOM UTF-8 by default)
  • Tokio async
  • Smol async
  • TLS over TCP syslog server connection
  • TCP/UDP syslog server connection
  • Local file writer

Available features:

  • feature = build_async_tokio or build_async_smol for asynchronious execution. Cannot be used together.
  • feature = build_async_interface an experimental feature which allowes to implement async for some unsupported executor. See /docs/ for info. Can not be used together with build_async_tokio or build_async_smol.
  • feature = build_sync for synchronious code
  • feature = build_with_queue for synchronious with async processing when using build_sync only. And when used with one of the build_async_tokio or build_async_smol, can be used to write to syslog server from both sync and async code using sinle connection.
  • feature = build_with_net enables the TCP/UDP
  • feature = build_ext_tls enables the TLS over TCP support.
  • feature = build_ext_file enables the local logging to file (without syslog server).

The use_sync is acting like the libc's/glibc's functions syslog(), openlog()...

The use_sync_queue has the same API as libc/glibc but it is different in some ways. It spawns a worker thread which sends messages from the queue (channel) to syslog. If used in conjunction with async can act as a sync/async interfacing by providing the ability to attach the async syslog instance to the sync queue and use a signle channel to syslog server.

┌───────────────────────────────────────────────────────────────────┐
│build_with_queue                                                   │
│                                                                   │
│       build_sync                                                  │
│       ┌────────────┐    ┌───────────┐                             │
│       │ SYNC_QUEUE ┼────► CROSSBEAM ┼────────────────┐            │
│       └────────────┘    │  adapter  │                │            │
│                         └───────────┘                │            │
│                                                      │            │
│       build_async_tokio                  ┌───────────▼───────────┐│
│       ┌────────────┐   ┌────────────┐    │                       ││
│  ┌────► SYNC_QUEUE ┼───► TOKIO MPSC ┼────►  SYSLOG_WORKER THREAD ││
│  │    └────────────┘   │   adapter  │    │                       ││
│  │                     └────────────┘    └───────────▲────▲──────┘│
│  │                                                   │    │       │
│  │    build_async_smol ┌────────────┐                │    │       │
│  │    ┌────────────┐   │ SMOL MPSC  │                │    │       │
│  ├────► SYNC_QUEUE ┼───►   adapter  ┼────────────────┘    │       │
│  │    └────────────┘   └────────────┘                     │       │
│  │                                                        │       │
│  │                                                        │       │
│  │    build_async_interface                               │       │
│  │    ┌────────────┐   ┌────────────┐                     │       │
│  ┼────► SYNC_QUEUE ┼───► EXT MPSC   ┼─────────────────────┘       │
│  │    └────────────┘   │  adapter   │                             │
│  │                     └────────────┘                             │
│  │  ┌──────────────────────┐                                      │
│  └──┼  AsyncSyslogQueueApi │                                      │
│     │       trait          │                                      │
│     └──────────────────────┘                                      │
└───────────────────────────────────────────────────────────────────┘                                           

The use_async_* is async realization of the use_sync based on the specific executor.

Available tunables:

  • feature = "udp_truncate_1024_bytes"
  • feature = "udp_truncate_1440_bytes" DEFAULT

The above is for RFC5424 which controls the syslog message length for forwarding via UDP protocol.

  • feature = "tcp_truncate_1024_bytes"

  • feature = "tcp_truncate_2048_bytes" DEFAULT

  • feature = "tcp_truncate_4096_bytes"

  • feature = "tcp_truncate_max_bytes"

  • feature = "truncate_default" - a shortcut for "udp_truncate_1440_bytes" and "tcp_truncate_2048_bytes"

The above is for RFC5424 which controls the syslog message length for forwarding via TCP protocol.

  • feature = "dgram_sysctl_failure_panic"

The above is for *BSD systems only and controls the behaviour of the sysctl error handling. If this is enabled, the crate will panic is access to sysctl fails. Not enabled by default.

Usage:

For default syslog-rs = "3.0"

For customization: syslog-rs = {version = "3.0", default-features = false, features = ["use_sync", "truncate_default"]}

Contributors

Ordered by Relkom s.r.o (c) 2021

Developed by: Aleksandr Morozov

Examples

See ./examples/ in the repository.

Sync syslog (Local Shared)

use std::{sync::LazyLock, thread};
use std::time::Duration;

use syslog_rs::Syslog;

use syslog_rs::{LogFacility, LogStat, Priority, SyslogLocal};

pub static SYSLOG: LazyLock<Syslog> = LazyLock::new(|| 
    {
        Syslog::openlog(
            Some("example"), 
            LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
            LogFacility::LOG_DAEMON, SyslogLocal::new()
        )
        .unwrap()
    }
);

pub static SYSLOG2: LazyLock<Syslog> = LazyLock::new(|| 
    {
        Syslog::openlog(
            None, 
            LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
            LogFacility::LOG_DAEMON, SyslogLocal::new()
        )
        .unwrap()
    }
);

macro_rules! logdebug 
{
    ($($arg:tt)*) => (
        SYSLOG.syslog(Priority::LOG_DEBUG, format!($($arg)*))
    )
}

macro_rules! logdebug2 
{
    ($($arg:tt)*) => (
        SYSLOG2.syslog(Priority::LOG_DEBUG, format!($($arg)*))
    )
}

pub fn main()
{
    logdebug2!("test program name!");

    
    logdebug!("test message1!");

    SYSLOG.change_identity("example2").unwrap();

    logdebug!("test message from new ident");

    thread::sleep(Duration::from_micros(10));

    return;
}

Async syslog (Local Shared)

use syslog_rs::sy_async::AsyncSyslog;
use tokio::sync::OnceCell;
use tokio::time::{Duration, sleep};

use syslog_rs::{LogFacility, LogStat, Priority, SyslogLocal};


pub static SYSLOG: OnceCell<AsyncSyslog> = OnceCell::const_new();

macro_rules! logdebug 
{
    ($($arg:tt)*) => (
        SYSLOG.get().unwrap().syslog(Priority::LOG_DEBUG, format!($($arg)*)).await
    )
}

#[tokio::main]
async fn main()
{
    let syslog =
        AsyncSyslog::openlog(
                Some("example"), 
                LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
                LogFacility::LOG_DAEMON,
                SyslogLocal::new()
            )
            .await
            .unwrap();


    SYSLOG.get_or_init(|| async { syslog }).await;


    logdebug!("test message async start!");
    
    SYSLOG.get().unwrap().vsyslog(Priority::LOG_DEBUG, "test 2").await;

    sleep(Duration::from_micros(10)).await;

    SYSLOG.get().unwrap().change_identity("new_identity").await.unwrap();

    logdebug!("test message new identity!");

    sleep(Duration::from_micros(10)).await;

    logdebug!("test 123!");
    logdebug!("test 123123! end ");
    return;
}


Custom formatter.

use std::{borrow::Cow, sync::OnceLock};
use std::thread;
use std::time::Duration;

use chrono::{Local, SecondsFormat};

use syslog_rs::sy_sync::Syslog;
use syslog_rs::{SyslogFile, NEXTLINE, WSPACE};
use syslog_rs::{common, formatters::{SyslogFormatted, SyslogFormatter}, SyslogShared, TapType};

use syslog_rs::{LogStat, LogFacility, Priority};

pub static SYNC_SYSLOG: OnceLock<Syslog<SyslogFile, SyslogShared<MyFormatter, SyslogFile>>> = OnceLock::new();

macro_rules! logdebug 
{
    ($($arg:tt)*) => (
        SYNC_SYSLOG.get().unwrap().syslog(Priority::LOG_DEBUG, format!($($arg)*));
    )
}

#[derive(Debug, Clone)]
pub struct MyFormatter();

unsafe impl Send for MyFormatter {}

impl SyslogFormatter for MyFormatter
{
    fn vsyslog1_format<'f>(_tap_type: TapType, _max_msg_size: usize, pri: Priority, progname: &'f str, pid: &'f str, fmt: &'f str) -> SyslogFormatted<'f>
    {
        let timedate = Local::now().to_rfc3339_opts(SecondsFormat::Secs, false);

        let msg_payload_final = 
            if fmt.ends_with("\n") == true
            {
                common::truncate(fmt)
            }
            else
            {
                fmt
            };
           
        let msg_pkt = 
            [
                Cow::Owned(pri.to_string()), Cow::Borrowed(WSPACE), Cow::Borrowed("MYFORMATTER"),
                Cow::Borrowed(WSPACE), Cow::Owned(timedate), 
                Cow::Borrowed(WSPACE), Cow::Borrowed(progname),
                Cow::Borrowed(WSPACE), Cow::Borrowed(pid),
                Cow::Borrowed(WSPACE), Cow::Borrowed(msg_payload_final), Cow::Borrowed(NEXTLINE)
            ]
            .to_vec();
       
        let msg_rng = msg_pkt.len();

        return SyslogFormatted::new( msg_pkt,  0..msg_rng );
    }
}

pub fn main()
{
    let syslog = 
        Syslog
            ::<SyslogFile, SyslogShared<MyFormatter, SyslogFile>>
            ::openlog_with(
            Some("example"), 
            LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
            LogFacility::LOG_DAEMON,
            SyslogFile::new("/tmp/myformatter.log")
        ).unwrap();

    SYNC_SYSLOG.get_or_init(|| syslog);

    logdebug!("test message!");

    SYNC_SYSLOG.get().unwrap().change_identity("another").unwrap();

    logdebug!("test message new!");

    thread::sleep(Duration::from_micros(10));

    return;
}

Formatter select

use std::sync::OnceLock;
use std::thread;
use std::time::Duration;

use syslog_rs::sy_sync::Syslog;

#[cfg(target_os = "linux")]
use syslog_rs::{formatters::{FormatRfc3146}, SyslogLocal, SyslogShared};

#[cfg(not(target_os = "linux"))]
use syslog_rs::{formatters::{FormatRfc5424}, SyslogLocal, SyslogShared};

use syslog_rs::{LogStat, LogFacility, Priority};

#[cfg(target_os = "linux")]
pub static SYNC_SYSLOG: OnceLock<Syslog<SyslogLocal, SyslogShared<FormatRfc3146>>> = OnceLock::new();
#[cfg(not(target_os = "linux"))]
pub static SYNC_SYSLOG: OnceLock<Syslog<SyslogLocal, SyslogShared<FormatRfc5424>>> = OnceLock::new();

macro_rules! logdebug 
{
    ($($arg:tt)*) => (
        SYNC_SYSLOG.get().unwrap().syslog(Priority::LOG_DEBUG, format!($($arg)*));
    )
}

pub fn main()
{
    let syslog = 
        Syslog::<_>::openlog_with(
            Some("formatter_sel"), 
            LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
            LogFacility::LOG_DAEMON,
            SyslogLocal::new()
        ).unwrap();

    SYNC_SYSLOG.get_or_init(|| syslog);

    logdebug!("test message!");

    thread::sleep(Duration::from_micros(10));

    return;
}

SYNC and ASYNC to same "tap" i.e same connection

Either build_async_tokio or build_async_smol and build_with_queue should be enabled. Example below requires also feature build_ext_file to be enabled.

use std::sync::OnceLock;
use std::thread;
use std::time::Duration;


use syslog_rs::formatters::DefaultSyslogFormatterFile;
use syslog_rs::sy_sync::Syslog;
use syslog_rs::sync::DefaultQueueAdapter;
use syslog_rs::{LogFacility, LogStat, Priority, SyslogFile, SyslogQueue};
use syslog_rs::AsyncSyslogQueueApi;
use tokio::sync::{mpsc};
use tokio::{runtime, task};

pub static SYSLOG: OnceLock<Syslog<SyslogFile, SyslogQueue<DefaultQueueAdapter, DefaultSyslogFormatterFile, SyslogFile>>> = OnceLock::new();


macro_rules! logdebug 
{
    ($($arg:tt)*) => (
        SYSLOG.get().as_ref().unwrap().syslog(Priority::LOG_DEBUG, format!($($arg)*))
    )
}

macro_rules! alogdebug 
{
    ($($arg:tt)*) => (
        SYSLOG.get().as_ref().unwrap().a_syslog(Priority::LOG_DEBUG, format!($($arg)*)).await
    )
}


pub fn main()
{
    SYSLOG.get_or_init(move || {
        Syslog::<SyslogFile, SyslogQueue<_, DefaultSyslogFormatterFile, SyslogFile>>::openlog_with(
            Some("example"), 
            LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
            LogFacility::LOG_DAEMON,
            SyslogFile::new("/tmp/example_logtofile.txt")
        )
        .unwrap()
    });
    

    logdebug!("test message logtofile!");

    thread::sleep(Duration::from_micros(10));

    let runtime = 
        runtime::Builder::new_multi_thread()
            .enable_all()
            .build()
            .unwrap();

    runtime.block_on(async 
        {
            let s =
                task::spawn_blocking(move || 
                    {
                        for i in 0..10
                        {
                            logdebug!("blocking thread message no: '{}'", i);
                        
                            thread::sleep(Duration::from_micros(300));
                        }

                        return;
                    }
                );

            for i in 0..10
            {
                alogdebug!("async thread message no: '{}'", i);

                tokio::time::sleep(Duration::from_micros(304)).await;
            }

            s.await.unwrap();

            let (tx, mut rx) = mpsc::channel::<u64>(1);

            task::spawn_blocking(move || 
                {
                    SYSLOG.get().unwrap().update_tap(SyslogFile::new("/tmp/example_logtofile2.txt")).unwrap();

                    tx.blocking_send(0).unwrap();

                    for i in 0..10
                    {
                        logdebug!("blocking NEW thread message no: '{}'", i);
                    
                        thread::sleep(Duration::from_micros(300));
                    }
                    return;
                }
            );

            rx.recv().await;

            for i in 0..10
            {
                alogdebug!("async NEW thread message no: '{}'", i);

                tokio::time::sleep(Duration::from_micros(304)).await;
            }
        }
    );

    logdebug!("test message logtofile!");

    return;
}

Exampe UDP

This example requires the feature build_ext_net to be enabled.


use std::{sync::LazyLock, thread};
use std::time::Duration;



use syslog_rs::formatters::DefaultSyslogFormatter;
use syslog_rs::sy_sync::Syslog;
use syslog_rs::{LogFacility, LogStat, Priority, SyslogNetUdp, SyslogShared};

pub static SYSLOG: LazyLock<Syslog<SyslogNetUdp, SyslogShared<DefaultSyslogFormatter, SyslogNetUdp>>> = LazyLock::new(|| 
    {
        Syslog::<SyslogNetUdp, SyslogShared<DefaultSyslogFormatter, SyslogNetUdp>>::openlog_with(
            Some("example"), 
            LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
            LogFacility::LOG_DAEMON,
            SyslogNetUdp::new("127.0.0.1:7777", None).unwrap()            
        )
        .unwrap()
    }
);

macro_rules! logdebug 
{
    ($($arg:tt)*) => (
        SYSLOG.syslog(Priority::LOG_DEBUG, format!($($arg)*))
    )
}

pub fn main()
{
    // netcat -ul 7777
    logdebug!("test message!");

    thread::sleep(Duration::from_micros(10));

    return;
}

TLS

This exampel requires the feature build_ext_tls to be enabled.

use std::{sync::LazyLock, thread};
use std::time::Duration;

use syslog_rs::formatters::DefaultSyslogFormatter;
use syslog_rs::sy_sync::Syslog;
use syslog_rs::{LogFacility, LogStat, Priority, SyslogShared, SyslogTls};

pub const  CERT_INLINE: &'static [u8] = b"cert...";

pub static SYSLOG: LazyLock<Syslog<SyslogTls, SyslogShared<DefaultSyslogFormatter, SyslogTls>>> = LazyLock::new(|| 
    {
        Syslog::<SyslogTls, SyslogShared<DefaultSyslogFormatter, SyslogTls>>::openlog_with(
            Some("example"), 
            LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
            LogFacility::LOG_DAEMON,
            SyslogTls::new("127.0.0.1:514", None, "domain.tld", CERT_INLINE.to_vec(), None).unwrap()
        )
        .unwrap()
    }
);

macro_rules! logdebug 
{
    ($($arg:tt)*) => (
        SYSLOG.syslog(Priority::LOG_DEBUG, format!($($arg)*))
    )
}

pub fn main()
{
    logdebug!("test message!");

    thread::sleep(Duration::from_micros(10));

    return;
}

SMOL

use std::time::Duration;

use smol::{io, Timer};
use smol::lock::OnceCell;
use syslog_rs::sy_async::AsyncSyslog;
use syslog_rs::{LogFacility, LogStat, Priority, SyslogLocal};


pub static SYSLOG: OnceCell<AsyncSyslog> = OnceCell::new();

macro_rules! logdebug 
{
    ($($arg:tt)*) => (
        SYSLOG.get().unwrap().syslog(Priority::LOG_DEBUG, format!($($arg)*)).await
    )
}

fn main() -> io::Result<()> 
{
    smol::block_on(
        async 
        {
            let syslog =
                AsyncSyslog::openlog(
                        Some("smol_example"), 
                        LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
                        LogFacility::LOG_DAEMON,
                        SyslogLocal::new()
                    )
                    .await
                    .unwrap();


            SYSLOG.get_or_init(|| async { syslog }).await;


            logdebug!("SMOL test message async start!");
            
            SYSLOG.get().unwrap().vsyslog(Priority::LOG_DEBUG, "SMOL test 2").await;

            Timer::after(Duration::from_micros(10)).await;

            SYSLOG.get().unwrap().change_identity("SMOL_new_identity").await;

            logdebug!("SMOL test message new identity!");

            Timer::after(Duration::from_micros(10)).await;

            logdebug!("SMOL test 123!");
            logdebug!("SMOL test 123123! end ");

            Ok(())
        }
    )
}

External async executor

From the crate version 3, the crate can be provided with custom implementation of the async props like sockets or other things. In order to achieve this some traits should be implemented in the program which uses this crate and enables the build_async_interface. The build-in async exec like tokio or smol can not be enabled together.

See example file.


extern crate syslog_rs; 

use std::fmt;
use std::io::ErrorKind;
use std::net::Shutdown;
use std::time::Duration;
use std::{borrow::Cow, io::IoSlice};

use syslog_rs::a_sync::AsyncMutexGuard;
use syslog_rs::formatters::DefaultSyslogFormatter;
use syslog_rs::nix::errno::Errno;
use syslog_rs::nix::libc;

use syslog_rs::error::SyslogError;
use syslog_rs::{common, throw_error_errno, throw_error_os, AsyncSyslog, AsyncSyslogTap, AsyncTap, LogFacility, LogStat, Priority, SyslogDestMsg, SyslogLocal, TapType, PATH_CONSOLE, PATH_LOG, PATH_LOG_PRIV, PATH_OLDLOG, PATH_OSX};
use syslog_rs::{a_sync::{syslog_async_internal::{AsyncSyslogInternal, AsyncSyslogInternalIO}, AsyncMutex}, error::SyRes, formatters::SyslogFormatter, map_error_os, AsyncSyslogDestination};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use tokio::net::UnixDatagram;
use tokio::sync::MutexGuard;
use tokio::time::sleep;
use tokio::{io::AsyncWrite, sync::Mutex};

/// Implemeting the custom IO and thread control things which is needed by the crate.
#[derive(Debug)]
pub struct OurAsyncSyslogIO;

impl OurAsyncSyslogIO
{
    /// Sends to the FD i.e file of stderr, stdout or any which 
    /// implements [Write] `write_vectored` in async manner
    ///
    /// # Arguments
    /// 
    /// * `file_fd` - mutable consume of the container FD.
    ///
    /// * `msg` - a reference on array of data
    ///
    /// * `newline` - a new line string ref i.e "\n" or "\r\n"
    pub(crate) async 
    fn async_send_to_fd<W>(mut file_fd: W, msg: &[Cow<'_, str>], newline: &str) -> SyRes<usize>
    where W: AsyncWrite + Unpin
    {
        let mut iov_list: Vec<IoSlice<'_>> = Vec::with_capacity(msg.len() + 1);

        msg.iter().for_each(|v| iov_list.push(IoSlice::new(v.as_bytes())));
        iov_list.push(IoSlice::new(newline.as_bytes()));


        return 
            file_fd
                .write_vectored(&iov_list)
                .await
                .map_err(|e|
                    map_error_os!(e, "async_send_to_fd() writev() failed")
                );
    } 
}

impl AsyncSyslogInternalIO for OurAsyncSyslogIO
{
    #[inline]
    async 
    fn send_to_stderr(logstat: LogStat, msg: &[Cow<'_, str>])
    {
        if logstat.intersects(LogStat::LOG_PERROR) == true
        {
            let stderr_lock = tokio::io::stderr();

            let newline = "\n";
            let _ = Self::async_send_to_fd(stderr_lock, msg, newline).await;
        }
    }

    #[inline]
    async 
    fn send_to_syscons(logstat: LogStat, msg_payload: &[Cow<'_, str>])
    {
        if logstat.intersects(LogStat::LOG_CONS)
        {
            let syscons = 
                File
                    ::options()
                        .create(false)
                        .read(false)
                        .write(true)
                        .custom_flags(libc::O_NONBLOCK | libc::O_CLOEXEC)
                        .open(*PATH_CONSOLE)
                        .await;

            if let Ok(file) = syscons
            {
                let newline = "\n";
                let _ = Self::async_send_to_fd(file, msg_payload, newline);
            }
        }
    }
    
    async 
    fn sleep_micro(us: u64) 
    {
        sleep(Duration::from_micros(us)).await;
    }

}

/// Implementing the mutex realization for out executor.
#[derive(Debug)]
struct MutexOveride<T: Sized>(Mutex<T>);

/// Also the mutex guard.
#[derive(Debug)]
struct MutexGuardNative<'a, T>(MutexGuard<'a, T>);

impl<'a, T>  MutexGuardNative<'a, T>
{ 
    fn new(g:  MutexGuard<'a, T>) -> Self
    {
        return Self(g);
    }
}

impl<F: SyslogFormatter + Send, D: AsyncSyslogDestination> AsyncMutex<F, D, AsyncSyslogInternal<F, D, OurAsyncSyslogIO>> 
for MutexOveride<AsyncSyslogInternal<F, D, OurAsyncSyslogIO>>
{
    type MutxGuard<'mux> = MutexGuardNative<'mux, AsyncSyslogInternal<F, D, OurAsyncSyslogIO>>;

    fn a_new(v: AsyncSyslogInternal<F, D, OurAsyncSyslogIO>) -> Self 
    {
        return Self(Mutex::new(v));
    }
    
    async 
    fn a_lock<'mux>(&'mux self) -> Self::MutxGuard<'mux>
    {
        return MutexGuardNative::new(self.0.lock().await);
    }
}

impl<'mux, F: SyslogFormatter + Send, D: AsyncSyslogDestination> AsyncMutexGuard<'mux, F, D, AsyncSyslogInternal<F, D, OurAsyncSyslogIO>>
for MutexGuardNative<'mux, AsyncSyslogInternal<F, D, OurAsyncSyslogIO>>
{
    fn guard(&self) -> &AsyncSyslogInternal<F, D, OurAsyncSyslogIO>
    {
        return &self.0;
    }

    fn guard_mut(&mut self) -> &mut AsyncSyslogInternal<F, D, OurAsyncSyslogIO>
    {
        return &mut self.0;
    }
}

/// Implementing the sylog provider (in this example a SyslogLocal will be wrapped into our struct
/// because it is not possible to implement foreign traits for foreign structs.
#[derive(Debug, Clone)]
struct NativeSyslogLocal(SyslogLocal);

impl NativeSyslogLocal
{
    fn new(s: SyslogLocal) -> Self
    {
        return Self(s);
    }
}

impl fmt::Display for NativeSyslogLocal
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
    {
        write!(f, "{}", self.0)
    }
}

/// A async destination where the type of the tap i.e socket is declared.
impl AsyncSyslogDestination for NativeSyslogLocal
{
    type SocketTap = AsyncTap::<tokio::net::UnixDatagram, Self>;

}

/// A maximum message size.
impl SyslogDestMsg for NativeSyslogLocal
{
    fn get_max_msg_len(&self) -> usize 
    {
        if *common::RFC5424_MAX_DGRAM >= common::MAXLINE
        {
            return common::MAXLINE;
        }
        else
        {
            return *common::RFC5424_MAX_DGRAM;
        };
    }
}

/// Implementing a [AsyncSyslogTap] for the previously created [NativeSyslogLocal] for the
/// [AsyncTap] - socket.
impl AsyncSyslogTap<NativeSyslogLocal> for AsyncTap<UnixDatagram, NativeSyslogLocal>
{
    fn new(req_tap: NativeSyslogLocal) -> SyRes<Self>
    {
        return Self::new(req_tap);
    }


    async 
    fn connectlog(&mut self) -> SyRes<()> 
    {
        let sock = 
            UnixDatagram
                ::unbound()
                    .map_err(|e|
                        map_error_os!(e, "unbounded unix datagram initialization failure: {}", e)
                    )?;

        let tap_type = 
            if self.get_tap_data().0.get_use_alternative() == false && self.get_tap_data().0.get_custom_remote_path().is_some() == true 
            {
                if let Err(e) = sock.connect(self.get_tap_data().0.get_custom_remote_path().as_ref().unwrap())
                {
                    throw_error_os!(e, "failed to open connection to syslog server at '{}'", 
                        self.get_tap_data().0.get_custom_remote_path().as_ref().unwrap().display());
                }
                else 
                {
                    TapType::CustomLog
                }
            }
            else if self.get_tap_data().0.get_custom_remote_path().is_some() == true &&
                sock.connect(self.get_tap_data().0.get_custom_remote_path().as_ref().unwrap()).is_ok() == true
            {
                TapType::CustomLog
            }
            else if let Ok(_) = sock.connect(PATH_LOG_PRIV)
            {
                TapType::Priv
            }
            else if let Ok(_) = sock.connect(PATH_LOG)
            {
                TapType::UnPriv
            }
            else if let Ok(_) = sock.connect(PATH_OLDLOG)
            {
                TapType::OldLog
            }
            else if let Ok(_) = sock.connect(PATH_OSX)
            {
                TapType::Priv
            }
            else
            {
                // failed to open socket
                throw_error_errno!(Errno::last(), "failed to open connection to syslog server");
            };

        self.set_sock(sock);
        self.set_cur_tap_type(tap_type);

        return Ok(());
    }

    async 
    fn send(&mut self, msg: &[u8]) -> std::io::Result<usize> 
    {
        let sock = 
            self
                .get_sock_mut()
                .ok_or_else(||
                    std::io::Error::new(ErrorKind::NotConnected, "no connection")
                )?;

        return sock.send(msg).await;
    }

    async 
    fn disconnectlog(&mut self) -> std::io::Result<()>
    {
        match self.take_sock()
        {
            Some(s) => 
            {
                self.set_cur_tap_type(TapType::None);

                s.shutdown(Shutdown::Both)
            },
            None =>
            {
                self.set_cur_tap_type(TapType::None);

                Ok(())
            }
        }
    }

    fn is_connected(&self) -> bool
    {
        return self.get_sock().is_some();
    }

    fn get_type(&self) -> TapType 
    {
        return self.get_tap_type();
    }

    fn get_max_msg_size(&self) -> usize
    {
        return self.get_tap_data().get_max_msg_len();
    }

    fn update_tap_data(&mut self, tap_data: NativeSyslogLocal)
    {
        self.update_tap_data(tap_data); 
    }
}

#[tokio::main]
async
fn main() 
{
    // provide everything which was created to the AsyncSyslog.
    let syslog =
        AsyncSyslog
            ::<NativeSyslogLocal, DefaultSyslogFormatter, OurAsyncSyslogIO, MutexOveride<_>>
            ::openlog_with(
                Some("example_cust"), 
                LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
                LogFacility::LOG_DAEMON,
                NativeSyslogLocal::new(SyslogLocal::new())
            )
            .await
            .unwrap();

    syslog.vsyslog(Priority::LOG_DEBUG, "test custom async exec in example").await;

    println!("Hello, world!");
}

</details>

Benchmarking

The test spawns 2 threads and one main thread. All 3 threads are sending messages to syslog. The time measurment in the tables are approximations.

Results of the tests in syslog_*.rs files in Debug mode (AMD Ryzen 5 7600X 6-Core Processor):

use_sync (sys mutex) use_sync_queue build_with_async
main: 87.31µs main: 12.74µs main: 94.849µs
t1: 170.809µs t2: 1.77µs t2: 12.339µs
t2: 69.529µs t1: 4.49µs t1: 7.2µs
t1: 6.87µs t2: 630ns t1: 16.32µs
t2: 5.8µs t1: 320ns t2: 6.26µs
t1: 5.7µs t2: 1.13µs t2: 14.74µs
t2: 5.46µs t1: 280ns t1: 6.7µs
t1: 5.83µs t1: 950ns t1: 8.73µs
t2: 7.239µs t2: 750ns t2: 5.37µs
t1: 5.8µs t1: 720ns t2: 8.02µs
t2: 5.38µs t2: 700ns t1: 5.34µs