1use crate::{
10 LogFormat, LogOutputDest, appender,
11 error::{Error, Result},
12};
13use std::collections::BTreeMap;
14use tracing_appender::non_blocking::WorkerGuard;
15use tracing_core::{Event, Level, Subscriber};
16use tracing_subscriber::{
17 Layer, Registry,
18 filter::Targets,
19 fmt::{
20 self as tracing_fmt, FmtContext, FormatEvent, FormatFields,
21 format::Writer,
22 time::{FormatTime, SystemTime},
23 },
24 layer::Filter,
25 registry::LookupSpan,
26 reload::{self, Handle},
27};
28
29const MAX_LOG_SIZE: usize = 20 * 1024 * 1024;
30const MAX_UNCOMPRESSED_LOG_FILES: usize = 10;
31const MAX_LOG_FILES: usize = 1000;
32const ALL_ANT_LOGS: &str = "all";
34const VERBOSE_ANT_LOGS: &str = "v";
36
37pub struct ReloadHandle(pub(crate) Handle<Box<dyn Filter<Registry> + Send + Sync>, Registry>);
39
40impl ReloadHandle {
41 pub fn modify_log_level(&self, logging_value: &str) -> Result<()> {
47 let targets: Vec<(String, Level)> = get_logging_targets(logging_value)?;
48 self.0.modify(|old_filter| {
49 let new_filter: Box<dyn Filter<Registry> + Send + Sync> =
50 Box::new(Targets::new().with_targets(targets));
51 *old_filter = new_filter;
52 })?;
53
54 Ok(())
55 }
56}
57
58#[derive(Default)]
59pub(crate) struct LogFormatter;
61
62impl<S, N> FormatEvent<S, N> for LogFormatter
63where
64 S: Subscriber + for<'a> LookupSpan<'a>,
65 N: for<'a> FormatFields<'a> + 'static,
66{
67 fn format_event(
68 &self,
69 ctx: &FmtContext<'_, S, N>,
70 mut writer: Writer,
71 event: &Event<'_>,
72 ) -> std::fmt::Result {
73 let level = *event.metadata().level();
75 let module = event.metadata().module_path().unwrap_or("<unknown module>");
76 let lno = event.metadata().line().unwrap_or(0);
77 let time = SystemTime;
78
79 write!(writer, "[")?;
80 time.format_time(&mut writer)?;
81 write!(writer, " {level} {module} {lno}")?;
82 ctx.visit_spans(|span| write!(writer, "/{}", span.name()))?;
83 write!(writer, "] ")?;
84
85 ctx.field_format().format_fields(writer.by_ref(), event)?;
87
88 writeln!(writer)
89 }
90}
91
92#[derive(Default)]
94pub(crate) struct TracingLayers {
95 pub(crate) layers: Vec<Box<dyn Layer<Registry> + Send + Sync>>,
96 pub(crate) log_appender_guard: Option<WorkerGuard>,
97}
98
99impl TracingLayers {
100 pub(crate) fn fmt_layer(
101 &mut self,
102 default_logging_targets: Vec<(String, Level)>,
103 output_dest: &LogOutputDest,
104 format: LogFormat,
105 max_uncompressed_log_files: Option<usize>,
106 max_compressed_log_files: Option<usize>,
107 print_updates_to_stdout: bool,
108 ) -> Result<ReloadHandle> {
109 let layer = match output_dest {
110 LogOutputDest::Stdout => {
111 if print_updates_to_stdout {
112 println!("Logging to stdout");
113 }
114 tracing_fmt::layer()
115 .with_ansi(false)
116 .with_target(false)
117 .event_format(LogFormatter)
118 .boxed()
119 }
120 LogOutputDest::Stderr => tracing_fmt::layer()
121 .with_ansi(false)
122 .with_target(false)
123 .event_format(LogFormatter)
124 .with_writer(std::io::stderr)
125 .boxed(),
126 LogOutputDest::Path(path) => {
127 if path.extension() == Some(std::ffi::OsStr::new("log")) {
129 if let Some(parent) = path.parent() {
131 std::fs::create_dir_all(parent)?;
132 }
133 if print_updates_to_stdout {
134 println!("Logging to file: {path:?}");
135 }
136
137 let file = std::fs::OpenOptions::new()
138 .create(true)
139 .append(true)
140 .open(path)?;
141
142 match format {
143 LogFormat::Json => tracing_fmt::layer()
144 .json()
145 .flatten_event(true)
146 .with_writer(file)
147 .boxed(),
148 LogFormat::Default => tracing_fmt::layer()
149 .with_ansi(false)
150 .with_writer(file)
151 .event_format(LogFormatter)
152 .boxed(),
153 }
154 } else {
155 std::fs::create_dir_all(path)?;
157 if print_updates_to_stdout {
158 println!("Logging to directory: {path:?}");
159 }
160
161 let max_uncompressed_log_files =
163 max_uncompressed_log_files.unwrap_or(MAX_UNCOMPRESSED_LOG_FILES);
164 let max_log_files =
166 if let Some(max_compressed_log_files) = max_compressed_log_files {
167 max_compressed_log_files + max_uncompressed_log_files
168 } else {
169 std::cmp::max(max_uncompressed_log_files, MAX_LOG_FILES)
170 };
171 let (file_rotation, worker_guard) = appender::file_rotater(
172 path,
173 MAX_LOG_SIZE,
174 max_uncompressed_log_files,
175 max_log_files,
176 );
177 self.log_appender_guard = Some(worker_guard);
178
179 match format {
180 LogFormat::Json => tracing_fmt::layer()
181 .json()
182 .flatten_event(true)
183 .with_writer(file_rotation)
184 .boxed(),
185 LogFormat::Default => tracing_fmt::layer()
186 .with_ansi(false)
187 .with_writer(file_rotation)
188 .event_format(LogFormatter)
189 .boxed(),
190 }
191 }
192 }
193 };
194 let targets = match std::env::var("ANT_LOG") {
195 Ok(sn_log_val) => {
196 if print_updates_to_stdout {
197 println!("Using ANT_LOG={sn_log_val}");
198 }
199 get_logging_targets(&sn_log_val)?
200 }
201 Err(_) => default_logging_targets,
202 };
203
204 let target_filters: Box<dyn Filter<Registry> + Send + Sync> =
205 Box::new(Targets::new().with_targets(targets));
206
207 let (filter, reload_handle) = reload::Layer::new(target_filters);
208
209 let layer = layer.with_filter(filter);
210 self.layers.push(Box::new(layer));
211
212 Ok(ReloadHandle(reload_handle))
213 }
214
215 #[cfg(feature = "otlp")]
216 pub(crate) fn otlp_layer(
217 &mut self,
218 default_logging_targets: Vec<(String, Level)>,
219 ) -> Result<()> {
220 use opentelemetry::{
221 KeyValue,
222 sdk::{Resource, trace},
223 };
224 use opentelemetry_otlp::WithExportConfig;
225 use opentelemetry_semantic_conventions::resource::{SERVICE_INSTANCE_ID, SERVICE_NAME};
226 use rand::{Rng, distributions::Alphanumeric, thread_rng};
227
228 let service_name = std::env::var("OTLP_SERVICE_NAME").unwrap_or_else(|_| {
229 let random_node_name: String = thread_rng()
230 .sample_iter(&Alphanumeric)
231 .take(10)
232 .map(char::from)
233 .collect();
234 random_node_name
235 });
236 println!("The opentelemetry traces are logged under the name: {service_name}");
237
238 let tracer = opentelemetry_otlp::new_pipeline()
239 .tracing()
240 .with_exporter(opentelemetry_otlp::new_exporter().tonic().with_env())
241 .with_trace_config(trace::config().with_resource(Resource::new(vec![
242 KeyValue::new(SERVICE_NAME, service_name),
243 KeyValue::new(SERVICE_INSTANCE_ID, std::process::id().to_string()),
244 ])))
245 .install_batch(opentelemetry::runtime::Tokio)?;
246
247 let targets = match std::env::var("ANT_LOG_OTLP") {
248 Ok(sn_log_val) => {
249 println!("Using ANT_LOG_OTLP={sn_log_val}");
250 get_logging_targets(&sn_log_val)?
251 }
252 Err(_) => default_logging_targets,
253 };
254
255 let target_filters: Box<dyn Filter<Registry> + Send + Sync> =
256 Box::new(Targets::new().with_targets(targets));
257 let otlp_layer = tracing_opentelemetry::layer()
258 .with_tracer(tracer)
259 .with_filter(target_filters)
260 .boxed();
261 self.layers.push(otlp_layer);
262 Ok(())
263 }
264}
265
266fn get_logging_targets(logging_env_value: &str) -> Result<Vec<(String, Level)>> {
271 let mut targets = BTreeMap::new();
272 let mut contains_keyword_all_sn_logs = false;
273 let mut contains_keyword_verbose_sn_logs = false;
274
275 for crate_log_level in logging_env_value.split(',') {
276 if crate_log_level == ALL_ANT_LOGS {
279 contains_keyword_all_sn_logs = true;
280 continue;
281 } else if crate_log_level == VERBOSE_ANT_LOGS {
282 contains_keyword_verbose_sn_logs = true;
283 continue;
284 }
285
286 let mut split = crate_log_level.split('=');
287 let crate_name = split.next().ok_or_else(|| {
288 Error::LoggingConfiguration("Could not obtain crate name in logging string".to_string())
289 })?;
290 let log_level = split.next().unwrap_or("trace");
291 targets.insert(crate_name.to_string(), get_log_level_from_str(log_level)?);
292 }
293
294 let mut to_be_overriden_targets =
295 if contains_keyword_all_sn_logs || contains_keyword_verbose_sn_logs {
296 let mut t = BTreeMap::from_iter(vec![
297 ("ant".to_string(), Level::TRACE),
299 ("evm_testnet".to_string(), Level::TRACE),
300 ("antnode".to_string(), Level::TRACE),
301 ("antctl".to_string(), Level::TRACE),
302 ("node_launchpad".to_string(), Level::DEBUG),
303 ("ant_bootstrap".to_string(), Level::TRACE),
305 ("ant_build_info".to_string(), Level::TRACE),
306 ("ant_evm".to_string(), Level::TRACE),
307 ("ant_logging".to_string(), Level::TRACE),
308 ("ant_node_manager".to_string(), Level::TRACE),
309 ("ant_node_rpc_client".to_string(), Level::TRACE),
310 ("ant_protocol".to_string(), Level::TRACE),
311 ("ant_service_management".to_string(), Level::TRACE),
312 ("service-manager".to_string(), Level::DEBUG),
313 ("autonomi".to_string(), Level::TRACE),
314 ("evmlib".to_string(), Level::TRACE),
315 ]);
316
317 if !t.contains_key("ant_node") {
318 if contains_keyword_all_sn_logs {
319 t.insert("ant_node".to_string(), Level::TRACE)
320 } else if contains_keyword_verbose_sn_logs {
321 t.insert("ant_node".to_string(), Level::DEBUG)
322 } else {
323 t.insert("ant_node".to_string(), Level::INFO)
324 };
325 }
326 t
327 } else {
328 Default::default()
329 };
330 to_be_overriden_targets.extend(targets);
331 Ok(to_be_overriden_targets.into_iter().collect())
332}
333
334fn get_log_level_from_str(log_level: &str) -> Result<Level> {
335 match log_level.to_lowercase().as_str() {
336 "info" => Ok(Level::INFO),
337 "debug" => Ok(Level::DEBUG),
338 "trace" => Ok(Level::TRACE),
339 "warn" => Ok(Level::WARN),
340 "error" => Ok(Level::WARN),
341 _ => Err(Error::LoggingConfiguration(format!(
342 "Log level {log_level} is not supported"
343 ))),
344 }
345}