#![cfg_attr(docsrs, feature(doc_cfg))]
use std::io;
use std::sync::Mutex;
use std::sync::MutexGuard;
use fasyslog::SDElement;
use fasyslog::format::SyslogContext;
use fasyslog::sender::SyslogSender;
use logforth_core::Append;
use logforth_core::Diagnostic;
use logforth_core::Error;
use logforth_core::Layout;
use logforth_core::record::Level;
use logforth_core::record::Record;
pub extern crate fasyslog;
#[derive(Debug, Copy, Clone)]
pub enum SyslogFormat {
RFC3164,
RFC5424,
}
#[derive(Debug)]
pub struct SyslogBuilder {
sender: SyslogSender,
formatter: SyslogFormatter,
}
impl SyslogBuilder {
pub fn new(sender: SyslogSender) -> Self {
Self {
sender,
formatter: SyslogFormatter {
format: SyslogFormat::RFC3164,
context: SyslogContext::default(),
layout: None,
},
}
}
pub fn build(self) -> Syslog {
let SyslogBuilder { sender, formatter } = self;
Syslog::new(sender, formatter)
}
pub fn format(mut self, format: SyslogFormat) -> Self {
self.formatter.format = format;
self
}
pub fn context(mut self, context: SyslogContext) -> Self {
self.formatter.context = context;
self
}
pub fn layout(mut self, layout: impl Into<Box<dyn Layout>>) -> Self {
self.formatter.layout = Some(layout.into());
self
}
pub fn tcp_well_known() -> io::Result<SyslogBuilder> {
fasyslog::sender::tcp_well_known()
.map(SyslogSender::Tcp)
.map(Self::new)
}
pub fn tcp<A: std::net::ToSocketAddrs>(addr: A) -> io::Result<SyslogBuilder> {
fasyslog::sender::tcp(addr)
.map(SyslogSender::Tcp)
.map(Self::new)
}
pub fn udp_well_known() -> io::Result<SyslogBuilder> {
fasyslog::sender::udp_well_known()
.map(SyslogSender::Udp)
.map(Self::new)
}
pub fn udp<L: std::net::ToSocketAddrs, R: std::net::ToSocketAddrs>(
local: L,
remote: R,
) -> io::Result<SyslogBuilder> {
fasyslog::sender::udp(local, remote)
.map(SyslogSender::Udp)
.map(Self::new)
}
pub fn broadcast_well_known() -> io::Result<SyslogBuilder> {
fasyslog::sender::broadcast_well_known()
.map(SyslogSender::Udp)
.map(Self::new)
}
pub fn broadcast(port: u16) -> io::Result<SyslogBuilder> {
fasyslog::sender::broadcast(port)
.map(SyslogSender::Udp)
.map(Self::new)
}
}
#[cfg(feature = "rustls")]
mod rustls_ext {
use std::io;
use std::net::ToSocketAddrs;
use std::sync::Arc;
use fasyslog::sender::SyslogSender;
use fasyslog::sender::rustls::ClientConfig;
use super::SyslogBuilder;
impl SyslogBuilder {
pub fn rustls_well_known<S: Into<String>>(domain: S) -> io::Result<SyslogBuilder> {
fasyslog::sender::rustls_well_known(domain)
.map(Box::new)
.map(SyslogSender::RustlsSender)
.map(Self::new)
}
pub fn rustls<A: ToSocketAddrs, S: Into<String>>(
addr: A,
domain: S,
) -> io::Result<SyslogBuilder> {
fasyslog::sender::rustls(addr, domain)
.map(Box::new)
.map(SyslogSender::RustlsSender)
.map(Self::new)
}
pub fn rustls_with<A: ToSocketAddrs, S: Into<String>>(
addr: A,
domain: S,
config: Arc<ClientConfig>,
) -> io::Result<SyslogBuilder> {
fasyslog::sender::rustls_with(addr, domain, config)
.map(Box::new)
.map(SyslogSender::RustlsSender)
.map(Self::new)
}
}
}
#[cfg(feature = "native-tls")]
mod native_tls_ext {
use std::io;
use std::net::ToSocketAddrs;
use fasyslog::sender::SyslogSender;
use fasyslog::sender::native_tls::TlsConnectorBuilder;
use super::SyslogBuilder;
impl SyslogBuilder {
pub fn native_tls_well_known<S: AsRef<str>>(domain: S) -> io::Result<SyslogBuilder> {
fasyslog::sender::native_tls_well_known(domain)
.map(SyslogSender::NativeTlsSender)
.map(Self::new)
}
pub fn native_tls<A: ToSocketAddrs, S: AsRef<str>>(
addr: A,
domain: S,
) -> io::Result<SyslogBuilder> {
fasyslog::sender::native_tls(addr, domain)
.map(SyslogSender::NativeTlsSender)
.map(Self::new)
}
pub fn native_tls_with<A: ToSocketAddrs, S: AsRef<str>>(
addr: A,
domain: S,
builder: TlsConnectorBuilder,
) -> io::Result<SyslogBuilder> {
fasyslog::sender::native_tls_with(addr, domain, builder)
.map(SyslogSender::NativeTlsSender)
.map(Self::new)
}
}
}
#[cfg(unix)]
mod unix_ext {
use std::io;
use fasyslog::sender::SyslogSender;
use super::SyslogBuilder;
impl SyslogBuilder {
pub fn unix_stream(path: impl AsRef<std::path::Path>) -> io::Result<SyslogBuilder> {
fasyslog::sender::unix_stream(path)
.map(SyslogSender::UnixStream)
.map(Self::new)
}
pub fn unix_datagram(path: impl AsRef<std::path::Path>) -> io::Result<SyslogBuilder> {
fasyslog::sender::unix_datagram(path)
.map(SyslogSender::UnixDatagram)
.map(Self::new)
}
pub fn unix(path: impl AsRef<std::path::Path>) -> io::Result<SyslogBuilder> {
fasyslog::sender::unix(path).map(Self::new)
}
}
}
#[derive(Debug)]
pub struct Syslog {
sender: Mutex<SyslogSender>,
formatter: SyslogFormatter,
}
impl Syslog {
fn new(sender: SyslogSender, formatter: SyslogFormatter) -> Self {
let sender = Mutex::new(sender);
Self { sender, formatter }
}
fn sender(&self) -> MutexGuard<'_, SyslogSender> {
self.sender.lock().unwrap_or_else(|e| e.into_inner())
}
}
impl Append for Syslog {
fn append(&self, record: &Record, diags: &[Box<dyn Diagnostic>]) -> Result<(), Error> {
let message = self.formatter.format_message(record, diags)?;
let mut sender = self.sender();
sender
.send_formatted(&message)
.map_err(Error::from_io_error)?;
Ok(())
}
fn flush(&self) -> Result<(), Error> {
let mut sender = self.sender();
sender.flush().map_err(Error::from_io_error)?;
Ok(())
}
}
impl Drop for Syslog {
fn drop(&mut self) {
let sender = self.sender.get_mut().unwrap_or_else(|e| e.into_inner());
let _ = sender.flush();
}
}
#[derive(Debug)]
struct SyslogFormatter {
format: SyslogFormat,
context: SyslogContext,
layout: Option<Box<dyn Layout>>,
}
fn log_level_to_syslog_severity(level: Level) -> fasyslog::Severity {
match level {
Level::Fatal | Level::Fatal2 | Level::Fatal3 | Level::Fatal4 => {
fasyslog::Severity::EMERGENCY
}
Level::Error3 | Level::Error4 => fasyslog::Severity::ALERT,
Level::Error2 => fasyslog::Severity::CRITICAL,
Level::Error => fasyslog::Severity::ERROR,
Level::Warn | Level::Warn2 | Level::Warn3 | Level::Warn4 => fasyslog::Severity::WARNING,
Level::Info2 | Level::Info3 | Level::Info4 => fasyslog::Severity::NOTICE,
Level::Info => fasyslog::Severity::INFORMATIONAL,
Level::Debug
| Level::Debug2
| Level::Debug3
| Level::Debug4
| Level::Trace
| Level::Trace2
| Level::Trace3
| Level::Trace4 => fasyslog::Severity::DEBUG,
}
}
impl SyslogFormatter {
fn format_message(
&self,
record: &Record,
diags: &[Box<dyn Diagnostic>],
) -> Result<Vec<u8>, Error> {
let severity = log_level_to_syslog_severity(record.level());
let message = match self.format {
SyslogFormat::RFC3164 => match self.layout {
None => format!(
"{}",
self.context
.format_rfc3164(severity, Some(record.payload()))
),
Some(ref layout) => {
let message = layout.format(record, diags)?;
let message = String::from_utf8_lossy(&message);
format!("{}", self.context.format_rfc3164(severity, Some(message)))
}
},
SyslogFormat::RFC5424 => {
const EMPTY_MSGID: Option<&str> = None;
const EMPTY_STRUCTURED_DATA: Vec<SDElement> = Vec::new();
match self.layout {
None => format!(
"{}",
self.context.format_rfc5424(
severity,
EMPTY_MSGID,
EMPTY_STRUCTURED_DATA,
Some(record.payload())
)
),
Some(ref layout) => {
let message = layout.format(record, diags)?;
let message = String::from_utf8_lossy(&message);
format!(
"{}",
self.context.format_rfc5424(
severity,
EMPTY_MSGID,
EMPTY_STRUCTURED_DATA,
Some(message)
)
)
}
}
}
};
Ok(message.into_bytes())
}
}