cuprated/
logging.rs

1//! Logging
2//!
3//! `cuprated` log filtering settings and related functionality.
4use std::ops::BitAnd;
5use std::{
6    fmt::{Display, Formatter},
7    mem::forget,
8    sync::OnceLock,
9};
10use tracing::{
11    instrument::WithSubscriber, level_filters::LevelFilter, subscriber::Interest, Metadata,
12};
13use tracing_appender::{non_blocking::NonBlocking, rolling::Rotation};
14use tracing_subscriber::{
15    filter::Filtered,
16    fmt::{
17        self,
18        format::{DefaultFields, Format},
19        Layer as FmtLayer,
20    },
21    layer::{Context, Filter, Layered, SubscriberExt},
22    reload::{Handle, Layer as ReloadLayer},
23    util::SubscriberInitExt,
24    Layer, Registry,
25};
26
27use cuprate_helper::fs::logs_path;
28
29use crate::config::Config;
30
31/// A [`OnceLock`] which holds the [`Handle`] to update the file logging output.
32///
33/// Initialized in [`init_logging`].
34static FILE_WRITER_FILTER_HANDLE: OnceLock<Handle<CupratedTracingFilter, Registry>> =
35    OnceLock::new();
36
37/// A [`OnceLock`] which holds the [`Handle`] to update the stdout logging output.
38///
39/// Initialized in [`init_logging`].
40#[expect(clippy::type_complexity)] // factoring out isn't going to help readability.
41static STDOUT_FILTER_HANDLE: OnceLock<
42    Handle<
43        CupratedTracingFilter,
44        Layered<
45            Filtered<
46                FmtLayer<Registry, DefaultFields, Format, NonBlocking>,
47                ReloadLayer<CupratedTracingFilter, Registry>,
48                Registry,
49            >,
50            Registry,
51            Registry,
52        >,
53    >,
54> = OnceLock::new();
55
56/// The [`Filter`] used to alter cuprated's log output.
57#[derive(Debug)]
58pub struct CupratedTracingFilter {
59    pub level: LevelFilter,
60}
61
62// Custom display behavior for command output.
63impl Display for CupratedTracingFilter {
64    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
65        f.debug_struct("Filter")
66            .field("minimum_level", &self.level.to_string())
67            .finish()
68    }
69}
70
71impl<S> Filter<S> for CupratedTracingFilter {
72    fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool {
73        Filter::<S>::enabled(&self.level, meta, cx)
74    }
75
76    fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
77        Filter::<S>::callsite_enabled(&self.level, meta)
78    }
79
80    fn max_level_hint(&self) -> Option<LevelFilter> {
81        Some(self.level)
82    }
83}
84
85/// Initialize [`tracing`] for logging to stdout and to a file.
86pub fn init_logging(config: &Config) {
87    // initialize the stdout filter, set `STDOUT_FILTER_HANDLE` and create the layer.
88    let (stdout_filter, stdout_handle) = ReloadLayer::new(CupratedTracingFilter {
89        level: config.tracing.stdout.level,
90    });
91
92    STDOUT_FILTER_HANDLE.set(stdout_handle).unwrap();
93
94    let stdout_layer = FmtLayer::default()
95        .with_target(false)
96        .with_filter(stdout_filter);
97
98    // create the tracing appender.
99    let appender_config = &config.tracing.file;
100    let (appender, guard) = tracing_appender::non_blocking(
101        tracing_appender::rolling::Builder::new()
102            .rotation(Rotation::DAILY)
103            .max_log_files(appender_config.max_log_files)
104            .build(logs_path(&config.fs.data_directory, config.network()))
105            .unwrap(),
106    );
107
108    // TODO: drop this when we shutdown.
109    forget(guard);
110
111    // initialize the appender filter, set `FILE_WRITER_FILTER_HANDLE` and create the layer.
112    let (appender_filter, appender_handle) = ReloadLayer::new(CupratedTracingFilter {
113        level: appender_config.level,
114    });
115    FILE_WRITER_FILTER_HANDLE.set(appender_handle).unwrap();
116
117    let appender_layer = fmt::layer()
118        .with_target(false)
119        .with_ansi(false)
120        .with_writer(appender)
121        .with_filter(appender_filter);
122
123    // initialize tracing with the 2 layers.
124    tracing_subscriber::registry()
125        .with(appender_layer)
126        .with(stdout_layer)
127        .init();
128}
129
130/// Modify the stdout [`CupratedTracingFilter`].
131///
132/// Must only be called after [`init_logging`].
133pub fn modify_stdout_output(f: impl FnOnce(&mut CupratedTracingFilter)) {
134    STDOUT_FILTER_HANDLE.get().unwrap().modify(f).unwrap();
135}
136
137/// Modify the file appender [`CupratedTracingFilter`].
138///
139/// Must only be called after [`init_logging`].
140pub fn modify_file_output(f: impl FnOnce(&mut CupratedTracingFilter)) {
141    FILE_WRITER_FILTER_HANDLE.get().unwrap().modify(f).unwrap();
142}
143
144/// Prints some text using [`eprintln`], with [`nu_ansi_term::Color::Red`] applied.
145pub fn eprintln_red(s: &str) {
146    eprintln!("{}", nu_ansi_term::Color::Red.bold().paint(s));
147}