gc_plugin_abi 0.5.0

Gridcore Plugin API
Documentation
//! A logging system to provide log information to the core gateway without dynamic allocation.
//! # Basic logging macros
//! - [info!] - Logs a message with the `INFO` level
//! - [error!] - Logs a message with the `ERROR` level
//! - [debug!] - Logs a message with the `DEBUG` level
//! - [warn!] - Logs a message with the `WARN` level
//! - [critical!] - Logs a message with the `CRITICAL` level
//! - [audit!] - Logs an audit message
//!
//! These macros work similar to the [log](https://docs.rs/log/latest/log/) crate, but they use the plugin interface to log the messages.
//! # Examples
//! ```rust
//! use gc_plugin_abi::{info, GCPluginInterface, debug};
//!
//! fn my_function(plugin_interface: &GCPluginInterface) {
//!    let var: u32 = 42;
//!    debug!(plugin_interface, "This is a debug message with a variable: {}", var);
//!    info!(plugin_interface, "This is an info message");
//! }
//! ```
//!
//! # Rate-limited logging macros
//! These macros log messages only if a specified interval has elapsed since the last log at the same call site.
//! - [error_limit!] - Rate-limited error logging
//! - [warn_limit!] - Rate-limited warning logging
//! - [info_limit!] - Rate-limited info logging
//! - [debug_limit!] - Rate-limited debug logging
//! - [critical_limit!] - Rate-limited critical logging
//! ## Examples
//! ```rust
//! use gc_plugin_abi::{error_limit, GCPluginInterface};
//!
//! // This function will only log the error once every 5 seconds
//! fn my_function(plugin_interface: &GCPluginInterface) {
//!     for _ in 0..3 {
//!         error_limit!(plugin_interface, 5, "This is a rate-limited error log");
//!     }
//! }
//! ```
//!
//! # Limitations
//! The log messages are limited to 1024 bytes, if a message exceeds this limit, the log message will be truncated.
use std::ffi::{CStr, FromBytesUntilNulError};
use std::io::Write;

/// An internal buffer used by the macros to do any formatting necessary.
/// This shouldn't be used directly. Instead use the provided macros.
pub struct LogBuffer {
    buffer: [u8; 1024],
    buffer_len: usize,
}
impl Default for LogBuffer {
    fn default() -> Self {
        Self::new()
    }
}

impl LogBuffer {
    pub fn new() -> Self {
        LogBuffer {
            buffer: [0; 1024],
            buffer_len: 0,
        }
    }

    /// Retrieves the log line
    pub fn get_log(&mut self) -> Result<&CStr, FromBytesUntilNulError> {
        self.buffer[self.buffer_len] = 0;
        std::ffi::CStr::from_bytes_until_nul(self.buffer.as_slice())
    }

    /// Clears the buffer
    pub fn clear(&mut self) {
        self.buffer_len = 0;
    }
}
impl Write for LogBuffer {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        if self.buffer_len + buf.len() > self.buffer.len() {
            return Err(std::io::Error::other("A single log message cannot exceed 1024 bytes"));
        }
        let mut i = 0;
        for &byte in buf {
            self.buffer[self.buffer_len + i] = byte;
            i += 1;
        }
        self.buffer_len += i;
        Ok(i)
    }
    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

/// Logs a message with a given level.
/// This macro should not be used directly, instead use the provided macros:
/// - `info!`
/// - `error!`
/// - `debug!`
/// - `critical!`
#[macro_export]
macro_rules! log {
    ($target:expr_2021, $lvl:expr_2021, $($arg:tt)+) => ({
        use std::io::Write;
        let interface = $target;
        let level = $lvl;
        if level <= interface.get_raw_log_level() {
            let mut buffer = $crate::LogBuffer::new();
            let result = write!(&mut buffer, $($arg)*).ok();
            let log_line = match result {
                Some(_) => buffer.get_log().expect("An empty log message was provided without a null terminating character"),
                None => {
                    buffer.clear();
                    write!(&mut buffer, "Failed to write log message, log messages with more then 1024 bytes are not supported").unwrap();
                    buffer.get_log().unwrap()
                }
            };
            interface.log(level, log_line);
        }
    });
}

/// Logs an audit message
#[macro_export]
macro_rules! audit {
    ($target:expr_2021, $($arg:tt)+) => ({
        use std::io::Write;
        let interface = $target;
        let mut buffer = $crate::LogBuffer::new();
        let result = write!(&mut buffer, $($arg)*).ok();
        let log_line = match result {
            Some(_) => buffer.get_log().expect("An empty log message was provided without a null terminating character"),
            None => {
                buffer.clear();
                write!(&mut buffer, "Failed to write log message, log messages with more then 1024 bytes are not supported").unwrap();
                buffer.get_log().unwrap()
            }
        };
        interface.log_audit(log_line);
    });
}

