logger2 0.1.5

A modern, feature-rich replacement for the classic `logger` CLI: syslog (local/remote, RFC3164 & RFC5424), truecolor hex output, emoji, JSON, and a fully configurable TOML config file.
Documentation
use crate::error::{Logger2Error, Result};
use std::io::Write;
use std::net::{TcpStream, UdpSocket};
use std::path::Path;
use std::time::Duration;

/// Sends a fully-formatted syslog line to the local system logger via its
/// Unix domain socket (e.g. `/dev/log` on Linux, `/var/run/syslog` on macOS).
#[cfg(unix)]
pub fn send_local(socket_path: &Path, message: &str) -> Result<()> {
    use std::os::unix::net::UnixDatagram;

    let sock = UnixDatagram::unbound()?;
    // Most syslog daemons expect a datagram; some (notably older syslog-ng
    // configs) expect a stream socket. Try datagram first, then fall back.
    match sock.connect(socket_path) {
        Ok(()) => {
            sock.send(message.as_bytes())
                .map_err(|e| Logger2Error::Network(socket_path.display().to_string(), e))?;
            Ok(())
        }
        Err(_) => {
            use std::os::unix::net::UnixStream;
            let mut stream = UnixStream::connect(socket_path)
                .map_err(|e| Logger2Error::Network(socket_path.display().to_string(), e))?;
            stream
                .write_all(message.as_bytes())
                .map_err(|e| Logger2Error::Network(socket_path.display().to_string(), e))?;
            Ok(())
        }
    }
}

#[cfg(not(unix))]
pub fn send_local(_socket_path: &Path, _message: &str) -> Result<()> {
    Err(Logger2Error::UnsupportedPlatform(
        "local unix-socket syslog is only available on unix-like systems; use --server to log to a remote syslog daemon instead".to_string(),
    ))
}

/// Sends a message to a remote syslog daemon over UDP.
pub fn send_udp(server: &str, port: u16, message: &str) -> Result<()> {
    let sock = UdpSocket::bind("0.0.0.0:0")
        .map_err(|e| Logger2Error::Network(format!("{server}:{port}"), e))?;
    sock.connect((server, port))
        .map_err(|e| Logger2Error::Network(format!("{server}:{port}"), e))?;
    sock.send(message.as_bytes())
        .map_err(|e| Logger2Error::Network(format!("{server}:{port}"), e))?;
    Ok(())
}

/// Sends a message to a remote syslog daemon over TCP, with an optional
/// connect timeout.
pub fn send_tcp(server: &str, port: u16, message: &str, timeout: Duration) -> Result<()> {
    use std::net::ToSocketAddrs;

    let addr = format!("{server}:{port}")
        .to_socket_addrs()
        .map_err(|e| Logger2Error::Network(format!("{server}:{port}"), e))?
        .next()
        .ok_or_else(|| {
            Logger2Error::Network(
                format!("{server}:{port}"),
                std::io::Error::new(std::io::ErrorKind::NotFound, "no address resolved"),
            )
        })?;

    let mut stream = TcpStream::connect_timeout(&addr, timeout)
        .map_err(|e| Logger2Error::Network(format!("{server}:{port}"), e))?;
    stream
        .write_all(message.as_bytes())
        .map_err(|e| Logger2Error::Network(format!("{server}:{port}"), e))?;
    Ok(())
}