cat_gateway/
logger.rs

1//! Setup for logging for the service.
2
3use std::sync::OnceLock;
4
5use clap::ValueEnum;
6use tracing::{Subscriber, level_filters::LevelFilter, log::error};
7use tracing_subscriber::{
8    Layer, Registry,
9    fmt::{self, format::FmtSpan, time},
10    prelude::*,
11    registry::LookupSpan,
12    reload::{self, Handle},
13};
14
15use crate::settings::Settings;
16/// Default log level
17pub(crate) const LOG_LEVEL_DEFAULT: &str = "info";
18
19/// All valid logging levels
20#[derive(ValueEnum, Clone, Copy, Debug)]
21pub(crate) enum LogLevel {
22    /// Debug messages
23    Debug,
24    /// Informational Messages
25    Info,
26    /// Warnings
27    Warn,
28    /// Errors
29    Error,
30}
31
32impl From<LogLevel> for tracing::Level {
33    fn from(val: LogLevel) -> Self {
34        match val {
35            LogLevel::Debug => Self::DEBUG,
36            LogLevel::Info => Self::INFO,
37            LogLevel::Warn => Self::WARN,
38            LogLevel::Error => Self::ERROR,
39        }
40    }
41}
42
43impl From<LogLevel> for tracing::log::LevelFilter {
44    fn from(val: LogLevel) -> Self {
45        match val {
46            LogLevel::Debug => Self::Debug,
47            LogLevel::Info => Self::Info,
48            LogLevel::Warn => Self::Warn,
49            LogLevel::Error => Self::Error,
50        }
51    }
52}
53
54/// Logger Handle for the Service.
55static LOGGER_HANDLE: OnceLock<LoggerHandle> = OnceLock::new();
56
57/// Default Span Guard for the Service.
58static GLOBAL_SPAN: OnceLock<tracing::span::Span> = OnceLock::new();
59
60/// Default Span Guard for the Service.
61static SPAN_GUARD: OnceLock<tracing::span::Entered> = OnceLock::new();
62
63/// Handle to our Logger
64pub(crate) type LoggerHandle = Handle<LevelFilter, Registry>;
65
66/// Set the default fields in a log, using a global span.
67fn set_default_span() {
68    let server_id = Settings::service_id();
69    // This is a hacky way to add fields to every log line.
70    // Add Fields here, as required.
71    let global_span = tracing::info_span!("Global", ServerID = server_id);
72    if GLOBAL_SPAN.set(global_span).is_err() {
73        error!("Failed to set default span.  Is it already set?");
74    }
75
76    // It MUST be Some because of the above.
77    if let Some(global_span) = GLOBAL_SPAN.get() {
78        let span_guard = global_span.enter();
79        if SPAN_GUARD.set(span_guard).is_err() {
80            error!("Failed to set default span.  Is it already set?");
81        }
82    }
83}
84
85/// Initialize the tracing subscriber
86pub(crate) fn build_fmt_layer<S>() -> Box<dyn Layer<S> + Send + Sync + 'static>
87where
88    S: Subscriber,
89    for<'a> S: LookupSpan<'a>,
90{
91    // Create the formatting layer
92    fmt::layer()
93        .json()
94        .with_timer(time::UtcTime::rfc_3339())
95        .with_span_events(FmtSpan::CLOSE)
96        .with_current_span(true)
97        .with_span_list(true)
98        .with_target(true)
99        .with_file(true)
100        .with_line_number(true)
101        .with_level(true)
102        .with_thread_names(true)
103        .with_thread_ids(true)
104        .flatten_event(true)
105        .boxed()
106}
107
108/// Create a reloadable layer with the specified `log_level`.
109///
110/// Initializes the `LOGGER_HANDLE` static variable, which can be used to modify the log
111/// level with the `modify_logger_level` function.
112pub(crate) fn build_reloadable_filter(log_level: LogLevel) -> reload::Layer<LevelFilter, Registry> {
113    let filter = LevelFilter::from_level(log_level.into());
114    let (filter, logger_handle) = reload::Layer::new(filter);
115
116    if LOGGER_HANDLE.set(logger_handle).is_err() {
117        error!("Failed to initialize logger handle. Called multiple times?");
118    }
119    filter
120}
121
122/// Initialize the tracing subscriber
123pub(crate) fn init(log_level: LogLevel) {
124    // Create the formatting layer
125    let layer = build_fmt_layer();
126
127    // Create a reloadable layer with the specified log_level
128    let filter = build_reloadable_filter(log_level);
129
130    tracing_subscriber::registry()
131        .with(filter)
132        .with(layer)
133        .with(
134            tracing_subscriber::EnvFilter::builder()
135                .with_default_directive(LevelFilter::INFO.into())
136                .from_env_lossy(),
137        )
138        .init();
139
140    post_init(log_level);
141}
142
143/// Initialize the tracing subscriber
144pub(crate) fn post_init(log_level: LogLevel) {
145    // Logging is globally disabled by default, so globally enable it to the required level.
146    tracing::log::set_max_level(log_level.into());
147    set_default_span();
148}
149
150/// Modify the logger level setting.
151/// This will reload the logger.
152pub(crate) fn modify_logger_level(level: LogLevel) {
153    if let Some(logger_handle) = LOGGER_HANDLE.get() {
154        if let Err(error) = logger_handle.modify(|f| *f = LevelFilter::from_level(level.into())) {
155            error!("Failed to modify log level to {level:?} : {error}");
156        }
157    } else {
158        // This should never happen.
159        error!("Failed to modify log level to {level:?} : Logger handle not available.",);
160    }
161}