tracing_browser_subscriber/
lib.rs

1// Copyright 2022 Jeremy Wall (Jeremy@marzhilsltudios.com)
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14use core::default::Default;
15use core::fmt::Write;
16
17use tracing;
18use tracing::span;
19use tracing_subscriber::layer::{Context, Layer};
20use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
21use tracing_subscriber::registry::{LookupSpan, Registry};
22
23mod console;
24use console::Recorder;
25use wasm_bindgen::UnwrapThrowExt;
26
27mod typings;
28
29pub struct BrowserLayer {
30    record_timings: bool,
31    max_level: tracing::Level,
32    //span_stack: Vec<span::Id>,
33}
34
35impl Default for BrowserLayer {
36    fn default() -> Self {
37        Self {
38            record_timings: true,
39            max_level: tracing::Level::INFO,
40        }
41    }
42}
43
44impl BrowserLayer {
45    pub fn new() -> Self {
46        Self::default()
47    }
48
49    pub fn with_max_level(mut self, level: tracing::Level) -> Self {
50        self.max_level = level;
51        self
52    }
53
54    pub fn with_record_timings(mut self, record_timings: bool) -> Self {
55        self.record_timings = record_timings;
56        self
57    }
58
59    pub fn write_for_level(
60        &self,
61        level: &tracing::Level,
62        origin: String,
63        context: Option<Vec<String>>,
64        recorder: Recorder,
65    ) {
66        let f: fn(String) = match *level {
67            tracing::Level::ERROR => typings::error,
68            tracing::Level::WARN => typings::warn,
69            tracing::Level::INFO => typings::info,
70            tracing::Level::DEBUG => typings::debug,
71            tracing::Level::TRACE => typings::trace,
72        };
73        let spans = context
74            .map(|v| {
75                let s: String = v.concat();
76                s
77            })
78            .unwrap_or_else(|| String::new());
79        f(format!("{} {} {} {}", level, origin, recorder, spans));
80    }
81}
82
83fn format_mark(name: Option<&str>, id: &tracing::Id) -> String {
84    let mut buffer = String::new();
85    if let Some(n) = name {
86        write!(buffer, "[{}]", n).unwrap_throw();
87    }
88    write!(buffer, "{:x}", id.into_u64()).unwrap_throw();
89    buffer
90}
91
92impl<S: tracing::Subscriber + for<'a> LookupSpan<'a>> Layer<S> for BrowserLayer {
93    fn enabled(&self, metadata: &tracing::Metadata<'_>, _ctx: Context<'_, S>) -> bool {
94        metadata.level() <= &self.max_level
95    }
96
97    fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
98        let mut recorder = Recorder::new();
99        attrs.record(&mut recorder);
100        if let Some(span_ref) = ctx.span(id) {
101            span_ref.extensions_mut().insert(recorder);
102        }
103    }
104
105    fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
106        if let Some(span_ref) = ctx.span(span) {
107            if let Some(recorder) = span_ref.extensions_mut().get_mut::<Recorder>() {
108                values.record(recorder)
109            }
110        }
111    }
112
113    fn on_event(&self, event: &tracing::Event<'_>, ctx: Context<'_, S>) {
114        let mut recorder = Recorder::new();
115        event.record(&mut recorder);
116        let metadata = event.metadata();
117        let recorders = ctx.current_span().id().and_then(|id| {
118            ctx.span_scope(id).map(|scope| {
119                let span_details: Vec<String> = scope
120                    .from_root()
121                    .map(|span_ref| {
122                        format!(
123                            "{} {}",
124                            span_ref.name(),
125                            span_ref
126                                .extensions()
127                                .get::<Recorder>()
128                                .expect("Unregistered Span")
129                        )
130                    })
131                    .collect();
132                span_details
133            })
134        });
135        self.write_for_level(
136            metadata.level(),
137            metadata
138                .file()
139                .and_then(|file| metadata.line().map(|ln| format!("{}:{}", file, ln)))
140                .unwrap_or_default(),
141            recorders,
142            recorder,
143        );
144    }
145
146    fn on_enter(&self, id: &span::Id, ctx: Context<'_, S>) {
147        if self.record_timings {
148            if let Some(span_ref) = ctx.span(id) {
149                typings::mark(&format_mark(Some(span_ref.metadata().name()), id));
150            } else {
151                typings::mark(&format_mark(None, id));
152            }
153        }
154    }
155
156    fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) {
157        if self.record_timings {
158            if let Some(span_ref) = ctx.span(id) {
159                let metadata = span_ref.metadata();
160                if let Some(recorder) = span_ref.extensions_mut().get_mut::<Recorder>() {
161                    typings::measure(
162                        format!(
163                            "[{}] {} {}",
164                            metadata.name(),
165                            metadata.module_path().unwrap_or("..."),
166                            recorder
167                        ),
168                        format_mark(Some(span_ref.metadata().name()), id),
169                    )
170                    .unwrap_throw();
171                } else {
172                    typings::measure(
173                        format!(
174                            "[{}] {}",
175                            metadata.name(),
176                            metadata.module_path().unwrap_or("...")
177                        ),
178                        format_mark(None, id),
179                    )
180                    .unwrap_throw();
181                }
182            }
183        }
184    }
185}
186
187pub fn configure_as_global_default() {
188    tracing::subscriber::set_global_default(
189        Registry::default().with(BrowserLayer::new().with_max_level(tracing::Level::DEBUG)),
190    )
191    .expect("default global");
192}
193#[cfg(test)]
194mod tests;