Skip to main content

bitwarden_logging/
event.rs

1//! Flight Recorder event definitions.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6#[cfg(feature = "wasm")]
7use {tsify::Tsify, wasm_bindgen::prelude::*};
8
9use crate::visitor::MessageVisitor;
10
11/// A single log event captured by the Flight Recorder.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(rename_all = "camelCase")]
14#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
15pub struct FlightRecorderEvent {
16    /// Unix timestamp in milliseconds.
17    pub timestamp: i64,
18    /// Log level (e.g. "DEBUG", "INFO", "WARN", "ERROR").
19    pub level: String,
20    /// Target module path (e.g. "bitwarden_core::client").
21    pub target: String,
22    /// Primary log message.
23    pub message: String,
24    /// Structured fields from the tracing event.
25    #[cfg_attr(feature = "wasm", tsify(type = "Record<string, string>"))]
26    pub fields: HashMap<String, String>,
27}
28
29impl From<&tracing::Event<'_>> for FlightRecorderEvent {
30    fn from(event: &tracing::Event<'_>) -> Self {
31        let mut visitor = MessageVisitor::default();
32        event.record(&mut visitor);
33
34        Self {
35            timestamp: chrono::Utc::now().timestamp_millis(),
36            level: event.metadata().level().to_string(),
37            target: event.metadata().target().to_string(),
38            message: visitor.message,
39            fields: visitor.fields,
40        }
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn test_event_serialization_roundtrip() {
50        let event = FlightRecorderEvent {
51            timestamp: 1234567890,
52            level: "INFO".to_string(),
53            target: "test::module".to_string(),
54            message: "Test message".to_string(),
55            fields: HashMap::new(),
56        };
57
58        let json = serde_json::to_string(&event).expect("should serialize");
59        let deserialized: FlightRecorderEvent =
60            serde_json::from_str(&json).expect("should deserialize");
61
62        assert_eq!(deserialized.timestamp, 1234567890);
63        assert_eq!(deserialized.level, "INFO");
64        assert_eq!(deserialized.target, "test::module");
65        assert_eq!(deserialized.message, "Test message");
66        assert!(deserialized.fields.is_empty());
67    }
68
69    #[test]
70    fn test_event_with_fields() {
71        let mut fields = HashMap::new();
72        fields.insert("user_id".to_string(), "abc-123".to_string());
73        fields.insert("action".to_string(), "login".to_string());
74
75        let event = FlightRecorderEvent {
76            timestamp: 0,
77            level: "INFO".to_string(),
78            target: "test".to_string(),
79            message: "hello".to_string(),
80            fields,
81        };
82
83        let json = serde_json::to_string(&event).expect("should serialize");
84        assert!(json.contains("\"user_id\":\"abc-123\""));
85        assert!(json.contains("\"action\":\"login\""));
86    }
87}