use slog::{Drain, Logger, o};
use slog_async::Async;
use slog_json::Json;
use slog_term::{CompactFormat, TermDecorator};
use std::io;
use std::time::{SystemTime, UNIX_EPOCH};
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct LoggerConfig {
pub format: LogFormat,
pub level: slog::Level,
pub include_location: bool,
pub include_thread_id: bool,
pub static_fields: std::collections::HashMap<String, String>,
}
impl Default for LoggerConfig {
fn default() -> Self {
Self {
format: LogFormat::Terminal,
level: slog::Level::Info,
include_location: true,
include_thread_id: true,
static_fields: std::collections::HashMap::new(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum LogFormat {
Terminal,
Json,
}
#[derive(Debug, Clone)]
pub struct RequestInfo {
pub trace_id: String,
pub method: String,
pub path: String,
pub remote_addr: String,
pub user_agent: String,
pub start_time_ms: u128,
}
impl RequestInfo {
pub fn elapsed_ms(&self) -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
.saturating_sub(self.start_time_ms)
}
}
pub fn generate_trace_id() -> String {
Uuid::new_v4().to_string()
}
pub fn init_global_logger(config: &LoggerConfig) -> LoggerGuard {
let drain = match config.format {
LogFormat::Terminal => {
let decorator = TermDecorator::new().build();
let drain = CompactFormat::new(decorator).build().fuse();
Async::new(drain).build().fuse()
}
LogFormat::Json => {
let drain = Json::new(io::stdout())
.set_pretty(false)
.set_newlines(true)
.add_key_value(o!("@timestamp" => slog::PushFnValue(|_record, ser| {
let time = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
ser.emit(time)
})))
.add_key_value(o!("message" => slog::PushFnValue(|record, ser| {
ser.emit(record.msg())
})))
.add_key_value(o!("level" => slog::PushFnValue(|record, ser| {
let level = record.level().as_str();
ser.emit(level)
})))
.build()
.fuse();
Async::new(drain).build().fuse()
}
};
let drain = drain.filter_level(config.level).fuse();
let mut logger = Logger::root(drain, o!());
for (key, value) in &config.static_fields {
let key_str: &'static str = Box::leak(key.clone().into_boxed_str());
logger = logger.new(o!(key_str => value.clone()));
}
let guard = slog_scope::set_global_logger(logger);
let log_level_filter = match config.level {
slog::Level::Trace => log::Level::Trace,
slog::Level::Debug => log::Level::Debug,
slog::Level::Info => log::Level::Info,
slog::Level::Warning => log::Level::Warn,
slog::Level::Error => log::Level::Error,
slog::Level::Critical => log::Level::Error,
};
let _ = slog_stdlog::init_with_level(log_level_filter);
LoggerGuard { _guard: guard }
}
pub struct LoggerGuard {
_guard: slog_scope::GlobalLoggerGuard,
}