Skip to main content

multistore_cf_workers/
tracing_layer.rs

1//! A lightweight `tracing` layer that routes log output to the Workers
2//! `console.log` / `console.error` / `console.warn` APIs.
3//!
4//! This avoids pulling in `tracing-subscriber` (which depends on `std::time`
5//! and other things unavailable on `wasm32`). Instead we implement a minimal
6//! [`tracing::Subscriber`] that formats events and forwards them through
7//! `worker::console_log!` / `console_error!` / `console_warn!`.
8
9use tracing::field::{Field, Visit};
10use tracing::span;
11use tracing::{Event, Level, Metadata, Subscriber};
12
13/// A minimal tracing subscriber that logs to the Workers console.
14///
15/// Install once at the start of each request:
16/// ```rust,ignore
17/// tracing::subscriber::set_global_default(WorkerSubscriber::new())
18///     .ok(); // ignore if already set
19/// ```
20pub struct WorkerSubscriber {
21    max_level: Level,
22}
23
24impl WorkerSubscriber {
25    /// Create a subscriber with the default max level (`DEBUG`).
26    pub fn new() -> Self {
27        Self {
28            max_level: Level::DEBUG,
29        }
30    }
31
32    /// Set the maximum verbosity level that will be logged.
33    ///
34    /// Events above this level are silently discarded. Defaults to `DEBUG`.
35    pub fn with_max_level(mut self, level: Level) -> Self {
36        self.max_level = level;
37        self
38    }
39}
40
41impl Default for WorkerSubscriber {
42    fn default() -> Self {
43        Self::new()
44    }
45}
46
47impl Subscriber for WorkerSubscriber {
48    fn enabled(&self, metadata: &Metadata<'_>) -> bool {
49        metadata.level() <= &self.max_level
50    }
51
52    fn new_span(&self, _attrs: &span::Attributes<'_>) -> span::Id {
53        // We don't track spans -- just log events.
54        span::Id::from_u64(1)
55    }
56
57    fn record(&self, _span: &span::Id, _values: &span::Record<'_>) {}
58    fn record_follows_from(&self, _span: &span::Id, _follows: &span::Id) {}
59    fn event(&self, event: &Event<'_>) {
60        let metadata = event.metadata();
61        let level = metadata.level();
62        let target = metadata.target();
63
64        let mut visitor = MessageVisitor::default();
65        event.record(&mut visitor);
66
67        let msg = if visitor.fields.is_empty() {
68            format!("[{level}] {target}: {}", visitor.message)
69        } else {
70            format!(
71                "[{level}] {target}: {} {{ {} }}",
72                visitor.message,
73                visitor.fields.join(", ")
74            )
75        };
76
77        // Route to the appropriate console method based on level.
78        // worker::console_log! and friends are macros that call into JS.
79        match *level {
80            Level::ERROR => worker::console_error!("{}", msg),
81            Level::WARN => worker::console_warn!("{}", msg),
82            _ => worker::console_log!("{}", msg),
83        }
84    }
85
86    fn enter(&self, _span: &span::Id) {}
87    fn exit(&self, _span: &span::Id) {}
88}
89
90/// Visitor that extracts the `message` field and collects remaining fields
91/// into `key=value` pairs.
92#[derive(Default)]
93struct MessageVisitor {
94    message: String,
95    fields: Vec<String>,
96}
97
98impl Visit for MessageVisitor {
99    fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
100        if field.name() == "message" {
101            self.message = format!("{:?}", value);
102        } else {
103            self.fields.push(format!("{}={:?}", field.name(), value));
104        }
105    }
106
107    fn record_str(&mut self, field: &Field, value: &str) {
108        if field.name() == "message" {
109            self.message = value.to_string();
110        } else {
111            self.fields.push(format!("{}=\"{}\"", field.name(), value));
112        }
113    }
114
115    fn record_u64(&mut self, field: &Field, value: u64) {
116        self.fields.push(format!("{}={}", field.name(), value));
117    }
118
119    fn record_i64(&mut self, field: &Field, value: i64) {
120        self.fields.push(format!("{}={}", field.name(), value));
121    }
122
123    fn record_bool(&mut self, field: &Field, value: bool) {
124        self.fields.push(format!("{}={}", field.name(), value));
125    }
126}