cuprated/
logging.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Logging
//!
//! `cuprated` log filtering settings and related functionality.
use std::ops::BitAnd;
use std::{
    fmt::{Display, Formatter},
    mem::forget,
    sync::OnceLock,
};
use tracing::{
    instrument::WithSubscriber, level_filters::LevelFilter, subscriber::Interest, Metadata,
};
use tracing_appender::{non_blocking::NonBlocking, rolling::Rotation};
use tracing_subscriber::{
    filter::Filtered,
    fmt::{
        self,
        format::{DefaultFields, Format},
        Layer as FmtLayer,
    },
    layer::{Context, Filter, Layered, SubscriberExt},
    reload::{Handle, Layer as ReloadLayer},
    util::SubscriberInitExt,
    Layer, Registry,
};

use cuprate_helper::fs::logs_path;

use crate::config::Config;

/// A [`OnceLock`] which holds the [`Handle`] to update the file logging output.
///
/// Initialized in [`init_logging`].
static FILE_WRITER_FILTER_HANDLE: OnceLock<Handle<CupratedTracingFilter, Registry>> =
    OnceLock::new();

/// A [`OnceLock`] which holds the [`Handle`] to update the stdout logging output.
///
/// Initialized in [`init_logging`].
#[expect(clippy::type_complexity)] // factoring out isn't going to help readability.
static STDOUT_FILTER_HANDLE: OnceLock<
    Handle<
        CupratedTracingFilter,
        Layered<
            Filtered<
                FmtLayer<Registry, DefaultFields, Format, NonBlocking>,
                ReloadLayer<CupratedTracingFilter, Registry>,
                Registry,
            >,
            Registry,
            Registry,
        >,
    >,
> = OnceLock::new();

/// The [`Filter`] used to alter cuprated's log output.
#[derive(Debug)]
pub struct CupratedTracingFilter {
    pub level: LevelFilter,
}

// Custom display behavior for command output.
impl Display for CupratedTracingFilter {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Filter")
            .field("minimum_level", &self.level.to_string())
            .finish()
    }
}

impl<S> Filter<S> for CupratedTracingFilter {
    fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool {
        Filter::<S>::enabled(&self.level, meta, cx)
    }

    fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
        Filter::<S>::callsite_enabled(&self.level, meta)
    }

    fn max_level_hint(&self) -> Option<LevelFilter> {
        Some(self.level)
    }
}

/// Initialize [`tracing`] for logging to stdout and to a file.
pub fn init_logging(config: &Config) {
    // initialize the stdout filter, set `STDOUT_FILTER_HANDLE` and create the layer.
    let (stdout_filter, stdout_handle) = ReloadLayer::new(CupratedTracingFilter {
        level: config.tracing.stdout.level,
    });

    STDOUT_FILTER_HANDLE.set(stdout_handle).unwrap();

    let stdout_layer = FmtLayer::default()
        .with_target(false)
        .with_filter(stdout_filter);

    // create the tracing appender.
    let appender_config = &config.tracing.file;
    let (appender, guard) = tracing_appender::non_blocking(
        tracing_appender::rolling::Builder::new()
            .rotation(Rotation::DAILY)
            .max_log_files(appender_config.max_log_files)
            .build(logs_path(&config.fs.data_directory, config.network()))
            .unwrap(),
    );

    // TODO: drop this when we shutdown.
    forget(guard);

    // initialize the appender filter, set `FILE_WRITER_FILTER_HANDLE` and create the layer.
    let (appender_filter, appender_handle) = ReloadLayer::new(CupratedTracingFilter {
        level: appender_config.level,
    });
    FILE_WRITER_FILTER_HANDLE.set(appender_handle).unwrap();

    let appender_layer = fmt::layer()
        .with_target(false)
        .with_ansi(false)
        .with_writer(appender)
        .with_filter(appender_filter);

    // initialize tracing with the 2 layers.
    tracing_subscriber::registry()
        .with(appender_layer)
        .with(stdout_layer)
        .init();
}

/// Modify the stdout [`CupratedTracingFilter`].
///
/// Must only be called after [`init_logging`].
pub fn modify_stdout_output(f: impl FnOnce(&mut CupratedTracingFilter)) {
    STDOUT_FILTER_HANDLE.get().unwrap().modify(f).unwrap();
}

/// Modify the file appender [`CupratedTracingFilter`].
///
/// Must only be called after [`init_logging`].
pub fn modify_file_output(f: impl FnOnce(&mut CupratedTracingFilter)) {
    FILE_WRITER_FILTER_HANDLE.get().unwrap().modify(f).unwrap();
}