syslog-rs 0.2.1

A native Rust implementation of the glibc/libc syslog.
Documentation
/*-
* syslog-rs - a syslog client translated from libc to rust
* Copyright (C) 2021  Aleksandr Morozov
* 
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
* 
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

use std::sync::atomic::{AtomicBool, Ordering};

use crossbeam::utils::Backoff;

use super::common::*;
use super::syslog_sync_internal::SyncSyslogInternal;
use super::error::{SyRes/* , SyslogError*/};


/// A common instance which describes the syslog state
pub struct Syslog
{   
    /// A giant lock which used to access the [SyncSyslogInternal]
    /// assets.
    lock: AtomicBool,

    /// A syslog assets
    inner: SyncSyslogInternal,
}

unsafe impl Send for Syslog {}
unsafe impl Sync for Syslog {}

impl Drop for Syslog 
{
    fn drop(&mut self) 
    {
        self.inner.disconnectlog();
    }
}

/// A public implementations
impl Syslog
{
    /// As in a libc, this function initializes the syslog instance. The 
    /// main difference with realization in C is it returns the instance
    /// to program used this crate. This structure implements the [Send] 
    /// and [Sync] so it does not require any additional synchonization.
    ///
    /// # Arguments
    /// 
    /// * `ident` - a identification of the sender. If not set, the crate
    ///             will determine automatically!
    /// * `logstat` - sets up the syslog behaviour. Use [LogStat] 
    /// 
    /// * `facility` - a syslog facility. Use [LogFacility]
    /// 
    /// # Returns
    ///
    /// * A [SyRes] with instance or Err()
    ///
    /// # Example
    /// 
    /// ```
    ///  Syslog::openlog(
    ///        Some("test1"), 
    ///         LogStat::LOG_NDELAY | LogStat::LOG_PID, 
    ///         LogFacility::LOG_DAEMON);
    /// ```
    pub fn openlog(
        ident: Option<&str>, 
        logstat: LogStat, 
        facility: LogFacility) -> SyRes<Self>
    {
        let ret = 
            Self
            {
                lock: AtomicBool::new(false),
                inner: SyncSyslogInternal::new(ident, logstat, facility),
            };

        if logstat.contains(LogStat::LOG_NDELAY) == true
        {
            ret.inner.connectlog()?;
        }

        return Ok(ret);
    }

    /// Sets the logmask to filter out the syslog calls.
    /// 
    /// See macroses [LOG_MASK] and [LOG_UPTO] to generate mask
    ///
    /// # Example
    ///
    /// LOG_MASK!(Priority::LOG_EMERG) | LOG_MASK!(Priority::LOG_ERROR)
    ///
    /// or
    ///
    /// ~(LOG_MASK!(Priority::LOG_INFO))
    /// LOG_UPTO!(Priority::LOG_ERROR)
    pub fn setlogmask(&self, logmask: i32) -> i32
    {
        let backoff = Backoff::new();

        // try lock
        while self.lock.swap(true, Ordering::Acquire) == true
        {
            backoff.snooze();
        }

        // locked

        let pri = self.inner.set_logmask(logmask);

        // unlock 
        self.lock.store(false, Ordering::Release);

        return pri;
    }

    /// Similar to libc, closelog() will close the log
    pub fn closelog(&self)
    {
        let backoff = Backoff::new();

        // try lock
        while self.lock.swap(true, Ordering::Acquire) == true
        {
            backoff.snooze();
        }
        
        self.inner.disconnectlog();

        // unlock
        self.lock.store(false, Ordering::Release);
    }

    /// Similar to libc, syslog() sends data to syslog server.
    /// 
    /// # Arguments
    ///
    /// * `pri` - a priority [Priority]
    ///
    /// * `fmt` - a string message. In C exists a functions with
    ///     variable argumets amount. In Rust you should create your
    ///     own macros like format!() or use format!()
    pub fn syslog(&self, pri: Priority, fmt: String)
    {
        self.vsyslog(pri, fmt);
    }

    /// Similar to syslog() and created for the compatability.
    pub fn vsyslog<S: AsRef<str>>(&self, pri: Priority, fmt: S)
    {
        let backoff = Backoff::new();

        // try lock
        while self.lock.swap(true, Ordering::Acquire) == true
        {
            backoff.snooze();
        }

        self.inner.vsyslog1(pri.bits(), fmt);

        // unlock
        self.lock.store(false, Ordering::Release);
    }

    // --- NON STANDART API

    /// This function can be used to update the facility name, for example
    /// after fork().
    /// 
    /// # Arguments
    /// 
    /// * `ident` - a new identity (up to 48 UTF8 chars)
    pub fn change_identity<I: AsRef<str>>(&self, ident: I)
    {
        let backoff = Backoff::new();

        // try lock
        while self.lock.swap(true, Ordering::Acquire) == true
        {
            backoff.snooze();
        }

        self.inner.set_logtag(ident);

        // unlock
        self.lock.store(false, Ordering::Release);
    }
}

#[test]
fn test_single_message()
{
    /*use std::sync::Arc;
    use std::thread;
    use std::time::Duration;
    use super::{LOG_MASK};*/

    let log = 
            Syslog::openlog(
                Some("test1"), 
                LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
                LogFacility::LOG_DAEMON);

    assert_eq!(log.is_ok(), true, "{}", log.err().unwrap());

    let log = log.unwrap();

    log.syslog(Priority::LOG_DEBUG, format!("test_set_logmask() проверка BOM"));

    log.closelog();

    return;
}

#[test]
fn test_multithreading()
{
    use std::sync::Arc;
    use std::thread;
    use std::time::{Instant, Duration};

    let log = 
            Syslog::openlog(
                Some("test1"), 
                LogStat::LOG_CONS | LogStat::LOG_NDELAY | LogStat::LOG_PID, 
                LogFacility::LOG_DAEMON);

    assert_eq!(log.is_ok(), true, "{}", log.err().unwrap());

    let log = Arc::new(log.unwrap());
    let c1_log = log.clone();
    let c2_log = log.clone();

    thread::spawn(move|| {
        for i in 0..5
        {
            thread::sleep(Duration::from_nanos(200));
            let now = Instant::now();
            c1_log.syslog(Priority::LOG_DEBUG, format!("a message from thread 1 #{}[]", i));
            let elapsed = now.elapsed();
            println!("t1: {:?}", elapsed);
        }
    });

    thread::spawn(move|| {
        for i in 0..5
        {
            thread::sleep(Duration::from_nanos(201));
            let now = Instant::now();
            c2_log.syslog(Priority::LOG_DEBUG, format!("сообщение от треда 2 №{}ХЪ", i));
            let elapsed = now.elapsed();
            println!("t2: {:?}", elapsed);
        }
    });

    let now = Instant::now();
    log.syslog(Priority::LOG_DEBUG, format!("A message from main, сообщение от главнюка"));
    let elapsed = now.elapsed();
    println!("main: {:?}", elapsed);

    thread::sleep(Duration::from_secs(2));

    log.closelog();

    return;
}