1use std::collections::{BTreeMap, HashMap};
40use std::sync::Once;
41
42use figment::{
43 providers::{Format, Serialized, Toml},
44 Figment,
45};
46use serde::{Deserialize, Serialize};
47use tracing::{Event, Subscriber};
48use tracing_subscriber::fmt::{format::Writer, FormattedFields};
49use tracing_subscriber::fmt::{FmtContext, FormatFields};
50use tracing_subscriber::prelude::*;
51use tracing_subscriber::registry::LookupSpan;
52use tracing_subscriber::EnvFilter;
53use tracing_subscriber::{filter::Directive, fmt};
54
55const FILTER_ENV: &str = "DYN_LOG";
57
58const DEFAULT_FILTER_LEVEL: &str = "info";
60
61const CONFIG_PATH_ENV: &str = "DYN_LOGGING_CONFIG_PATH";
63
64static INIT: Once = Once::new();
66
67#[derive(Serialize, Deserialize, Debug)]
68struct LoggingConfig {
69 log_level: String,
70 log_filters: HashMap<String, String>,
71}
72impl Default for LoggingConfig {
73 fn default() -> Self {
74 LoggingConfig {
75 log_level: DEFAULT_FILTER_LEVEL.to_string(),
76 log_filters: HashMap::from([
77 ("h2".to_string(), "error".to_string()),
78 ("tower".to_string(), "error".to_string()),
79 ("hyper_util".to_string(), "error".to_string()),
80 ("neli".to_string(), "error".to_string()),
81 ("async_nats".to_string(), "error".to_string()),
82 ]),
83 }
84 }
85}
86
87pub fn init() {
89 INIT.call_once(|| {
90 let config = load_config();
91
92 let mut filter_layer = EnvFilter::builder()
96 .with_default_directive(config.log_level.parse().unwrap())
97 .with_env_var(FILTER_ENV)
98 .from_env_lossy();
99
100 for (module, level) in config.log_filters {
102 match format!("{module}={level}").parse::<Directive>() {
103 Ok(d) => {
104 filter_layer = filter_layer.add_directive(d);
105 }
106 Err(e) => {
107 eprintln!("Failed parsing filter '{level}' for module '{module}': {e}");
108 }
109 }
110 }
111
112 if crate::config::jsonl_logging_enabled() {
113 let l = fmt::layer()
114 .with_ansi(false) .event_format(CustomJsonFormatter)
116 .with_writer(std::io::stderr)
117 .with_filter(filter_layer);
118 tracing_subscriber::registry().with(l).init();
120 } else {
121 let l = fmt::layer()
122 .with_ansi(!crate::config::disable_ansi_logging())
123 .event_format(fmt::format().compact())
124 .with_writer(std::io::stderr)
125 .with_filter(filter_layer);
126 tracing_subscriber::registry().with(l).init();
127 };
128 });
129}
130
131pub fn log_message(level: &str, message: &str, module: &str, file: &str, line: u32) {
134 let level = match level {
135 "debug" => log::Level::Debug,
136 "info" => log::Level::Info,
137 "warn" => log::Level::Warn,
138 "error" => log::Level::Error,
139 "warning" => log::Level::Warn,
140 _ => log::Level::Info,
141 };
142 log::logger().log(
143 &log::Record::builder()
144 .args(format_args!("{}", message))
145 .level(level)
146 .target(module)
147 .file(Some(file))
148 .line(Some(line))
149 .build(),
150 );
151}
152
153fn load_config() -> LoggingConfig {
155 let config_path = std::env::var(CONFIG_PATH_ENV).unwrap_or_else(|_| "".to_string());
156 let figment = Figment::new()
157 .merge(Serialized::defaults(LoggingConfig::default()))
158 .merge(Toml::file("/opt/dynamo/etc/logging.toml"))
159 .merge(Toml::file(config_path));
160
161 figment.extract().unwrap()
162}
163
164#[derive(Serialize)]
165struct JsonLog<'a> {
166 time: String,
167 level: String,
168 #[serde(skip_serializing_if = "Option::is_none")]
169 file_path: Option<&'a str>,
170 #[serde(skip_serializing_if = "Option::is_none")]
171 line_number: Option<u32>,
172 message: serde_json::Value,
173 #[serde(flatten)]
174 fields: BTreeMap<String, serde_json::Value>,
175}
176
177struct CustomJsonFormatter;
179
180impl<S, N> tracing_subscriber::fmt::FormatEvent<S, N> for CustomJsonFormatter
181where
182 S: Subscriber + for<'a> LookupSpan<'a>,
183 N: for<'a> FormatFields<'a> + 'static,
184{
185 fn format_event(
186 &self,
187 ctx: &FmtContext<'_, S, N>,
188 mut writer: Writer<'_>,
189 event: &Event<'_>,
190 ) -> std::fmt::Result {
191 let mut visitor = JsonVisitor::default();
192 event.record(&mut visitor);
193 let message = visitor
194 .fields
195 .remove("message")
196 .unwrap_or(serde_json::Value::String("".to_string()));
197
198 let current_span = event
199 .parent()
200 .and_then(|id| ctx.span(id))
201 .or_else(|| ctx.lookup_current());
202 if let Some(span) = current_span {
203 let ext = span.extensions();
204 let data = ext.get::<FormattedFields<N>>().unwrap();
209 let span_fields: Vec<(&str, &str)> = data
210 .fields
211 .split(' ')
212 .filter_map(|entry| entry.split_once('='))
213 .collect();
214 for (name, value) in span_fields {
215 visitor.fields.insert(
216 name.to_string(),
217 serde_json::Value::String(value.trim_matches('"').to_string()),
218 );
219 }
220 visitor.fields.insert(
221 "span_name".to_string(),
222 serde_json::Value::String(span.name().to_string()),
223 );
224 }
225
226 let metadata = event.metadata();
227 let log = JsonLog {
228 level: metadata.level().to_string(),
229 time: format!("{}", chrono::Local::now().format("%m-%d %H:%M:%S.%3f")),
230 file_path: if cfg!(debug_assertions) {
231 metadata.file()
232 } else {
233 None
234 },
235 line_number: if cfg!(debug_assertions) {
236 metadata.line()
237 } else {
238 None
239 },
240 message,
241 fields: visitor.fields,
242 };
243 let json = serde_json::to_string(&log).unwrap();
244 writeln!(writer, "{json}")
245 }
246}
247
248#[derive(Default)]
250struct JsonVisitor {
251 fields: BTreeMap<String, serde_json::Value>,
253}
254
255impl tracing::field::Visit for JsonVisitor {
256 fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
257 self.fields.insert(
258 field.name().to_string(),
259 serde_json::Value::String(format!("{value:?}")),
260 );
261 }
262
263 fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
264 self.fields.insert(
265 field.name().to_string(),
266 serde_json::Value::String(value.to_string()),
267 );
268 }
269
270 fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
271 self.fields
272 .insert(field.name().to_string(), serde_json::Value::Bool(value));
273 }
274
275 fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
276 self.fields.insert(
277 field.name().to_string(),
278 serde_json::Value::Number(value.into()),
279 );
280 }
281
282 fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
283 self.fields.insert(
284 field.name().to_string(),
285 serde_json::Value::Number(value.into()),
286 );
287 }
288
289 fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {
290 use serde_json::value::Number;
291 self.fields.insert(
292 field.name().to_string(),
293 serde_json::Value::Number(Number::from_f64(value).unwrap_or(0.into())),
296 );
297 }
298}