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