/// Logs a message with the `INFO` level
#[macro_export]
macro_rules! info {
    ($target:expr_2021, $($arg:tt)+) => ({
        $crate::log!($target, gc_plugin_abi::raw::eGCLogLevel_INFO, $($arg)+)
    });
}

/// Logs a message with the `WARN` level
#[macro_export]
macro_rules! warn {
    ($target:expr_2021, $($arg:tt)+) => ({
        $crate::log!($target, gc_plugin_abi::raw::eGCLogLevel_WARNING, $($arg)+)
    });
}

/// Logs a message with the `ERROR` level
#[macro_export]
macro_rules! error {
    ($target:expr_2021, $($arg:tt)+) => ({
        $crate::log!($target, gc_plugin_abi::raw::eGCLogLevel_ERROR, $($arg)+)
    });
}

/// Logs a message with the `DEBUG` level
#[macro_export]
macro_rules! debug {
    ($target:expr_2021, $($arg:tt)+) => ({
        $crate::log!($target, gc_plugin_abi::raw::eGCLogLevel_DEBUG, $($arg)+)
    });
}

/// Logs a message with the `CRITICAL` level
#[macro_export]
macro_rules! critical {
    ($target:expr_2021, $($arg:tt)+) => ({
        $crate::log!($target, gc_plugin_abi::raw::eGCLogLevel_CRITICAL, $($arg)+)
    });
}

/// Internal macro that implements rate-limited logging logic
///
/// This macro is used by all the public rate-limited logging macros.
#[doc(hidden)]
#[macro_export]
macro_rules! log_rate_limit_internal {
    ($target:expr_2021, $level:expr, $interval_secs:expr, $($arg:tt)*) => {{
        use std::io::Write;
        let level = $level;
        let interface = $target;
        if level <= interface.get_raw_log_level() {
            static LAST_LOG: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
            let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs();
            let last = LAST_LOG.load(std::sync::atomic::Ordering::Relaxed);

            #[allow(clippy::int_plus_one)]
            if now >= last + $interval_secs {
                LAST_LOG.store(now, std::sync::atomic::Ordering::Relaxed);

                let mut buffer = $crate::LogBuffer::new();
                let result = write!(&mut buffer, $($arg)*).ok();
                let log_line = match result {
                    Some(_) => buffer.get_log().expect("An empty log message was provided without a null terminating character"),
                    None => {
                        buffer.clear();
                        write!(&mut buffer, "Failed to write log message, log messages with more then 1024 bytes are not supported").unwrap();
                        buffer.get_log().unwrap()
                    }
                };
                interface.log(level, log_line);
            }
        }
    }};
}

/// Rate-limited error logging
#[macro_export]
macro_rules! error_limit {
    ($target:expr_2021, $interval_secs:expr, $($arg:tt)*) => {
        $crate::log_rate_limit_internal!($target, gc_plugin_abi::raw::eGCLogLevel_ERROR, $interval_secs, $($arg)*)
    };
}

/// Rate-limited warning logging
#[macro_export]
macro_rules! warn_limit {
    ($target:expr_2021, $interval_secs:expr, $($arg:tt)*) => {
        $crate::log_rate_limit_internal!($target, gc_plugin_abi::raw::eGCLogLevel_WARNING, $interval_secs, $($arg)*)
    };
}

/// Rate-limited info logging
#[macro_export]
macro_rules! info_limit {
    ($target:expr_2021, $interval_secs:expr, $($arg:tt)*) => {
        $crate::log_rate_limit_internal!($target, gc_plugin_abi::raw::eGCLogLevel_INFO, $interval_secs, $($arg)*)
    };
}

/// Rate-limited debug logging
#[macro_export]
macro_rules! debug_limit {
    ($target:expr_2021, $interval_secs:expr, $($arg:tt)*) => {
        $crate::log_rate_limit_internal!($target, gc_plugin_abi::raw::eGCLogLevel_DEBUG, $interval_secs, $($arg)*)
    };
}

/// Rate-limited critical logging
#[macro_export]
macro_rules! critical_limit {
    ($target:expr_2021, $interval_secs:expr, $($arg:tt)*) => {
        $crate::log_rate_limit_internal!($target, gc_plugin_abi::raw::eGCLogLevel_CRITICAL, $interval_secs, $($arg)*)
    };
}