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 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) .with_timer(UtcTime::rfc_3339()) .with_span_events(fmt_span)
28 .without_time()
29 .with_writer(MakeWebConsoleWriter::new()); tracing_subscriber::registry()
32 .with(FilterFn::new(filter(config.clone())))
33 .with(fmt_layer)
34 .init();
35
36 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}