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