1use std::time::Instant;
2use std::{io::Write, sync::LazyLock};
3
4use env_logger::fmt::style::{RgbColor, Style};
5use log::{Record, info};
6#[cfg(feature = "otel_tracing")]
7use opentelemetry::trace::TracerProvider as _;
8#[cfg(feature = "otel_tracing")]
9use opentelemetry_otlp::WithExportConfig as _;
10#[cfg(feature = "otel_tracing")]
11use opentelemetry_sdk::Resource;
12#[cfg(feature = "otel_tracing")]
13use tracing_subscriber::Layer as _;
14#[cfg(any(feature = "otel_tracing", feature = "flame", feature = "tokio_console"))]
15use tracing_subscriber::layer::SubscriberExt as _;
16
17use crate::format_duration;
18
19pub static INIT_INSTANT: LazyLock<Instant> = LazyLock::new(Instant::now);
22
23#[cfg(not(tarpaulin_include))]
25#[inline]
26pub fn uptime() -> u64 {
27 INIT_INSTANT.elapsed().as_secs()
28}
29
30#[allow(clippy::module_name_repetitions)]
31#[cfg(not(tarpaulin_include))]
47#[allow(clippy::missing_inline_in_public_items)]
48pub fn init_logger(filter: log::LevelFilter, log_file_path: Option<std::path::PathBuf>) {
49 let now = LazyLock::force(&INIT_INSTANT);
51
52 let log_file = log_file_path.map(|path| {
54 let path = if path.is_dir() {
55 path.join("mecomp.log")
56 } else {
57 path
58 };
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 match std::env::var("RUST_LOG") {
73 Ok(e) => {
74 unsafe {
75 std::env::set_var("RUST_LOG", &e);
77 }
78 env = e;
79 }
80 _ => unsafe {
83 std::env::set_var("RUST_LOG", format!("off,mecomp={filter}"));
85 },
86 }
87
88 env_logger::Builder::new()
89 .format(move |buf, record| {
90 let style = buf.default_level_style(record.level());
91 let (level_style, level) = match record.level() {
92 log::Level::Debug => (
93 style
94 .fg_color(Some(RgbColor::from((0, 0x80, 0x80)).into()))
95 .bold(),
96 "D",
97 ),
98 log::Level::Trace => (
99 style
100 .fg_color(Some(RgbColor::from((255, 0, 255)).into()))
101 .bold(),
102 "T",
103 ),
104 log::Level::Info => (
105 style
106 .fg_color(Some(RgbColor::from((255, 255, 255)).into()))
107 .bold(),
108 "I",
109 ),
110 log::Level::Warn => (
111 style
112 .fg_color(Some(RgbColor::from((255, 255, 0)).into()))
113 .bold(),
114 "W",
115 ),
116 log::Level::Error => (
117 style
118 .fg_color(Some(RgbColor::from((255, 0, 0)).into()))
119 .bold(),
120 "E",
121 ),
122 };
123
124 let dimmed_style = Style::default().dimmed();
125
126 let log_line = format!(
127 "| {level_style}{level}{level_style:#} | {dimmed_style}{}{dimmed_style:#} | {dimmed_style}{: >39} @ {: <4}{dimmed_style:#} | {}",
136 format_duration(&now.elapsed()),
137 process_path_of(record),
138 record.line().unwrap_or_default(),
139 record.args(),
140 );
141 writeln!(buf, "{log_line}")?;
142
143 if let Some(log_file) = &log_file {
145 let mut log_file = log_file.try_clone().expect("Failed to clone log file");
146
147 let unformatted_log_line: String = log_line
149 .replace(&level_style.render().to_string(), "")
150 .replace(&dimmed_style.render().to_string(), "")
151 .replace("\x1B[0m", "");
152
153 writeln!(log_file, "{unformatted_log_line}")?;
154 log_file.sync_all().expect("Failed to sync log file");
155 }
156
157 Ok(())
158 })
159 .write_style(env_logger::WriteStyle::Always)
160 .parse_default_env()
161 .init();
162
163 if env.is_empty() {
164 info!("Log Level (Flag) ... {filter}");
165 } else {
166 info!("Log Level (RUST_LOG) ... {env}");
167 }
168}
169
170fn process_path_of<'a>(record: &'a Record<'a>) -> &'a str {
177 #[cfg(debug_assertions)]
178 const DEBUG_BUILD: bool = true;
179 #[cfg(not(debug_assertions))]
180 const DEBUG_BUILD: bool = false;
181
182 let module_path = record.module_path();
183 let file_path = record.file();
184
185 match (DEBUG_BUILD, module_path, file_path) {
186 (true, _, Some(file)) | (false, None, Some(file)) => {
189 if file.contains("mecomp/") {
191 file.split("mecomp/").last().unwrap_or(file)
192 } else {
193 file
194 }
195 }
196 (true, Some(module), None) | (false, Some(module), _) => module,
199
200 (true | false, None, None) => "??",
202 }
203}
204
205#[must_use]
211#[allow(clippy::missing_inline_in_public_items)]
212pub fn init_tracing() -> impl tracing::Subscriber {
213 let subscriber = tracing_subscriber::registry();
214
215 #[cfg(feature = "flame")]
216 let (flame_layer, _guard) = tracing_flame::FlameLayer::with_file("tracing.folded").unwrap();
217 #[cfg(feature = "flame")]
218 let subscriber = subscriber.with(flame_layer);
219
220 #[cfg(not(feature = "verbose_tracing"))]
221 #[allow(unused_variables)]
222 let filter = tracing_subscriber::EnvFilter::builder()
223 .parse("off,mecomp=trace")
224 .unwrap();
225 #[cfg(feature = "verbose_tracing")]
226 #[allow(unused_variables)]
227 let filter = tracing_subscriber::EnvFilter::new("trace")
228 .add_directive("hyper=off".parse().unwrap())
229 .add_directive("opentelemetry=off".parse().unwrap())
230 .add_directive("tonic=off".parse().unwrap())
231 .add_directive("h2=off".parse().unwrap())
232 .add_directive("reqwest=off".parse().unwrap());
233
234 #[cfg(feature = "otel_tracing")]
235 unsafe {
236 std::env::set_var("OTEL_BSP_MAX_EXPORT_BATCH_SIZE", "12");
238 }
239 #[cfg(feature = "otel_tracing")]
240 let tracer = opentelemetry_sdk::trace::SdkTracerProvider::builder()
241 .with_batch_exporter(
242 opentelemetry_otlp::SpanExporter::builder()
243 .with_tonic()
244 .with_endpoint("http://localhost:4317")
245 .build()
246 .expect("Failed to build OTLP exporter"),
247 )
248 .with_id_generator(opentelemetry_sdk::trace::RandomIdGenerator::default())
249 .with_resource(Resource::builder().with_service_name("mecomp").build())
250 .build()
251 .tracer("mecomp");
252
253 #[cfg(feature = "otel_tracing")]
254 let subscriber = subscriber.with(
255 tracing_opentelemetry::layer()
256 .with_tracer(tracer)
257 .with_filter(filter),
258 );
259
260 #[cfg(feature = "tokio_console")]
261 let console_layer = console_subscriber::Builder::default()
262 .retention(std::time::Duration::from_secs(60 * 20)) .enable_self_trace(true)
264 .spawn();
265 #[cfg(feature = "tokio_console")]
266 let subscriber = subscriber.with(console_layer);
267
268 subscriber
269}