foxy/logging/
structured.rs1use slog::{Drain, Logger, o};
8use slog_async::Async;
9use slog_json::Json;
10use slog_term::{CompactFormat, TermDecorator};
11use std::io;
12use std::time::{SystemTime, UNIX_EPOCH};
13use uuid::Uuid;
14
15#[derive(Debug, Clone)]
17pub struct LoggerConfig {
18 pub format: LogFormat,
20 pub level: slog::Level,
22 pub include_location: bool,
24 pub include_thread_id: bool,
26 pub static_fields: std::collections::HashMap<String, String>,
28}
29
30impl Default for LoggerConfig {
31 fn default() -> Self {
32 Self {
33 format: LogFormat::Terminal,
34 level: slog::Level::Info,
35 include_location: true,
36 include_thread_id: true,
37 static_fields: std::collections::HashMap::new(),
38 }
39 }
40}
41
42#[derive(Debug, Clone, PartialEq)]
44pub enum LogFormat {
45 Terminal,
47 Json,
49}
50
51#[derive(Debug, Clone)]
53pub struct RequestInfo {
54 pub trace_id: String,
56 pub method: String,
58 pub path: String,
60 pub remote_addr: String,
62 pub user_agent: String,
64 pub start_time_ms: u128,
66}
67
68impl RequestInfo {
69 pub fn elapsed_ms(&self) -> u128 {
71 SystemTime::now()
72 .duration_since(UNIX_EPOCH)
73 .unwrap_or_default()
74 .as_millis()
75 .saturating_sub(self.start_time_ms)
76 }
77}
78
79pub fn generate_trace_id() -> String {
81 Uuid::new_v4().to_string()
82}
83
84pub fn init_global_logger(config: &LoggerConfig) -> LoggerGuard {
86 let drain = match config.format {
87 LogFormat::Terminal => {
88 let decorator = TermDecorator::new().build();
89 let drain = CompactFormat::new(decorator).build().fuse();
90 Async::new(drain).build().fuse()
91 }
92 LogFormat::Json => {
93 let drain = Json::new(io::stdout())
95 .set_pretty(false)
96 .set_newlines(true)
97 .add_key_value(o!("@timestamp" => slog::PushFnValue(|_record, ser| {
99 let time = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
100 ser.emit(time)
101 })))
102 .add_key_value(o!("message" => slog::PushFnValue(|record, ser| {
104 ser.emit(record.msg())
105 })))
106 .add_key_value(o!("level" => slog::PushFnValue(|record, ser| {
108 let level = record.level().as_str();
109 ser.emit(level)
110 })))
111 .build()
112 .fuse();
113 Async::new(drain).build().fuse()
114 }
115 };
116
117 let drain = drain.filter_level(config.level).fuse();
118
119 let mut logger = Logger::root(drain, o!());
121 for (key, value) in &config.static_fields {
122 let key_str: &'static str = Box::leak(key.clone().into_boxed_str());
123 logger = logger.new(o!(key_str => value.clone()));
124 }
125
126 let guard = slog_scope::set_global_logger(logger);
128
129 let log_level_filter = match config.level {
130 slog::Level::Trace => log::Level::Trace,
131 slog::Level::Debug => log::Level::Debug,
132 slog::Level::Info => log::Level::Info,
133 slog::Level::Warning => log::Level::Warn,
134 slog::Level::Error => log::Level::Error,
135 slog::Level::Critical => log::Level::Error,
136 };
137
138 let _ = slog_stdlog::init_with_level(log_level_filter);
139
140 LoggerGuard { _guard: guard }
141}
142
143pub struct LoggerGuard {
145 _guard: slog_scope::GlobalLoggerGuard,
146}