use std::cell::RefCell;
use napi::bindgen_prelude::BigInt;
use napi_derive::napi;
#[cfg(feature = "perfetto")]
use rspack_tracing::PerfettoTracer;
use rspack_tracing::{StdoutTracer, TraceEvent, Tracer};
use rspack_util::tracing_preset::{
TRACING_ALL_PRESET, TRACING_BENCH_TARGET, TRACING_OVERVIEW_PRESET,
};
use rustc_hash::FxHashMap as HashMap;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::{
EnvFilter, Layer, Registry, layer::SubscriberExt, reload, util::SubscriberInitExt,
};
thread_local! {
static GLOBAL_TRACE_STATE: RefCell<TraceState> = const { RefCell::new(TraceState::Uninitialized) };
}
#[napi(object)]
#[derive(Debug)]
pub struct RawTraceEvent {
pub name: String,
pub track_name: Option<String>,
pub process_name: Option<String>,
pub args: Option<HashMap<String, String>>,
pub uuid: u32,
pub ts: BigInt,
pub ph: String,
pub categories: Option<Vec<String>>,
}
#[derive(Default)]
enum TraceState {
Uninitialized,
On(Box<dyn Tracer>, reload::Handle<EnvFilter, Registry>),
#[default]
Off,
}
pub(super) fn register_global_trace(
filter: String,
layer: String,
output: String,
) -> anyhow::Result<()> {
let filter = match filter.as_str() {
"OVERVIEW" => TRACING_OVERVIEW_PRESET,
"ALL" => TRACING_ALL_PRESET,
"BENCH" => TRACING_BENCH_TARGET,
_ => filter.as_str(),
};
GLOBAL_TRACE_STATE.with(|state| {
let mut state = state.borrow_mut();
if let TraceState::Uninitialized = *state {
let supported_layers = if cfg!(feature = "perfetto") {
"'logger', 'perfetto'"
} else {
"'logger'"
};
let mut tracer: Box<dyn Tracer> = match layer.as_str() {
"logger" => Box::new(StdoutTracer::default()),
#[cfg(feature = "perfetto")]
"perfetto" => Box::new(PerfettoTracer::default()),
#[cfg(not(feature = "perfetto"))]
"perfetto" => anyhow::bail!(
"Perfetto trace layer is not enabled in this build. Use `RSPACK_TRACE_LAYER=logger` or build with the `perfetto` feature."
),
_ => anyhow::bail!(
"Unexpected layer: {layer}, supported layers: {supported_layers}"
),
};
if let Some(layer) = tracer.setup(&output) {
let (filter,reload_handle) = reload::Layer::new(EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.with_regex(true)
.parse(filter)
.expect("Parse tracing directive syntax failed, for details about the directive syntax you could refer https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives")
);
tracing_subscriber::registry()
.with(<_ as Layer<Registry>>::with_filter(layer, filter))
.init();
let new_state = TraceState::On(tracer, reload_handle);
*state = new_state;
};
}
Ok(())
})
}
pub(super) fn cleanup_global_trace() {
GLOBAL_TRACE_STATE.with(|state| {
let mut state = state.borrow_mut();
match *state {
TraceState::Uninitialized => {
panic!("Global trace is not initialized, please call register_global_trace first");
}
TraceState::Off => {
}
TraceState::On(ref mut tracer, ref mut reload_handle) => {
tracer.teardown();
let _ = reload_handle.modify(|filter| *filter = EnvFilter::new("off"));
*state = TraceState::Off;
}
}
});
}
pub(super) fn sync_trace_event(events: Vec<RawTraceEvent>) {
use std::borrow::BorrowMut;
GLOBAL_TRACE_STATE.with(|state| {
let mut state = state.borrow_mut();
if let TraceState::On(tracer, _) = &mut **state.borrow_mut() {
tracer.sync_trace(
events
.into_iter()
.map(|event| TraceEvent {
name: event.name,
track_name: event.track_name,
process_name: event.process_name,
args: event.args.map(|args| args.into_iter().collect()),
uuid: event.uuid,
ts: event.ts.get_u64().1,
ph: event.ph,
categories: event.categories,
})
.collect(),
);
}
});
}