wasm_tracing/
layer.rs

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    /// Create a new [Layer] with the provided config
44    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 and measure so you can see a little blip in the profile
110            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}