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