1use std::sync::atomic::AtomicUsize;
2
3use tracing::Subscriber;
4#[cfg(feature = "tracing-log")]
5use tracing_log::NormalizeEvent as _;
6use tracing_subscriber::{layer::Context, registry::LookupSpan, Layer};
7
8use crate::{
9 log1, log4, mark, mark_name, measure, prelude::*, recorder::StringRecorder,
10 thread_display_suffix,
11};
12
13#[doc = r#"
14Implements [tracing_subscriber::layer::Layer] which uses [wasm_bindgen] for marking and measuring via `window.performance` and `window.console`
15
16If composing a subscriber, provide `WasmLayer` as such:
17
18```notest
19use tracing_subscriber::prelude::*;
20use tracing::Subscriber;
21
22pub struct MySubscriber {
23 // ...
24}
25
26impl Subscriber for MySubscriber {
27 // ...
28}
29
30let subscriber = MySubscriber::new()
31 .with(WasmLayer::default());
32
33tracing::subscriber::set_global_default(subscriber);
34```
35
36"#]
37pub struct WasmLayer {
38 last_event_id: AtomicUsize,
39 config: WasmLayerConfig,
40}
41
42impl WasmLayer {
43 pub fn new(config: WasmLayerConfig) -> Self {
45 WasmLayer {
46 last_event_id: AtomicUsize::new(0),
47 config,
48 }
49 }
50}
51
52impl Default for WasmLayer {
53 fn default() -> Self {
54 WasmLayer::new(WasmLayerConfig::default())
55 }
56}
57
58impl<S: Subscriber + for<'a> LookupSpan<'a>> Layer<S> for WasmLayer {
59 fn enabled(&self, metadata: &tracing::Metadata<'_>, _: Context<'_, S>) -> bool {
60 let level = metadata.level();
61 level <= &self.config.max_level
62 }
63
64 fn on_new_span(
65 &self,
66 attrs: &tracing::span::Attributes<'_>,
67 id: &tracing::Id,
68 ctx: Context<'_, S>,
69 ) {
70 let mut new_debug_record = StringRecorder::new(self.config.show_fields);
71 attrs.record(&mut new_debug_record);
72
73 if let Some(span_ref) = ctx.span(id) {
74 span_ref
75 .extensions_mut()
76 .insert::<StringRecorder>(new_debug_record);
77 }
78 }
79
80 fn on_record(&self, id: &tracing::Id, values: &tracing::span::Record<'_>, ctx: Context<'_, S>) {
81 if let Some(span_ref) = ctx.span(id) {
82 if let Some(debug_record) = span_ref.extensions_mut().get_mut::<StringRecorder>() {
83 values.record(debug_record);
84 }
85 }
86 }
87
88 fn on_event(&self, event: &tracing::Event<'_>, ctx: Context<'_, S>) {
89 if !self.config.report_logs_in_timings && !self.config.console.reporting_enabled() {
90 return;
91 }
92
93 let mut recorder = StringRecorder::new(self.config.show_fields);
94 event.record(&mut recorder);
95 #[cfg(feature = "tracing-log")]
96 let normalized_meta = event.normalized_metadata();
97 #[cfg(feature = "tracing-log")]
98 let meta = normalized_meta.as_ref().unwrap_or_else(|| event.metadata());
99 #[cfg(not(feature = "tracing-log"))]
100 let meta = event.metadata();
101 let level = meta.level();
102
103 if self.config.report_logs_in_timings {
104 let mark_name = format!(
105 "c{:x}",
106 self.last_event_id
107 .fetch_add(1, core::sync::atomic::Ordering::Relaxed)
108 );
109 mark(&mark_name);
111 let _ = measure(
112 format!(
113 "{} {}{} {}",
114 level,
115 meta.module_path().unwrap_or("..."),
116 thread_display_suffix(),
117 recorder,
118 ),
119 mark_name,
120 );
121 }
122 if let ConsoleConfig::NoReporting = self.config.console {
123 return;
124 }
125 let origin = if self.config.show_origin {
126 meta.file()
127 .and_then(|file| {
128 meta.line().map(|ln| {
129 format!(
130 "{}{}:{}",
131 self.config.origin_base_url.as_deref().unwrap_or_default(),
132 file,
133 ln
134 )
135 })
136 })
137 .unwrap_or_default()
138 } else {
139 String::new()
140 };
141
142 let fields = ctx
143 .lookup_current()
144 .and_then(|span| {
145 span.extensions()
146 .get::<StringRecorder>()
147 .map(|span_recorder| {
148 span_recorder
149 .fields
150 .iter()
151 .map(|(key, value)| format!("\n\t{key}: {value}"))
152 .collect::<Vec<_>>()
153 .join("")
154 })
155 })
156 .unwrap_or_default();
157
158 match self.config.console {
159 ConsoleConfig::ReportWithConsoleColor => log4(
160 format!(
161 "%c{}%c {}{}%c{}{}",
162 level,
163 origin,
164 thread_display_suffix(),
165 recorder,
166 fields
167 ),
168 match *level {
169 tracing::Level::TRACE => "color: dodgerblue; background: #444",
170 tracing::Level::DEBUG => "color: lawngreen; background: #444",
171 tracing::Level::INFO => "color: whitesmoke; background: #444",
172 tracing::Level::WARN => "color: orange; background: #444",
173 tracing::Level::ERROR => "color: red; background: #444",
174 },
175 "color: gray; font-style: italic",
176 "color: inherit",
177 ),
178 ConsoleConfig::ReportWithoutConsoleColor => log1(format!(
179 "{} {}{} {}{}",
180 level,
181 origin,
182 thread_display_suffix(),
183 recorder,
184 fields
185 )),
186 ConsoleConfig::NoReporting => unreachable!(),
187 };
188 }
189
190 fn on_enter(&self, id: &tracing::Id, _ctx: Context<'_, S>) {
191 if self.config.report_logs_in_timings {
192 mark(&mark_name(id));
193 }
194 }
195
196 fn on_exit(&self, id: &tracing::Id, ctx: Context<'_, S>) {
197 if !self.config.report_logs_in_timings {
198 return;
199 }
200
201 if let Some(span_ref) = ctx.span(id) {
202 let meta = span_ref.metadata();
203 if let Some(debug_record) = span_ref.extensions().get::<StringRecorder>() {
204 let _ = measure(
205 format!(
206 "\"{}\"{} {} {}",
207 meta.name(),
208 thread_display_suffix(),
209 meta.module_path().unwrap_or("..."),
210 debug_record,
211 ),
212 mark_name(id),
213 );
214 } else {
215 let _ = measure(
216 format!(
217 "\"{}\"{} {}",
218 meta.name(),
219 thread_display_suffix(),
220 meta.module_path().unwrap_or("..."),
221 ),
222 mark_name(id),
223 );
224 }
225 }
226 }
227}