use std::time::Instant;
use std::{io::Write, sync::LazyLock};
use env_logger::fmt::style::{RgbColor, Style};
use log::{Record, info};
#[cfg(feature = "otel_tracing")]
use opentelemetry::trace::TracerProvider as _;
#[cfg(feature = "otel_tracing")]
use opentelemetry_otlp::WithExportConfig as _;
#[cfg(feature = "otel_tracing")]
use opentelemetry_sdk::Resource;
#[cfg(feature = "otel_tracing")]
use tracing_subscriber::Layer as _;
use tracing_subscriber::layer::SubscriberExt;
use crate::format_duration;
pub static INIT_INSTANT: LazyLock<Instant> = LazyLock::new(Instant::now);
#[cfg(not(tarpaulin_include))]
#[inline]
pub fn uptime() -> u64 {
INIT_INSTANT.elapsed().as_secs()
}
#[allow(clippy::module_name_repetitions)]
#[cfg(not(tarpaulin_include))]
#[allow(clippy::missing_inline_in_public_items)]
pub fn init_logger(filter: log::LevelFilter, log_file_path: Option<std::path::PathBuf>) {
let now = LazyLock::force(&INIT_INSTANT);
let log_file = log_file_path.map(|path| {
let path = if path.is_dir() {
path.join("mecomp.log")
} else {
path
};
std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)
.expect("Failed to create log file")
});
let mut env = String::new();
match std::env::var("RUST_LOG") {
Ok(e) => {
unsafe {
std::env::set_var("RUST_LOG", &e);
}
env = e;
}
_ => unsafe {
std::env::set_var(
"RUST_LOG",
format!("off,mecomp={filter},surrealqlx={filter}"),
);
},
}
env_logger::Builder::new()
.format(move |buf, record| {
let style = buf.default_level_style(record.level());
let (level_style, level) = match record.level() {
log::Level::Debug => (
style
.fg_color(Some(RgbColor::from((0, 0x80, 0x80)).into()))
.bold(),
"D",
),
log::Level::Trace => (
style
.fg_color(Some(RgbColor::from((255, 0, 255)).into()))
.bold(),
"T",
),
log::Level::Info => (
style
.fg_color(Some(RgbColor::from((255, 255, 255)).into()))
.bold(),
"I",
),
log::Level::Warn => (
style
.fg_color(Some(RgbColor::from((255, 255, 0)).into()))
.bold(),
"W",
),
log::Level::Error => (
style
.fg_color(Some(RgbColor::from((255, 0, 0)).into()))
.bold(),
"E",
),
};
let dimmed_style = Style::default().dimmed();
let log_line = format!(
"| {level_style}{level}{level_style:#} | {dimmed_style}{}{dimmed_style:#} | {dimmed_style}{: >39} @ {: <4}{dimmed_style:#} | {}",
format_duration(&now.elapsed()),
process_path_of(record),
record.line().unwrap_or_default(),
record.args(),
);
writeln!(buf, "{log_line}")?;
if let Some(log_file) = &log_file {
let mut log_file = log_file.try_clone().expect("Failed to clone log file");
let unformatted_log_line: String = log_line
.replace(&level_style.render().to_string(), "")
.replace(&dimmed_style.render().to_string(), "")
.replace("\x1B[0m", "");
writeln!(log_file, "{unformatted_log_line}")?;
log_file.sync_all().expect("Failed to sync log file");
}
Ok(())
})
.write_style(env_logger::WriteStyle::Always)
.parse_default_env()
.init();
if env.is_empty() {
info!("Log Level (Flag) ... {filter}");
} else {
info!("Log Level (RUST_LOG) ... {env}");
}
}
fn process_path_of<'a>(record: &'a Record<'a>) -> &'a str {
#[cfg(debug_assertions)]
const DEBUG_BUILD: bool = true;
#[cfg(not(debug_assertions))]
const DEBUG_BUILD: bool = false;
let module_path = record.module_path();
let file_path = record.file();
match (DEBUG_BUILD, module_path, file_path) {
(true, _, Some(file)) | (false, None, Some(file)) => {
if file.contains("mecomp/") {
file.split("mecomp/").last().unwrap_or(file)
} else {
file
}
}
(true, Some(module), None) | (false, Some(module), _) => module,
(true | false, None, None) => "??",
}
}
#[must_use]
#[allow(clippy::missing_inline_in_public_items)]
pub fn init_tracing() -> impl tracing::Subscriber {
let subscriber = tracing_subscriber::registry();
#[cfg(feature = "flame")]
let (flame_layer, _guard) = tracing_flame::FlameLayer::with_file("tracing.folded").unwrap();
#[cfg(feature = "flame")]
let subscriber = subscriber.with(flame_layer);
#[cfg(all(not(feature = "verbose_tracing"), feature = "otel_tracing"))]
let filter = tracing_subscriber::EnvFilter::builder()
.parse("off,mecomp=trace,surrealqlx=trace")
.unwrap();
#[cfg(all(feature = "verbose_tracing", feature = "otel_tracing"))]
let filter = tracing_subscriber::EnvFilter::new("trace")
.add_directive("hyper=off".parse().unwrap())
.add_directive("opentelemetry=off".parse().unwrap())
.add_directive("tonic=off".parse().unwrap())
.add_directive("h2=off".parse().unwrap())
.add_directive("reqwest=off".parse().unwrap());
#[cfg(feature = "otel_tracing")]
unsafe {
std::env::set_var("OTEL_BSP_MAX_EXPORT_BATCH_SIZE", "12");
}
#[cfg(feature = "otel_tracing")]
let tracer = opentelemetry_sdk::trace::SdkTracerProvider::builder()
.with_batch_exporter(
opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint("http://localhost:4317")
.build()
.expect("Failed to build OTLP exporter"),
)
.with_id_generator(opentelemetry_sdk::trace::RandomIdGenerator::default())
.with_resource(Resource::builder().with_service_name("mecomp").build())
.build()
.tracer("mecomp");
#[cfg(feature = "otel_tracing")]
let subscriber = subscriber.with(
tracing_opentelemetry::layer()
.with_tracer(tracer)
.with_filter(filter),
);
#[cfg(feature = "tokio_console")]
let console_layer = console_subscriber::Builder::default()
.retention(std::time::Duration::from_secs(60 * 20)) .enable_self_trace(true)
.spawn();
#[cfg(feature = "tokio_console")]
let subscriber = subscriber.with(console_layer);
#[cfg(not(any(feature = "otel_tracing", feature = "flame", feature = "tokio_console")))]
let subscriber = subscriber.with(tracing_subscriber::filter::LevelFilter::OFF);
subscriber
}