syslog-rs 6.5.0

A native Rust implementation of the glibc/libc/windows syslog client and windows native log for logging.
Documentation
/*-
 * syslog-rs - a syslog client translated from libc to rust
 * 
 * Copyright 2025 Aleksandr Morozov
 * 
 * The syslog-rs crate can be redistributed and/or modified
 * under the terms of either of the following licenses:
 *
 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
 *
 *   2. The MIT License (MIT)
 *                     
 *   3. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
 */

/// The full message construction is the following:
/// 
/// "|", pri.bits().to_string().as_str(), "|"
/// 
/// (Priority.bits() | LogFacility.bits() );
/// 
/// same way as PRI for syslog.
/// 
/// **The payload is NULL-terminated!**

use std::borrow::Cow;

use crate::{CBRACE_SEM, OBRACE, SyslogMsgPriFac, WINDOWS_EVENT_REPORT_MAX_PAYLOAD_LEN, WSPACE, truncate, truncate_n};

use super::{SyslogFormatted, SyslogFormatter};



#[derive(Debug, Clone)]
#[repr(transparent)]
pub struct FormatWindows(Cow<'static, str>);

unsafe impl Send for FormatWindows {}

impl From<String> for FormatWindows
{
    fn from(value: String) -> FormatWindows 
    {
        return Self(Cow::Owned(value));
    }
}

impl From<&'static str> for FormatWindows
{
    fn from(value: &'static str) -> FormatWindows
    {
        return FormatWindows(Cow::Borrowed(value));
    }
}



impl SyslogFormatter for FormatWindows
{
    fn vsyslog1_format(&self, _max_msg_size: usize, prifac: SyslogMsgPriFac, progname: &str, pid: Option<&str>) -> SyslogFormatted
    {
        let msg_payload = truncate_n(&self.0, WINDOWS_EVENT_REPORT_MAX_PAYLOAD_LEN);

        let msg_payload_final = 
            if msg_payload.ends_with("\n") == true
            {
                truncate(&msg_payload)
            }
            else
            {
                &msg_payload
            };
        
        // message based on RFC 3164
        let msg_pri = 
            [
                "|", prifac.get_val().to_string().as_str(), "|"
            ]
            .concat();

        let msg_pid =
            if let Some(p) = pid
            {
                [OBRACE, p, CBRACE_SEM].concat()
            }
            else
            {
                [""].concat()
            };

        let msg_pkt = 
            [
                // hostname
                // " ".as_bytes(), hostname.as_bytes(), 
                // appname
                WSPACE, progname,
                // PID
                msg_pid.as_str(),
                // msg
                WSPACE, /*b"\xEF\xBB\xBF",*/ msg_payload_final,
                "\x00"
            ]
            .concat();


        return 
            SyslogFormatted
            { 
                msg_header: Some(msg_pri), 
                msg_payload: msg_pkt, 
                full_msg: None
            };
    }
}

#[cfg(test)]
mod test_format_windows
{
    use crate::{LogFacility, Priority};

    use super::*;

    #[test]
    fn test_1()
    {
        let msg = FormatWindows::from("test string!");
        let prifac = SyslogMsgPriFac::set_facility(Priority::LOG_ERR, LogFacility::LOG_DAEMON);
        let mut res = msg.vsyslog1_format(1024, prifac, "testprog", Some("102"));

        assert_eq!(res.msg_header.as_ref().map(|f| f.as_str()), Some("|27|"));

        let fullmsg = res.get_full_msg();

        println!("{}", fullmsg);

        assert_eq!(fullmsg.find("testprog"), Some(5));

        assert_eq!(fullmsg.find("[102]"), Some(13));

        assert_eq!(fullmsg.find("]:"), Some(17));

        assert_eq!(fullmsg.find("test string!"), Some(20));
    }
}