1use std::io::Write;
2use std::time::Instant;
3
4use env_logger::fmt::style::{RgbColor, Style};
5use log::info;
6use once_cell::sync::Lazy;
7#[cfg(feature = "otel_tracing")]
8use opentelemetry::trace::TracerProvider as _;
9#[cfg(feature = "otel_tracing")]
10use opentelemetry_otlp::WithExportConfig as _;
11#[cfg(feature = "otel_tracing")]
12use opentelemetry_sdk::Resource;
13#[cfg(any(feature = "otel_tracing", feature = "flame"))]
14use tracing_subscriber::layer::SubscriberExt as _;
15#[cfg(feature = "otel_tracing")]
16use tracing_subscriber::Layer as _;
17
18use crate::format_duration;
19
20pub static INIT_INSTANT: Lazy<Instant> = Lazy::new(Instant::now);
23
24#[cfg(not(tarpaulin_include))]
26#[inline]
27pub fn uptime() -> u64 {
28 INIT_INSTANT.elapsed().as_secs()
29}
30
31#[allow(clippy::module_name_repetitions)]
32#[cfg(not(tarpaulin_include))]
48#[allow(clippy::missing_inline_in_public_items)]
49pub fn init_logger(filter: log::LevelFilter, log_file_path: Option<std::path::PathBuf>) {
50 let now = Lazy::force(&INIT_INSTANT);
52
53 let log_file = log_file_path.map(|path| {
55 let path = path
56 .is_dir()
57 .then(|| path.join("mecomp.log"))
58 .unwrap_or(path);
59
60 let log_file = std::fs::OpenOptions::new()
61 .create(true)
62 .append(true)
63 .open(path)
64 .expect("Failed to create log file");
65
66 log_file
67 });
68
69 let mut env = String::new();
72 #[allow(clippy::option_if_let_else)]
73 match std::env::var("RUST_LOG") {
74 Ok(e) => {
75 std::env::set_var("RUST_LOG", &e);
76 env = e;
77 }
78 _ => std::env::set_var("RUST_LOG", format!("off,mecomp={filter}")),
81 }
82
83 env_logger::Builder::new()
84 .format(move |buf, record| {
85 let style = buf.default_level_style(record.level());
86 let (level_style, level) = match record.level() {
87 log::Level::Debug => (
88 style
89 .fg_color(Some(RgbColor::from((0, 0x80, 0x80)).into()))
90 .bold(),
91 "D",
92 ),
93 log::Level::Trace => (
94 style
95 .fg_color(Some(RgbColor::from((255, 0, 255)).into()))
96 .bold(),
97 "T",
98 ),
99 log::Level::Info => (
100 style
101 .fg_color(Some(RgbColor::from((255, 255, 255)).into()))
102 .bold(),
103 "I",
104 ),
105 log::Level::Warn => (
106 style
107 .fg_color(Some(RgbColor::from((255, 255, 0)).into()))
108 .bold(),
109 "W",
110 ),
111 log::Level::Error => (
112 style
113 .fg_color(Some(RgbColor::from((255, 0, 0)).into()))
114 .bold(),
115 "E",
116 ),
117 };
118
119 let dimmed_style = Style::default().dimmed();
120
121 let log_line = format!(
122 "| {level_style}{level}{level_style:#} | {dimmed_style}{}{dimmed_style:#} | {dimmed_style}{: >39} @ {: <4}{dimmed_style:#} | {}",
131 format_duration(&now.elapsed()),
132 process_file(record.file().unwrap_or("???")),
133 record.line().unwrap_or(0),
134 record.args(),
135 );
136 writeln!(buf, "{log_line}")?;
137
138 if let Some(log_file) = &log_file {
140 let mut log_file = log_file.try_clone().expect("Failed to clone log file");
141
142 let unformatted_log_line: String = log_line
144 .replace(&level_style.render().to_string(), "")
145 .replace(&dimmed_style.render().to_string(), "")
146 .replace("\x1B[0m", "");
147
148 writeln!(log_file, "{unformatted_log_line}")?;
149 log_file.sync_all().expect("Failed to sync log file");
150 }
151
152 Ok(())
153 })
154 .write_style(env_logger::WriteStyle::Always)
155 .parse_default_env()
156 .init();
157
158 if env.is_empty() {
159 info!("Log Level (Flag) ... {filter}");
160 } else {
161 info!("Log Level (RUST_LOG) ... {env}");
162 }
163}
164
165fn process_file(file: &str) -> &str {
167 if file.contains("mecomp/") {
168 file.split("mecomp/").last().unwrap_or(file)
169 } else {
170 file
171 }
172}
173
174#[must_use]
180#[allow(clippy::missing_inline_in_public_items)]
181pub fn init_tracing() -> impl tracing::Subscriber {
182 let subscriber = tracing_subscriber::registry();
183
184 #[cfg(feature = "flame")]
185 let (flame_layer, _guard) = tracing_flame::FlameLayer::with_file("tracing.folded").unwrap();
186 #[cfg(feature = "flame")]
187 let subscriber = subscriber.with(flame_layer);
188
189 #[cfg(not(feature = "verbose_tracing"))]
190 #[allow(unused_variables)]
191 let filter = tracing_subscriber::EnvFilter::builder()
192 .parse("off,mecomp=trace")
193 .unwrap();
194 #[cfg(feature = "verbose_tracing")]
195 #[allow(unused_variables)]
196 let filter = tracing_subscriber::EnvFilter::new("trace")
197 .add_directive("hyper=off".parse().unwrap())
198 .add_directive("opentelemetry=off".parse().unwrap())
199 .add_directive("tonic=off".parse().unwrap())
200 .add_directive("h2=off".parse().unwrap())
201 .add_directive("reqwest=off".parse().unwrap());
202
203 #[cfg(feature = "otel_tracing")]
204 std::env::set_var("OTEL_BSP_MAX_EXPORT_BATCH_SIZE", "12");
205 #[cfg(feature = "otel_tracing")]
206 let tracer = opentelemetry_sdk::trace::SdkTracerProvider::builder()
207 .with_batch_exporter(
208 opentelemetry_otlp::SpanExporter::builder()
209 .with_tonic()
210 .with_endpoint("http://localhost:4317")
211 .build()
212 .expect("Failed to build OTLP exporter"),
213 )
214 .with_id_generator(opentelemetry_sdk::trace::RandomIdGenerator::default())
215 .with_resource(Resource::builder().with_service_name("mecomp").build())
216 .build()
217 .tracer("mecomp");
218
219 #[cfg(feature = "otel_tracing")]
220 let subscriber = subscriber.with(
221 tracing_opentelemetry::layer()
222 .with_tracer(tracer)
223 .with_filter(filter),
224 );
225
226 subscriber
227}