kumo_server_common/
diagnostic_logging.rs1use anyhow::Context;
2use clap::ValueEnum;
3use std::path::PathBuf;
4use std::sync::OnceLock;
5use tracing_subscriber::fmt::writer::BoxMakeWriter;
6use tracing_subscriber::prelude::*;
7use tracing_subscriber::{fmt, EnvFilter, Layer};
8
9static TRACING_FILTER_RELOAD_HANDLE: OnceLock<
25 Box<dyn Fn(&str) -> anyhow::Result<()> + Send + Sync>,
26> = OnceLock::new();
27
28pub fn set_diagnostic_log_filter(new_filter: &str) -> anyhow::Result<()> {
29 let func = TRACING_FILTER_RELOAD_HANDLE
30 .get()
31 .ok_or_else(|| anyhow::anyhow!("unable to retrieve filter reload handle"))?;
32 (func)(new_filter)
33}
34
35#[derive(Debug, Clone, Copy, ValueEnum)]
36#[clap(rename_all = "kebab_case")]
37pub enum DiagnosticFormat {
38 Pretty,
39 Full,
40 Compact,
41 Json,
42}
43
44pub struct LoggingConfig<'a> {
45 pub log_dir: Option<PathBuf>,
46 pub filter_env_var: &'a str,
47 pub default_filter: &'a str,
48 pub diag_format: DiagnosticFormat,
49}
50
51impl LoggingConfig<'_> {
52 pub fn init(&self) -> anyhow::Result<()> {
53 let (non_blocking, _non_blocking_flusher);
54 let log_writer = if let Some(log_dir) = &self.log_dir {
55 let file_appender = tracing_appender::rolling::hourly(log_dir, "log");
56 (non_blocking, _non_blocking_flusher) = tracing_appender::non_blocking(file_appender);
57 BoxMakeWriter::new(non_blocking)
58 } else {
59 BoxMakeWriter::new(std::io::stderr)
60 };
61
62 let layer = fmt::layer().with_thread_names(true).with_writer(log_writer);
63 let layer = match self.diag_format {
64 DiagnosticFormat::Pretty => layer.pretty().boxed(),
65 DiagnosticFormat::Full => layer.boxed(),
66 DiagnosticFormat::Compact => layer.compact().boxed(),
67 DiagnosticFormat::Json => layer.json().boxed(),
68 };
69
70 let env_filter = EnvFilter::try_new(
71 std::env::var(self.filter_env_var)
72 .as_deref()
73 .unwrap_or(self.default_filter),
74 )?;
75 let (env_filter, reload_handle) = tracing_subscriber::reload::Layer::new(env_filter);
76 tracing_subscriber::registry()
77 .with(layer.with_filter(env_filter))
78 .init();
79
80 TRACING_FILTER_RELOAD_HANDLE
81 .set(Box::new(move |new_filter: &str| {
82 let f = EnvFilter::try_new(new_filter)
83 .with_context(|| format!("parsing log filter '{new_filter}'"))?;
84 reload_handle.reload(f).context("applying new log filter")
85 }))
86 .map_err(|_| anyhow::anyhow!("failed to assign reloadable logging filter"))?;
87
88 metrics::set_global_recorder(metrics_prometheus::Recorder::builder().build())?;
89 Ok(())
90 }
91}