use std::sync::OnceLock;
static INSTALLED: OnceLock<()> = OnceLock::new();
pub fn install_tracing() {
#[cfg(target_arch = "wasm32")]
INSTALLED.get_or_init(|| {
cratefield_core::set_error_forwarder(|level, line| match level {
cratefield_core::ControlLevel::Error => worker::console_error!("{line}"),
cratefield_core::ControlLevel::Warn => worker::console_warn!("{line}"),
cratefield_core::ControlLevel::Info => worker::console_log!("{line}"),
})
});
#[cfg(not(target_arch = "wasm32"))]
INSTALLED.get_or_init(|| {
let guard = tracing::dispatcher::set_default(&tracing::dispatcher::Dispatch::new(
ConsoleSubscriber {
next_span: AtomicU64::new(0),
},
));
std::mem::forget(guard);
});
}
#[cfg(not(target_arch = "wasm32"))]
mod native_subscriber {
use std::sync::atomic::AtomicU64;
use serde_json::{Map, Value, json};
use tracing::field::Visit;
use tracing::span::{Attributes, Id, Record};
use tracing::{Event, Level, Metadata, Subscriber};
#[derive(Default)]
struct JsonRedactingVisitor {
fields: Map<String, Value>,
}
impl JsonRedactingVisitor {
fn record_field(&mut self, name: &str, value: &str) {
let redacted = cratefield_core::redacted_value(name, value);
if redacted == "[redacted]" {
self.fields.insert(name.to_owned(), json!("[redacted]"));
} else if let Some(hash) = redacted.strip_prefix("subject_hash:").map(str::to_owned) {
self.fields
.insert(format!("{name}_hash"), json!(format!("sha256:{hash}")));
} else {
self.fields.insert(name.to_owned(), json!(redacted));
}
}
}
impl Visit for JsonRedactingVisitor {
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
self.record_field(field.name(), value);
}
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
self.record_field(field.name(), &format!("{value:?}"));
}
fn record_error(
&mut self,
field: &tracing::field::Field,
value: &(dyn std::error::Error + 'static),
) {
self.record_field(field.name(), &value.to_string());
}
}
pub(crate) struct ConsoleSubscriber {
pub next_span: AtomicU64,
}
impl Subscriber for ConsoleSubscriber {
fn enabled(&self, _metadata: &Metadata<'_>) -> bool {
true
}
fn new_span(&self, _span: &Attributes<'_>) -> Id {
use std::sync::atomic::Ordering;
let next = self.next_span.fetch_add(1, Ordering::Relaxed) + 1;
Id::from_u64(next)
}
fn record(&self, _span: &Id, _values: &Record<'_>) {}
fn record_follows_from(&self, _span: &Id, _follows: &Id) {}
fn event(&self, event: &Event<'_>) {
let mut visitor = JsonRedactingVisitor::default();
event.record(&mut visitor);
let metadata = event.metadata();
visitor
.fields
.insert("level".into(), json!(metadata.level().as_str()));
visitor
.fields
.insert("target".into(), json!(metadata.target()));
let timestamp = time::OffsetDateTime::now_utc()
.format(&time::format_description::well_known::Rfc3339)
.unwrap_or_else(|_| "unknown".to_string());
visitor.fields.insert("timestamp".into(), json!(timestamp));
let line = Value::Object(visitor.fields).to_string();
if *metadata.level() == Level::ERROR {
worker::console_error!("{line}");
} else {
worker::console_log!("{line}");
}
}
fn enter(&self, _span: &Id) {}
fn exit(&self, _span: &Id) {}
}
}
#[cfg(not(target_arch = "wasm32"))]
use native_subscriber::ConsoleSubscriber;
#[cfg(not(target_arch = "wasm32"))]
use std::sync::atomic::AtomicU64;