tracing_actix_web_mozlog/
subscriber.rs

1use gethostname::gethostname;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::{collections::HashMap, io::Write, time::SystemTime};
5use tracing::{Event, Level, Subscriber};
6use tracing_bunyan_formatter::JsonStorage;
7use tracing_subscriber::{fmt::MakeWriter, layer::Context};
8
9const MOZLOG_VERSION: &str = "2.0";
10
11/// This layer is exclusively concerned with formatting information using the
12/// [MozLog format](https://wiki.mozilla.org/Firefox/Services/Logging). It relies
13/// on the upstream [`crate::JsonStorageLayer`] to get access
14/// to the fields attached to each span.
15///
16/// # Example
17///
18/// ```
19/// use tracing_actix_web_mozlog::{JsonStorageLayer, MozLogFormatLayer};
20/// use tracing_subscriber::layer::SubscriberExt;
21/// let subscriber = tracing_subscriber::registry()
22///     .with(JsonStorageLayer)
23///     .with(MozLogFormatLayer::new("service-name", std::io::stdout));
24/// ```
25pub struct MozLogFormatLayer<W: for<'a> MakeWriter<'a> + 'static> {
26    name: String,
27    pid: u32,
28    hostname: String,
29    make_writer: W,
30}
31
32/// A logging message in MozLog format, adapted to Tracing.
33#[derive(Clone, Default, Debug, PartialEq, Deserialize, Serialize)]
34#[serde(rename_all = "PascalCase")]
35pub struct MozLogMessage {
36    /// Number of nanoseconds since the UNIX epoch (which is UTC)
37    pub timestamp: i64,
38
39    /// Type of message i.e. "request.summary"
40    #[serde(rename = "type")]
41    pub message_type: String,
42
43    /// Data source, server that is doing the logging, e.g. “Sync-1_5”
44    pub logger: String,
45
46    /// Hostname that generated the message
47    pub hostname: String,
48
49    /// Envelope version; log format version
50    pub env_version: String,
51
52    /// Process ID that generated the message
53    pub pid: u32,
54
55    /// Syslog severity levels
56    pub severity: u32,
57
58    /// Hash of fields
59    pub fields: HashMap<String, Value>,
60}
61
62impl<W: for<'a> MakeWriter<'a> + 'static> MozLogFormatLayer<W> {
63    /// Create a new moz log subscriber.
64    pub fn new<S: AsRef<str>>(name: S, make_writer: W) -> Self {
65        Self {
66            name: name.as_ref().to_string(),
67            make_writer,
68            pid: std::process::id(),
69            hostname: gethostname().to_string_lossy().into_owned(),
70        }
71    }
72
73    fn emit(&self, mut buffer: Vec<u8>) -> Result<(), std::io::Error> {
74        buffer.write_all(b"\n")?;
75        self.make_writer.make_writer().write_all(&buffer)
76    }
77}
78
79impl<S, W> tracing_subscriber::Layer<S> for MozLogFormatLayer<W>
80where
81    S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
82    W: for<'a> MakeWriter<'a> + 'static,
83{
84    fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
85        // Use a closure that returns a `Result` to enable usage of the `?`
86        // operator and make clearer code. This is called immediately below.
87        let make_log_line = || {
88            let mut event_visitor = JsonStorage::default();
89            event.record(&mut event_visitor);
90
91            let mut values: HashMap<String, Value> = event_visitor
92                .values()
93                .iter()
94                .map(|(k, v)| (k.to_string(), v.clone()))
95                .collect();
96
97            let spans = {
98                let mut span_names = vec![];
99                let mut current = ctx.lookup_current();
100                while let Some(span) = &current {
101                    {
102                        let ext = span.extensions();
103                        let span_visitor = ext
104                            .get::<JsonStorage>()
105                            .expect("MozLogFormatLayer requires JsonStorage layer");
106                        for (k, v) in span_visitor.values() {
107                            values.entry(k.to_string()).or_insert_with(|| v.clone());
108                        }
109                    }
110
111                    span_names.push(span.name());
112                    current = span.parent();
113                }
114                span_names.reverse();
115                span_names.join(",")
116            };
117
118            // See https://en.wikipedia.org/wiki/Syslog#Severity_levels
119            let severity = match *event.metadata().level() {
120                Level::ERROR => 3, // Syslog Error
121                Level::WARN => 4,  // Syslog Warning
122                Level::INFO => 5,  // Syslog Normal
123                Level::DEBUG => 6, // Syslog Informational
124                Level::TRACE => 7, // Syslog Debug
125            };
126
127            let type_field = values.remove("type");
128            let raw_type_field = values.remove("r#type");
129            values.insert("spans".to_string(), spans.into());
130
131            let v = MozLogMessage {
132                timestamp: SystemTime::now()
133                    .duration_since(SystemTime::UNIX_EPOCH)
134                    .unwrap_or_default()
135                    .as_nanos() as i64,
136                message_type: type_field
137                    .or(raw_type_field)
138                    .and_then(|v| v.as_str().map(|s| s.to_string()))
139                    .unwrap_or_else(|| "<unknown>".to_string()),
140                logger: self.name.clone(),
141                hostname: self.hostname.clone(),
142                env_version: MOZLOG_VERSION.to_string(),
143                pid: self.pid,
144                severity,
145                fields: values,
146            };
147
148            // If there is an error, just squash it quietly. After all, if we
149            // failed to log, we can't exactly log an error.
150            serde_json::to_vec(&v).map_err(|_| ())
151        };
152
153        let log_line_result: Result<Vec<u8>, ()> = make_log_line();
154        // Discard any errors, since they probably can't be logged anyways.
155        if let Ok(log_line) = log_line_result {
156            let _ = self.emit(log_line);
157        }
158    }
159}