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 std::fs::OpenOptions::new()
61 .create(true)
62 .append(true)
63 .open(path)
64 .expect("Failed to create log file")
65 });
66
67 let mut env = String::new();
70 match std::env::var("RUST_LOG") {
71 Ok(e) => {
72 unsafe {
73 std::env::set_var("RUST_LOG", &e);
75 }
76 env = e;
77 }
78 _ => unsafe {
81 std::env::set_var(
83 "RUST_LOG",
84 format!("off,mecomp={filter},surrealqlx={filter}"),
85 );
86 },
87 }
88
89 env_logger::Builder::new()
90 .format(move |buf, record| {
91 let style = buf.default_level_style(record.level());
92 let (level_style, level) = match record.level() {
93 log::Level::Debug => (
94 style
95 .fg_color(Some(RgbColor::from((0, 0x80, 0x80)).into()))
96 .bold(),
97 "D",
98 ),
99 log::Level::Trace => (
100 style
101 .fg_color(Some(RgbColor::from((255, 0, 255)).into()))
102 .bold(),
103 "T",
104 ),
105 log::Level::Info => (
106 style
107 .fg_color(Some(RgbColor::from((255, 255, 255)).into()))
108 .bold(),
109 "I",
110 ),
111 log::Level::Warn => (
112 style
113 .fg_color(Some(RgbColor::from((255, 255, 0)).into()))
114 .bold(),
115 "W",
116 ),
117 log::Level::Error => (
118 style
119 .fg_color(Some(RgbColor::from((255, 0, 0)).into()))
120 .bold(),
121 "E",
122 ),
123 };
124
125 let dimmed_style = Style::default().dimmed();
126
127 let log_line = format!(
128 "| {level_style}{level}{level_style:#} | {dimmed_style}{}{dimmed_style:#} | {dimmed_style}{: >39} @ {: <4}{dimmed_style:#} | {}",
137 format_duration(&now.elapsed()),
138 process_path_of(record),
139 record.line().unwrap_or_default(),
140 record.args(),
141 );
142 writeln!(buf, "{log_line}")?;
143
144 if let Some(log_file) = &log_file {
146 let mut log_file = log_file.try_clone().expect("Failed to clone log file");
147
148 let unformatted_log_line: String = log_line
150 .replace(&level_style.render().to_string(), "")
151 .replace(&dimmed_style.render().to_string(), "")
152 .replace("\x1B[0m", "");
153
154 writeln!(log_file, "{unformatted_log_line}")?;
155 log_file.sync_all().expect("Failed to sync log file");
156 }
157
158 Ok(())
159 })
160 .write_style(env_logger::WriteStyle::Always)
161 .parse_default_env()
162 .init();
163
164 if env.is_empty() {
165 info!("Log Level (Flag) ... {filter}");
166 } else {
167 info!("Log Level (RUST_LOG) ... {env}");
168 }
169}
170
171fn process_path_of<'a>(record: &'a Record<'a>) -> &'a str {
178 #[cfg(debug_assertions)]
179 const DEBUG_BUILD: bool = true;
180 #[cfg(not(debug_assertions))]
181 const DEBUG_BUILD: bool = false;
182
183 let module_path = record.module_path();
184 let file_path = record.file();
185
186 match (DEBUG_BUILD, module_path, file_path) {
187 (true, _, Some(file)) | (false, None, Some(file)) => {
190 if file.contains("mecomp/") {
192 file.split("mecomp/").last().unwrap_or(file)
193 } else {
194 file
195 }
196 }
197 (true, Some(module), None) | (false, Some(module), _) => module,
200
201 (true | false, None, None) => "??",
203 }
204}
205
206#[must_use]
212#[allow(clippy::missing_inline_in_public_items)]
213pub fn init_tracing() -> impl tracing::Subscriber {
214 let subscriber = tracing_subscriber::registry();
215
216 #[cfg(feature = "flame")]
217 let (flame_layer, _guard) = tracing_flame::FlameLayer::with_file("tracing.folded").unwrap();
218 #[cfg(feature = "flame")]
219 let subscriber = subscriber.with(flame_layer);
220
221 #[cfg(not(feature = "verbose_tracing"))]
222 #[allow(unused_variables)]
223 let filter = tracing_subscriber::EnvFilter::builder()
224 .parse("off,mecomp=trace,surrealqlx=trace")
225 .unwrap();
226 #[cfg(feature = "verbose_tracing")]
227 #[allow(unused_variables)]
228 let filter = tracing_subscriber::EnvFilter::new("trace")
229 .add_directive("hyper=off".parse().unwrap())
230 .add_directive("opentelemetry=off".parse().unwrap())
231 .add_directive("tonic=off".parse().unwrap())
232 .add_directive("h2=off".parse().unwrap())
233 .add_directive("reqwest=off".parse().unwrap());
234
235 #[cfg(feature = "otel_tracing")]
236 unsafe {
237 std::env::set_var("OTEL_BSP_MAX_EXPORT_BATCH_SIZE", "12");
239 }
240 #[cfg(feature = "otel_tracing")]
241 let tracer = opentelemetry_sdk::trace::SdkTracerProvider::builder()
242 .with_batch_exporter(
243 opentelemetry_otlp::SpanExporter::builder()
244 .with_tonic()
245 .with_endpoint("http://localhost:4317")
246 .build()
247 .expect("Failed to build OTLP exporter"),
248 )
249 .with_id_generator(opentelemetry_sdk::trace::RandomIdGenerator::default())
250 .with_resource(Resource::builder().with_service_name("mecomp").build())
251 .build()
252 .tracer("mecomp");
253
254 #[cfg(feature = "otel_tracing")]
255 let subscriber = subscriber.with(
256 tracing_opentelemetry::layer()
257 .with_tracer(tracer)
258 .with_filter(filter),
259 );
260
261 #[cfg(feature = "tokio_console")]
262 let console_layer = console_subscriber::Builder::default()
263 .retention(std::time::Duration::from_secs(60 * 20)) .enable_self_trace(true)
265 .spawn();
266 #[cfg(feature = "tokio_console")]
267 let subscriber = subscriber.with(console_layer);
268
269 subscriber
270}