tlsn_wasm/
log.rs

1use serde::Deserialize;
2use tracing::{error, Level, Metadata};
3use tracing_subscriber::{
4    filter::FilterFn,
5    fmt::{format::FmtSpan, time::UtcTime},
6    layer::SubscriberExt,
7    util::SubscriberInitExt,
8};
9use tracing_web::MakeWebConsoleWriter;
10use tsify_next::Tsify;
11
12pub(crate) fn init_logging(config: Option<LoggingConfig>) {
13    let mut config = config.unwrap_or_default();
14
15    // Default is NONE
16    let fmt_span = config
17        .span_events
18        .take()
19        .unwrap_or_default()
20        .into_iter()
21        .map(FmtSpan::from)
22        .fold(FmtSpan::NONE, |acc, span| acc | span);
23
24    let fmt_layer = tracing_subscriber::fmt::layer()
25        .with_ansi(false) // Only partially supported across browsers
26        .with_timer(UtcTime::rfc_3339()) // std::time is not available in browsers
27        .with_span_events(fmt_span)
28        .without_time()
29        .with_writer(MakeWebConsoleWriter::new()); // write events to the console
30
31    tracing_subscriber::registry()
32        .with(FilterFn::new(filter(config.clone())))
33        .with(fmt_layer)
34        .init();
35
36    // https://github.com/rustwasm/console_error_panic_hook
37    std::panic::set_hook(Box::new(|info| {
38        error!("panic occurred: {:?}", info);
39        console_error_panic_hook::hook(info);
40    }));
41}
42
43#[derive(Debug, Clone, Copy, Tsify, Deserialize)]
44#[tsify(from_wasm_abi)]
45pub enum LoggingLevel {
46    Trace,
47    Debug,
48    Info,
49    Warn,
50    Error,
51}
52
53impl From<LoggingLevel> for Level {
54    fn from(value: LoggingLevel) -> Self {
55        match value {
56            LoggingLevel::Trace => Level::TRACE,
57            LoggingLevel::Debug => Level::DEBUG,
58            LoggingLevel::Info => Level::INFO,
59            LoggingLevel::Warn => Level::WARN,
60            LoggingLevel::Error => Level::ERROR,
61        }
62    }
63}
64
65#[derive(Debug, Clone, Copy, Tsify, Deserialize)]
66#[tsify(from_wasm_abi)]
67pub enum SpanEvent {
68    New,
69    Close,
70    Active,
71}
72
73impl From<SpanEvent> for FmtSpan {
74    fn from(value: SpanEvent) -> Self {
75        match value {
76            SpanEvent::New => FmtSpan::NEW,
77            SpanEvent::Close => FmtSpan::CLOSE,
78            SpanEvent::Active => FmtSpan::ACTIVE,
79        }
80    }
81}
82
83#[derive(Debug, Default, Clone, Tsify, Deserialize)]
84#[tsify(from_wasm_abi)]
85pub struct LoggingConfig {
86    pub level: Option<LoggingLevel>,
87    pub crate_filters: Option<Vec<CrateLogFilter>>,
88    pub span_events: Option<Vec<SpanEvent>>,
89}
90
91#[derive(Debug, Clone, Tsify, Deserialize)]
92#[tsify(from_wasm_abi)]
93pub struct CrateLogFilter {
94    pub level: LoggingLevel,
95    pub name: String,
96}
97
98pub(crate) fn filter(config: LoggingConfig) -> impl Fn(&Metadata) -> bool {
99    let default_level: Level = config.level.unwrap_or(LoggingLevel::Info).into();
100    let crate_filters = config
101        .crate_filters
102        .unwrap_or_default()
103        .into_iter()
104        .map(|filter| (filter.name, Level::from(filter.level)))
105        .collect::<Vec<_>>();
106
107    move |meta| {
108        let level = if let Some(crate_name) = meta.target().split("::").next() {
109            crate_filters
110                .iter()
111                .find_map(|(filter_name, filter_level)| {
112                    if crate_name.eq_ignore_ascii_case(filter_name) {
113                        Some(filter_level)
114                    } else {
115                        None
116                    }
117                })
118                .unwrap_or(&default_level)
119        } else {
120            &default_level
121        };
122
123        meta.level() <= level
124    }
125}