1use std::marker::PhantomData;
2
3use js_sys::{JsString, Object, Reflect};
4use tracing_core::{span, Subscriber};
5use tracing_subscriber::{
6 field::RecordFields,
7 fmt::{FormatFields, FormattedFields},
8 layer::Context,
9 registry::{Extensions, ExtensionsMut, LookupSpan, SpanRef},
10 Layer,
11};
12use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
13
14#[wasm_bindgen]
15extern "C" {
16 #[wasm_bindgen(js_name = _fakeGlobal)]
17 type Global;
18 #[wasm_bindgen()]
19 type Performance;
20 #[wasm_bindgen(static_method_of = Global, js_class = "globalThis", getter)]
21 fn performance() -> Performance;
22 #[wasm_bindgen(method, catch, js_name = "mark")]
23 fn do_mark(this: &Performance, name: &str) -> Result<(), JsValue>;
24 #[wasm_bindgen(method, catch, js_name = "mark")]
25 fn do_mark_with_details(
26 this: &Performance,
27 name: &str,
28 details: &JsValue,
29 ) -> Result<(), JsValue>;
30 #[wasm_bindgen(method, catch, js_name = "measure")]
31 fn do_measure_with_start_mark_and_end_mark(
32 this: &Performance,
33 name: &str,
34 start: &str,
35 end: &str,
36 ) -> Result<(), JsValue>;
37 #[wasm_bindgen(method, catch, js_name = "measure")]
38 fn do_measure_with_details(
39 this: &Performance,
40 name: &str,
41 details: &JsValue,
42 ) -> Result<(), JsValue>;
43}
44
45impl Performance {
46 fn mark(&self, name: &str) -> Result<(), JsValue> {
47 self.do_mark(name)
48 }
49 fn mark_detailed(&self, name: &str, details: &str) -> Result<(), JsValue> {
50 let details_obj = Object::create(JsValue::NULL.unchecked_ref::<Object>());
51 let detail_prop = JsString::from(wasm_bindgen::intern("detail"));
52 Reflect::set(&details_obj, &detail_prop, &JsValue::from(details)).unwrap();
53 self.do_mark_with_details(name, &details_obj)
54 }
55 fn measure(&self, name: &str, start: &str, end: &str) -> Result<(), JsValue> {
56 self.do_measure_with_start_mark_and_end_mark(name, start, end)
57 }
58 fn measure_detailed(
59 &self,
60 name: &str,
61 start: &str,
62 end: &str,
63 details: &str,
64 ) -> Result<(), JsValue> {
65 let details_obj = Object::create(JsValue::NULL.unchecked_ref::<Object>());
66 let detail_prop = JsString::from(wasm_bindgen::intern("detail"));
67 let start_prop = JsString::from(wasm_bindgen::intern("start"));
68 let end_prop = JsString::from(wasm_bindgen::intern("end"));
69 Reflect::set(&details_obj, &detail_prop, &JsValue::from(details)).unwrap();
70 Reflect::set(&details_obj, &start_prop, &JsValue::from(start)).unwrap();
71 Reflect::set(&details_obj, &end_prop, &JsValue::from(end)).unwrap();
72 self.do_measure_with_details(name, &details_obj)
73 }
74}
75
76thread_local! {
77 static PERF: Performance = {
78 let performance = Global::performance();
79 assert!(!performance.is_undefined(), "browser seems to not support the Performance API");
80 performance
81 };
82}
83
84pub struct PerformanceEventsLayer<S, N = ()> {
88 fmt_details: N,
89 _inner: PhantomData<fn(S)>,
90}
91
92impl<S, N> PerformanceEventsLayer<S, N> {
93 pub fn with_details_from_fields<N2>(
98 self,
99 fmt_fields: N2,
100 ) -> PerformanceEventsLayer<S, FormatSpanFromFields<N2>>
101 where
102 N2: 'static + for<'writer> FormatFields<'writer>,
103 {
104 self.with_details(FormatSpanFromFields { inner: fmt_fields })
105 }
106 pub fn with_details<N2: FormatSpan>(self, fmt_details: N2) -> PerformanceEventsLayer<S, N2> {
110 PerformanceEventsLayer {
111 fmt_details,
112 _inner: PhantomData,
113 }
114 }
115}
116
117impl<S, N> PerformanceEventsLayer<S, N>
118where
119 S: Subscriber + for<'lookup> LookupSpan<'lookup>,
120 N: FormatSpan,
121{
122 fn template_name(span: &SpanRef<'_, S>, event_name: &str) -> String {
123 let span_id = span.id().into_u64();
124 let name = span.metadata().name();
125 format!("{name} [{span_id}]: {event_name}")
126 }
127 fn span_enter_name(&self, span: &SpanRef<'_, S>) -> String {
128 Self::template_name(span, "span-enter")
129 }
130 fn span_exit_name(&self, span: &SpanRef<'_, S>) -> String {
131 Self::template_name(span, "span-exit")
132 }
133 fn span_record_name(&self, span: &SpanRef<'_, S>) -> String {
134 Self::template_name(span, "span-record")
135 }
136 fn span_measure_name(&self, span: &SpanRef<'_, S>) -> String {
137 Self::template_name(span, "span-measure")
138 }
139}
140
141impl<S, N> Layer<S> for PerformanceEventsLayer<S, N>
142where
143 S: Subscriber + for<'lookup> LookupSpan<'lookup>,
144 N: FormatSpan,
145{
146 fn on_new_span(&self, attrs: &span::Attributes<'_>, span: &span::Id, ctx: Context<'_, S>) {
147 let span = ctx.span(span).expect("can't find span, this is a bug");
148
149 self.fmt_details
150 .add_details(&mut span.extensions_mut(), attrs);
151 }
152 fn on_record(&self, span: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
153 let span = ctx.span(span).expect("can't find span, this is a bug");
154 self.fmt_details
155 .record_values(&mut span.extensions_mut(), values);
156
157 let mark_name = self.span_record_name(&span);
158 let _ = PERF.with(|p| {
159 if let Some(details) = self.fmt_details.find_details(&span.extensions()) {
160 p.mark_detailed(&mark_name, details)
161 } else {
162 p.mark(&mark_name)
163 }
164 }); }
166 fn on_enter(&self, span: &span::Id, ctx: Context<'_, S>) {
167 let span = ctx.span(span).expect("can't find span, this is a bug");
168 let mark_name = self.span_enter_name(&span);
169 let _ = PERF.with(|p| {
170 if let Some(details) = self.fmt_details.find_details(&span.extensions()) {
171 p.mark_detailed(&mark_name, details)
172 } else {
173 p.mark(&mark_name)
174 }
175 }); }
177 fn on_exit(&self, span: &span::Id, ctx: Context<'_, S>) {
178 let span = ctx.span(span).expect("can't find span, this is a bug");
179 let mark_enter_name = self.span_enter_name(&span);
180 let mark_exit_name = self.span_exit_name(&span);
181 let mark_measure_name = self.span_measure_name(&span);
182 let _ = PERF.with(|p| {
183 if let Some(details) = self.fmt_details.find_details(&span.extensions()) {
184 p.mark_detailed(&mark_exit_name, details)?;
185 p.measure_detailed(
186 &mark_measure_name,
187 &mark_enter_name,
188 &mark_exit_name,
189 details,
190 )?;
191 } else {
192 p.mark(&mark_exit_name)?;
193 p.measure(&mark_measure_name, &mark_enter_name, &mark_exit_name)?;
194 }
195 Result::<(), JsValue>::Ok(())
196 }); }
198 fn on_id_change(&self, _: &span::Id, _: &span::Id, _ctx: Context<'_, S>) {
199 web_sys::console::warn_1(&JsValue::from(
200 "A span changed id, this is currently not supported",
201 ));
202 debug_assert!(false, "A span changed id, this is currently not supported");
203 }
204}
205
206pub fn performance_layer<S>() -> PerformanceEventsLayer<S, ()>
210where
211 S: Subscriber + for<'lookup> LookupSpan<'lookup>,
212{
213 PerformanceEventsLayer {
214 fmt_details: (),
215 _inner: PhantomData,
216 }
217}
218
219pub trait FormatSpan: 'static {
221 fn find_details<'ext>(&self, ext: &'ext Extensions<'_>) -> Option<&'ext str>;
223 fn add_details(&self, ext: &mut ExtensionsMut<'_>, attrs: &span::Attributes<'_>);
227 fn record_values(&self, ext: &mut ExtensionsMut<'_>, values: &span::Record<'_>);
231}
232
233impl FormatSpan for () {
234 fn find_details<'ext>(&self, _: &'ext Extensions<'_>) -> Option<&'ext str> {
235 None
236 }
237 fn add_details(&self, _: &mut ExtensionsMut<'_>, _: &span::Attributes<'_>) {}
238 fn record_values(&self, _: &mut ExtensionsMut<'_>, _: &span::Record<'_>) {}
239}
240
241pub struct FormatSpanFromFields<N> {
246 inner: N,
247}
248impl<N> FormatSpanFromFields<N>
249where
250 N: 'static + for<'writer> FormatFields<'writer>,
251{
252 fn add_formatted_fields(&self, ext: &mut ExtensionsMut<'_>, fields: impl RecordFields) {
253 if ext.get_mut::<FormattedFields<N>>().is_none() {
254 let mut fmt_fields = FormattedFields::<N>::new(String::new());
255 if self
256 .inner
257 .format_fields(fmt_fields.as_writer(), fields)
258 .is_ok()
259 {
260 ext.insert(fmt_fields);
261 }
262 }
263 }
264}
265
266impl<N> FormatSpan for FormatSpanFromFields<N>
267where
268 N: 'static + for<'writer> FormatFields<'writer>,
269{
270 fn find_details<'ext>(&self, ext: &'ext Extensions<'_>) -> Option<&'ext str> {
271 let fields = ext.get::<FormattedFields<N>>()?;
272 Some(&fields.fields)
273 }
274
275 fn add_details(&self, ext: &mut ExtensionsMut<'_>, attrs: &span::Attributes<'_>) {
276 self.add_formatted_fields(ext, attrs);
277 }
278
279 fn record_values(&self, ext: &mut ExtensionsMut<'_>, values: &span::Record<'_>) {
280 if let Some(fields) = ext.get_mut::<FormattedFields<N>>() {
281 let _ = self.inner.add_fields(fields, values);
282 } else {
283 self.add_formatted_fields(ext, values);
284 }
285 }
286}