26 releases (2 stable)

new 1.1.0 Feb 16, 2025
0.11.0 Feb 10, 2025
0.10.0 Dec 27, 2024
0.7.1 Oct 20, 2024
0.2.1 Jun 12, 2021

#93 in Asynchronous

Download history 569/week @ 2024-12-09 34/week @ 2024-12-16 250/week @ 2024-12-23 4/week @ 2025-01-06 2/week @ 2025-01-13 148/week @ 2025-02-10

148 downloads per month

EUPL-1.2

230KB
5K SLoC

syslog-rs

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

This crate does not include/use AI generated code! The AI generated code is prohibited.

Issues tracker: A public crate's Issues Tracker

v 1.1.0

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.

Available features:

  • feature = "use_async" for asynchronious code
  • feature = "use_sync" for synchronious code
  • feature = "use_sync_queue" for synchronious with async processing
  • feature = "build_with_net" enables the TCP/UDP and extended interface

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 is different in some ways. Also, it spawns a worker thread which sends messages from the queue to syslog.

The use_async is async realization of the use_sync. Untested, probably requires further dev.

Features:

  • build_with_file enables the functionality to open a log file directly on local FS. And a subset of the use_sync_queue. this feature enables the code which allowes to log into file using sync_queue. It is possible to log from both sync and async code, see example_logtofile.rs.
  • build_with_net enables the TCP, UDP, TLS (should be enabled separatly using build_with_tls)
  • build_with_tls enables the TLS for TCP (a subset of build_with_net)

All 3 features can be used simpltaniously.

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.

!!! use_async is using tokio mutex to achieve the synchronization. On the large async queues, when many tasks are spawned, the syslog becomes a performace bottleneck because syslog server may be busy, and syslog() is slow, and calling syslog() will lock other tasks until the lock will be released. Maybe it is good idea to have for example for each tokio thread a sibgle instance of the syslog.

Usage:

For default syslog-rs = "1.0"

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

Supports

  • GNU/Linux RFC3164 (UTF-8 by default)
  • *BSD and OSX RFC5424 (BOM UTF-8 by default)

Contributors

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

Developed by: Aleksandr Morozov

Examples

See ./examples/ in the repository.

Sync syslog (Local)



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

#[cfg(feature = "use_sync")]
use syslog_rs::sy_sync::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)

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};
#[cfg(feature = "use_sync")]
use syslog_rs::sy_sync::Syslog;
use syslog_rs::{SyslogFile, NEXTLINE, WSPACE};
use syslog_rs::{common, formatters::{SyslogFormatted, SyslogFormatter}, SyslogShared, TapType};
#[cfg(feature = "use_sync")]
use syslog_rs::{LogStat, LogFacility, Priority};

pub static SYNC_SYSLOG: OnceLock<Syslog<SyslogFile, SyslogShared<MyFormatter>>> = 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, 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>>
            ::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;

#[cfg(feature = "use_sync")]
use syslog_rs::sy_sync::Syslog;
use syslog_rs::{formatters::{FormatRfc3146, FormatRfc5424}, SyslogLocal, SyslogShared};
#[cfg(feature = "use_sync")]
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("example"), 
            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




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


use syslog_rs::formatters::DefaultSyslogFormatterFile;
use syslog_rs::sy_async::AsyncSyslog;
use syslog_rs::sy_sync::Syslog;
use syslog_rs::{AsyncSyslogQueue, LogFacility, LogStat, Priority, SyslogFile, SyslogQueue};
use tokio::sync::{mpsc, OnceCell};
use tokio::{runtime, task};

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

pub static ASYSLOG: OnceCell<AsyncSyslog<SyslogFile, AsyncSyslogQueue<DefaultSyslogFormatterFile>>> = OnceCell::const_new();


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

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


pub fn main()
{
    SYSLOG.get_or_init(move || {
        Syslog::<SyslogFile, SyslogQueue<DefaultSyslogFormatterFile>>::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 
        {
            ASYSLOG.get_or_init(|| async {
                AsyncSyslog::<SyslogFile, AsyncSyslogQueue<DefaultSyslogFormatterFile>>::attach_queue(SYSLOG.get().unwrap()).await.unwrap()
            }).await;

            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;
}


Log to file




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


use syslog_rs::formatters::DefaultSyslogFormatterFile;
use syslog_rs::sy_async::AsyncSyslog;
use syslog_rs::sy_sync::Syslog;
use syslog_rs::{AsyncSyslogQueue, LogFacility, LogStat, Priority, SyslogFile, SyslogQueue};
use tokio::sync::{mpsc, OnceCell};
use tokio::{runtime, task};

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

pub static ASYSLOG: OnceCell<AsyncSyslog<SyslogFile, AsyncSyslogQueue<DefaultSyslogFormatterFile>>> = OnceCell::const_new();


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

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


pub fn main()
{
    SYSLOG.get_or_init(move || {
        Syslog::<SyslogFile, SyslogQueue<DefaultSyslogFormatterFile>>::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 
        {
            ASYSLOG.get_or_init(|| async {
                AsyncSyslog::<SyslogFile, AsyncSyslogQueue<DefaultSyslogFormatterFile>>::attach_queue(SYSLOG.get().unwrap()).await.unwrap()
            }).await;

            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


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, SyslogNet, SyslogShared};

pub static SYSLOG: LazyLock<Syslog<SyslogNet, SyslogShared<DefaultSyslogFormatter>>> = LazyLock::new(|| 
    {
        Syslog::<SyslogNet, SyslogShared<DefaultSyslogFormatter>>::openlog_with(
            Some("example"), 
            LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
            LogFacility::LOG_DAEMON,
            SyslogNet::new_udp("127.0.0.1:514")            
        )
        .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;
}

TLS

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>>> = LazyLock::new(|| 
    {
        Syslog::<SyslogTls, SyslogShared<DefaultSyslogFormatter>>::openlog_with(
            Some("example"), 
            LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
            LogFacility::LOG_DAEMON,
            SyslogTls::new("127.0.0.1:514", "domain.tld", CERT_INLINE.to_vec())
        )
        .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;
}

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 use_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

Dependencies

~12–22MB
~397K SLoC