1use std::ops::BitAnd;
5use std::{
6 fmt::{Display, Formatter},
7 io::IsTerminal,
8 mem::forget,
9 sync::OnceLock,
10};
11use tracing::{
12 instrument::WithSubscriber, level_filters::LevelFilter, subscriber::Interest, Metadata,
13};
14use tracing_appender::{non_blocking::NonBlocking, rolling::Rotation};
15use tracing_subscriber::{
16 filter::Filtered,
17 fmt::{
18 self,
19 format::{DefaultFields, Format},
20 Layer as FmtLayer,
21 },
22 layer::{Context, Filter, Layered, SubscriberExt},
23 reload::{Handle, Layer as ReloadLayer},
24 util::SubscriberInitExt,
25 Layer, Registry,
26};
27
28use cuprate_helper::fs::logs_path;
29
30use crate::config::Config;
31
32static FILE_WRITER_FILTER_HANDLE: OnceLock<Handle<CupratedTracingFilter, Registry>> =
36 OnceLock::new();
37
38#[expect(clippy::type_complexity)] static STDOUT_FILTER_HANDLE: OnceLock<
43 Handle<
44 CupratedTracingFilter,
45 Layered<
46 Filtered<
47 FmtLayer<Registry, DefaultFields, Format, NonBlocking>,
48 ReloadLayer<CupratedTracingFilter, Registry>,
49 Registry,
50 >,
51 Registry,
52 Registry,
53 >,
54 >,
55> = OnceLock::new();
56
57#[derive(Debug)]
59pub struct CupratedTracingFilter {
60 pub level: LevelFilter,
61}
62
63impl Display for CupratedTracingFilter {
65 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
66 f.debug_struct("Filter")
67 .field("minimum_level", &self.level.to_string())
68 .finish()
69 }
70}
71
72impl<S> Filter<S> for CupratedTracingFilter {
73 fn enabled(&self, meta: &Metadata<'_>, cx: &Context<'_, S>) -> bool {
74 Filter::<S>::enabled(&self.level, meta, cx)
75 }
76
77 fn callsite_enabled(&self, meta: &'static Metadata<'static>) -> Interest {
78 Filter::<S>::callsite_enabled(&self.level, meta)
79 }
80
81 fn max_level_hint(&self) -> Option<LevelFilter> {
82 Some(self.level)
83 }
84}
85
86pub fn init_logging(config: &Config) {
88 let (stdout_filter, stdout_handle) = ReloadLayer::new(CupratedTracingFilter {
90 level: config.tracing.stdout.level,
91 });
92
93 STDOUT_FILTER_HANDLE.set(stdout_handle).unwrap();
94
95 let stdout_layer = FmtLayer::default()
96 .with_target(false)
97 .with_ansi(ansi_enabled(&std::io::stdout()))
98 .with_filter(stdout_filter);
99
100 let appender_config = &config.tracing.file;
102 let (appender, guard) = tracing_appender::non_blocking(
103 tracing_appender::rolling::Builder::new()
104 .rotation(Rotation::DAILY)
105 .max_log_files(appender_config.max_log_files)
106 .build(logs_path(&config.fs.fast_data_directory, config.network()))
107 .unwrap(),
108 );
109
110 forget(guard);
112
113 let (appender_filter, appender_handle) = ReloadLayer::new(CupratedTracingFilter {
115 level: appender_config.level,
116 });
117 FILE_WRITER_FILTER_HANDLE.set(appender_handle).unwrap();
118
119 let appender_layer = fmt::layer()
120 .with_target(false)
121 .with_ansi(false)
122 .with_writer(appender)
123 .with_filter(appender_filter);
124
125 tracing_subscriber::registry()
127 .with(appender_layer)
128 .with(stdout_layer)
129 .init();
130}
131
132pub fn modify_stdout_output(f: impl FnOnce(&mut CupratedTracingFilter)) {
136 STDOUT_FILTER_HANDLE.get().unwrap().modify(f).unwrap();
137}
138
139pub fn modify_file_output(f: impl FnOnce(&mut CupratedTracingFilter)) {
143 FILE_WRITER_FILTER_HANDLE.get().unwrap().modify(f).unwrap();
144}
145
146pub fn eprintln_red(s: &str) {
148 if ansi_enabled(&std::io::stderr()) {
149 eprintln!("{}", nu_ansi_term::Color::Red.bold().paint(s));
150 } else {
151 eprintln!("{s}");
152 }
153}
154
155fn ansi_enabled(stream: &impl IsTerminal) -> bool {
157 if std::env::var_os("NO_COLOR").is_some_and(|v| !v.is_empty()) {
158 return false;
159 }
160 stream.is_terminal()
161}