1use std::{
5 fmt::{Display, Formatter},
6 io::IsTerminal,
7 sync::OnceLock,
8};
9use tracing::{level_filters::LevelFilter, subscriber::Interest, Metadata};
10use tracing_appender::{
11 non_blocking::{NonBlocking, WorkerGuard},
12 rolling::Rotation,
13};
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
31static FILE_WRITER_FILTER_HANDLE: OnceLock<Handle<CupratedTracingFilter, Registry>> =
35 OnceLock::new();
36
37#[expect(clippy::type_complexity)] static 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#[derive(Debug)]
58pub struct CupratedTracingFilter {
59 pub level: LevelFilter,
60}
61
62impl 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
85pub fn init_logging(config: &Config) -> WorkerGuard {
87 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_ansi(ansi_enabled(&std::io::stdout()))
97 .with_filter(stdout_filter);
98
99 let appender_config = &config.tracing.file;
101 let (appender, guard) = tracing_appender::non_blocking(
102 tracing_appender::rolling::Builder::new()
103 .rotation(Rotation::DAILY)
104 .max_log_files(appender_config.max_log_files)
105 .build(logs_path(&config.fs.fast_data_directory, config.network()))
106 .unwrap(),
107 );
108
109 let (appender_filter, appender_handle) = ReloadLayer::new(CupratedTracingFilter {
111 level: appender_config.level,
112 });
113 FILE_WRITER_FILTER_HANDLE.set(appender_handle).unwrap();
114
115 let appender_layer = fmt::layer()
116 .with_target(false)
117 .with_ansi(false)
118 .with_writer(appender)
119 .with_filter(appender_filter);
120
121 tracing_subscriber::registry()
123 .with(appender_layer)
124 .with(stdout_layer)
125 .init();
126
127 guard
128}
129
130pub fn modify_stdout_output(f: impl FnOnce(&mut CupratedTracingFilter)) {
134 STDOUT_FILTER_HANDLE.get().unwrap().modify(f).unwrap();
135}
136
137pub fn modify_file_output(f: impl FnOnce(&mut CupratedTracingFilter)) {
141 FILE_WRITER_FILTER_HANDLE.get().unwrap().modify(f).unwrap();
142}
143
144pub fn eprintln_red(s: &str) {
146 if ansi_enabled(&std::io::stderr()) {
147 eprintln!("{}", nu_ansi_term::Color::Red.bold().paint(s));
148 } else {
149 eprintln!("{s}");
150 }
151}
152
153fn ansi_enabled(stream: &impl IsTerminal) -> bool {
155 if std::env::var_os("NO_COLOR").is_some_and(|v| !v.is_empty()) {
156 return false;
157 }
158 stream.is_terminal()
159}