shors 0.13.0

Transport layer for cartridge + tarantool-module projects.
Documentation
use crate::transport::Context;
use opentelemetry::trace::TraceContextExt;
use opentelemetry::Context as OTELContext;

pub extern crate log as rs_log;

pub trait RequestIdOwner {
    fn request_id(&self) -> String;
}

impl RequestIdOwner for OTELContext {
    fn request_id(&self) -> String {
        self.span().span_context().trace_id().to_string()
    }
}

impl RequestIdOwner for Context {
    fn request_id(&self) -> String {
        self.get("request_id")
            .map(|s| s.to_string())
            .unwrap_or_else(|| "unknown".to_string())
    }
}

#[macro_export]
macro_rules! shors_error {
        (ctx: $ctx:expr, target: $target:expr, $fmt:expr, $($arg:tt)*) => {
            $crate::log::rs_log::error!(target: $target, concat!("[{}]: ", $fmt), $crate::log::RequestIdOwner::request_id($ctx), $($arg)*)
        };
        (ctx: $ctx:expr, $fmt:expr, $($arg:tt)*) => {
            $crate::log::rs_log::error!(concat!("[{}]: ", $fmt), $crate::log::RequestIdOwner::request_id($ctx), $($arg)*)
        };
        (target: $target:expr, $($arg:tt)+) => {
            $crate::log::rs_log::error!(target: $target, $($arg)+)
        };
        ($($arg:tt)+) => {
            $crate::log::rs_log::error!($($arg)+)
        };
    }

#[macro_export]
macro_rules! shors_warn {
        (ctx: $ctx:expr, target: $target:expr, $fmt:expr, $($arg:tt)*) => {
            $crate::log::rs_log::warn!(target: $target, concat!("[{}]: ", $fmt), $crate::log::RequestIdOwner::request_id($ctx), $($arg)*)
        };
        (ctx: $ctx:expr, $fmt:expr, $($arg:tt)*) => {
            $crate::log::rs_log::warn!(concat!("[{}]: ", $fmt), $crate::log::RequestIdOwner::request_id($ctx), $($arg)*)
        };
        (target: $target:expr, $($arg:tt)+) => {
            $crate::log::rs_log::warn!(target: $target, $($arg)+)
        };
        ($($arg:tt)+) => {
            $crate::log::rs_log::warn!($($arg)+)
        };
    }

#[macro_export]
macro_rules! shors_info {
        (ctx: $ctx:expr, target: $target:expr, $fmt:expr, $($arg:tt)*) => {
            $crate::log::rs_log::info!(target: $target, concat!("[{}]: ", $fmt), $crate::log::RequestIdOwner::request_id($ctx), $($arg)*)
        };
        (ctx: $ctx:expr, $fmt:expr, $($arg:tt)*) => {
            $crate::log::rs_log::info!(concat!("[{}]: ", $fmt), $crate::log::RequestIdOwner::request_id($ctx), $($arg)*)
        };
        (target: $target:expr, $($arg:tt)+) => {
            $crate::log::rs_log::info!(target: $target, $($arg)+)
        };
        ($($arg:tt)+) => {
            $crate::log::rs_log::info!($($arg)+)
        };
    }

#[macro_export]
macro_rules! shors_debug {
        (ctx: $ctx:expr, target: $target:expr, $fmt:expr, $($arg:tt)*) => {
            $crate::log::rs_log::debug!(target: $target, concat!("[{}]: ", $fmt), $crate::log::RequestIdOwner::request_id($ctx), $($arg)*)
        };
        (ctx: $ctx:expr, $fmt:expr, $($arg:tt)*) => {
            $crate::log::rs_log::debug!(concat!("[{}]: ", $fmt), $crate::log::RequestIdOwner::request_id($ctx), $($arg)*)
        };
        (target: $target:expr, $($arg:tt)+) => {
            $crate::log::rs_log::debug!(target: $target, $($arg)+)
        };
        ($($arg:tt)+) => {
            $crate::log::rs_log::debug!($($arg)+)
        };
    }

#[cfg(test)]
mod test {
    use crate::transport::Context;
    use log::{Level, LevelFilter, Log, Metadata, Record};
    use once_cell::sync::Lazy;
    use std::sync::Mutex;

    struct BufferedLogger {
        buff: Mutex<Vec<(Level, String, String)>>,
    }

