6 releases (stable)

Uses new Rust 2024

new 2.0.1 Jun 5, 2025
1.1.1 May 3, 2025
1.1.0 Feb 16, 2025
0.11.0 Feb 10, 2025
0.2.1 Jun 12, 2021

#180 in Asynchronous

Download history 493/week @ 2025-02-15 27/week @ 2025-02-22 15/week @ 2025-03-01 122/week @ 2025-05-03 19/week @ 2025-05-10 4/week @ 2025-05-17 79/week @ 2025-05-31

105 downloads per month

MPL-2.0 OR EUPL-1.2

275KB
6K SLoC

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 2.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_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.

                                                                      
                            blocking send                             
                    ---------------------------->                     
                                                                      
                                                                      
+----------------------+    CHANNEL           +----------------------+
|                      |                      |                      |
|      SYNC_QUEUE      |-----------------------     SYSLOG_WORKER    |
|                      |    crossbeam/        |                      |
+----------------------+    tokio mpsc/       +----------------------+
         |                  smol mpsc                       ^         
         |          -                                       |         
         v                                                  |         
     +------------+                                         |         
     |  adapter   |                                         |         
     +------------+    shares the channel with async client |         
         |                                                  |         
+--------v-------------+                                    |         
|                      |                                    |         
|     ASYNC_SYSLOG     |  ---------------------------------->         
|                      |   non-blocking send                          
+----------------------+                                              

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 = "2.0"

For customization: syslog-rs = {version = "2.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::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 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>>> = 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;

use syslog_rs::sy_sync::Syslog;
use syslog_rs::{formatters::{FormatRfc3146, 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("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, SyslogFile>>> = OnceLock::new();

pub static ASYSLOG: OnceCell<AsyncSyslog<SyslogFile, AsyncSyslogQueue<DefaultSyslogFormatterFile, SyslogFile>>> = 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, 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 
        {
            ASYSLOG.get_or_init(|| async {
                AsyncSyslog::<SyslogFile, AsyncSyslogQueue<DefaultSyslogFormatterFile, SyslogFile>>::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, 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

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.unwrap();

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

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

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

            Ok(())
        }
    )
}

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

Dependencies

~3–15MB
~219K SLoC

OSZAR »