mergegate/crates/mergegate-core/src/logger.rs
2026-04-10 14:50:57 +09:00

158 lines
4.2 KiB
Rust

//! Logging utilities for Miyabi
//!
//! Provides structured logging with multiple output formats
use tracing_subscriber::{fmt, layer::SubscriberExt, prelude::*, EnvFilter};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogLevel {
Trace,
Debug,
Info,
Warn,
Error,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogFormat {
/// Human-readable colored output
Pretty,
/// Compact format for CI/CD
Compact,
/// JSON format for structured parsing
Json,
}
/// Logger configuration
#[derive(Debug, Clone)]
pub struct LoggerConfig {
/// Log level
pub level: LogLevel,
/// Log format
pub format: LogFormat,
}
impl Default for LoggerConfig {
fn default() -> Self {
Self {
level: LogLevel::Info,
format: LogFormat::Pretty,
}
}
}
impl From<&str> for LogLevel {
fn from(s: &str) -> Self {
match s.to_lowercase().as_str() {
"trace" => LogLevel::Trace,
"debug" => LogLevel::Debug,
"info" => LogLevel::Info,
"warn" => LogLevel::Warn,
"error" => LogLevel::Error,
_ => LogLevel::Info,
}
}
}
impl From<LogLevel> for &'static str {
fn from(level: LogLevel) -> Self {
match level {
LogLevel::Trace => "trace",
LogLevel::Debug => "debug",
LogLevel::Info => "info",
LogLevel::Warn => "warn",
LogLevel::Error => "error",
}
}
}
impl From<&str> for LogFormat {
fn from(s: &str) -> Self {
match s.to_lowercase().as_str() {
"json" => LogFormat::Json,
"compact" => LogFormat::Compact,
_ => LogFormat::Pretty,
}
}
}
/// Initialize logging with default configuration
pub fn init_logger(level: LogLevel) {
let config = LoggerConfig {
level,
..Default::default()
};
init_logger_with_config(config);
}
/// Initialize logging with custom configuration
pub fn init_logger_with_config(config: LoggerConfig) {
let level_str: &'static str = config.level.into();
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(level_str));
let subscriber = tracing_subscriber::registry().with(filter);
match config.format {
LogFormat::Pretty => {
let fmt_layer = fmt::layer()
.pretty()
.with_target(true)
.with_thread_ids(false)
.with_line_number(true);
subscriber.with(fmt_layer).init();
}
LogFormat::Compact => {
let fmt_layer = fmt::layer()
.compact()
.with_target(false)
.with_thread_ids(false);
subscriber.with(fmt_layer).init();
}
LogFormat::Json => {
let fmt_layer = fmt::layer()
.json()
.with_current_span(true)
.with_span_list(true);
subscriber.with(fmt_layer).init();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_log_level_from_str() {
assert_eq!(LogLevel::from("trace"), LogLevel::Trace);
assert_eq!(LogLevel::from("debug"), LogLevel::Debug);
assert_eq!(LogLevel::from("info"), LogLevel::Info);
assert_eq!(LogLevel::from("warn"), LogLevel::Warn);
assert_eq!(LogLevel::from("error"), LogLevel::Error);
assert_eq!(LogLevel::from("invalid"), LogLevel::Info);
}
#[test]
fn test_log_level_to_str() {
let level_str: &'static str = LogLevel::Trace.into();
assert_eq!(level_str, "trace");
let level_str: &'static str = LogLevel::Info.into();
assert_eq!(level_str, "info");
}
#[test]
fn test_log_format_from_str() {
assert_eq!(LogFormat::from("json"), LogFormat::Json);
assert_eq!(LogFormat::from("compact"), LogFormat::Compact);
assert_eq!(LogFormat::from("pretty"), LogFormat::Pretty);
assert_eq!(LogFormat::from("invalid"), LogFormat::Pretty);
}
#[test]
fn test_logger_config_default() {
let config = LoggerConfig::default();
assert_eq!(config.level, LogLevel::Info);
assert_eq!(config.format, LogFormat::Pretty);
}
}