    impl Log for BufferedLogger {
        fn enabled(&self, _metadata: &Metadata) -> bool {
            true
        }

        fn log(&self, record: &Record) {
            self.buff.lock().unwrap().push((
                record.level(),
                record.target().to_string(),
                record.args().to_string(),
            ))
        }

        fn flush(&self) {
            self.buff.lock().unwrap().clear();
        }
    }

    static LOGGER: Lazy<BufferedLogger> = Lazy::new(|| BufferedLogger {
        buff: Mutex::default(),
    });

    #[test]
    fn test_logger() {
        log::set_logger(&*LOGGER)
            .map(|()| log::set_max_level(LevelFilter::Debug))
            .unwrap();

        test_logger_simple();
        test_logger_with_custom_target();
        test_logger_with_request_id();
        test_logger_with_custom_target_and_request_id();
    }

    fn test_logger_simple() {
        LOGGER.flush();

        shors_debug!("simple log record {}", 1);
        shors_info!("simple log record {}", 2);
        shors_warn!("simple log record {}", 3);
        shors_error!("simple log record {}", 4);

        assert_eq!(
            &[
                (
                    Level::Debug,
                    "shors::builtin::log::test".to_string(),
                    "simple log record 1".to_string()
                ),
                (
                    Level::Info,
                    "shors::builtin::log::test".to_string(),
                    "simple log record 2".to_string()
                ),
                (
                    Level::Warn,
                    "shors::builtin::log::test".to_string(),
                    "simple log record 3".to_string()
                ),
                (
                    Level::Error,
                    "shors::builtin::log::test".to_string(),
                    "simple log record 4".to_string()
                ),
            ],
            LOGGER.buff.lock().unwrap().as_slice()
        );
    }

    fn test_logger_with_custom_target() {
        LOGGER.flush();

        shors_debug!(target: "test", "log record");
        shors_info!(target: "test","log record");
        shors_warn!(target: "test","log record");
        shors_error!(target: "test", "log record");

        assert_eq!(
            &[
                (Level::Debug, "test".to_string(), "log record".to_string()),
                (Level::Info, "test".to_string(), "log record".to_string()),
                (Level::Warn, "test".to_string(), "log record".to_string()),
                (Level::Error, "test".to_string(), "log record".to_string()),
            ],
            LOGGER.buff.lock().unwrap().as_slice()
        );
    }

    fn test_logger_with_request_id() {
        LOGGER.flush();

        let mut ctx = Context::new();
        ctx.put("request_id", "12345");

        shors_debug!(ctx: &ctx, "{}", "log record");
        shors_info!(ctx: &ctx, "{}", "log record");
        shors_warn!(ctx: &ctx, "{}", "log record");
        shors_error!(ctx: &ctx, "{}", "log record");

        assert_eq!(
            &[
                (
                    Level::Debug,
                    "shors::builtin::log::test".to_string(),
                    "[12345]: log record".to_string()
                ),
                (
                    Level::Info,
                    "shors::builtin::log::test".to_string(),
                    "[12345]: log record".to_string()
                ),
                (
                    Level::Warn,
                    "shors::builtin::log::test".to_string(),
                    "[12345]: log record".to_string()
                ),
                (
                    Level::Error,
                    "shors::builtin::log::test".to_string(),
                    "[12345]: log record".to_string()
                ),
            ],
            LOGGER.buff.lock().unwrap().as_slice()
        );
    }

    fn test_logger_with_custom_target_and_request_id() {
        LOGGER.flush();

        let mut ctx = Context::new();
        ctx.put("request_id", "12345");

        shors_debug!(ctx: &ctx, target: "test", "{}", "log record");
        shors_info!(ctx: &ctx, target: "test", "{}", "log record");
        shors_warn!(ctx: &ctx, target: "test", "{}", "log record");
        shors_error!(ctx: &ctx, target: "test", "{}", "log record");

        assert_eq!(
            &[
                (
                    Level::Debug,
                    "test".to_string(),
                    "[12345]: log record".to_string()
                ),
                (
                    Level::Info,
                    "test".to_string(),
                    "[12345]: log record".to_string()
                ),
                (
                    Level::Warn,
                    "test".to_string(),
                    "[12345]: log record".to_string()
                ),
                (
                    Level::Error,
                    "test".to_string(),
                    "[12345]: log record".to_string()
                ),
            ],
            LOGGER.buff.lock().unwrap().as_slice()
        );
    }
}