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 |
|
0.2.1 |
|
#180 in Asynchronous
105 downloads per month
275KB
6K
SLoC
syslog-rs
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
orbuild_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 usingbuild_sync
only. And when used with one of thebuild_async_tokio
orbuild_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