Skip to main content

otelite_core/telemetry/
log.rs

1//! Log telemetry types
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6/// Represents a log record
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct LogRecord {
9    /// Timestamp in nanoseconds since Unix epoch
10    pub timestamp: i64,
11
12    /// Observed timestamp (when the event was observed)
13    pub observed_timestamp: Option<i64>,
14
15    /// Severity level
16    pub severity: SeverityLevel,
17
18    /// Severity text (human-readable)
19    pub severity_text: Option<String>,
20
21    /// Log body/message
22    pub body: String,
23
24    /// Log attributes
25    pub attributes: HashMap<String, String>,
26
27    /// Trace ID (if part of a trace)
28    pub trace_id: Option<String>,
29
30    /// Span ID (if part of a span)
31    pub span_id: Option<String>,
32
33    /// Associated resource
34    pub resource: Option<super::Resource>,
35}
36
37/// Log severity levels (aligned with OpenTelemetry)
38#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
39#[repr(u8)]
40pub enum SeverityLevel {
41    /// Trace level (most verbose)
42    Trace = 1,
43
44    /// Debug level
45    Debug = 5,
46
47    /// Info level
48    Info = 9,
49
50    /// Warn level
51    Warn = 13,
52
53    /// Error level
54    Error = 17,
55
56    /// Fatal level (most severe)
57    Fatal = 21,
58}
59
60impl LogRecord {
61    /// Create a new log record
62    pub fn new(severity: SeverityLevel, body: impl Into<String>, timestamp: i64) -> Self {
63        Self {
64            timestamp,
65            observed_timestamp: None,
66            severity,
67            severity_text: None,
68            body: body.into(),
69            attributes: HashMap::new(),
70            trace_id: None,
71            span_id: None,
72            resource: None,
73        }
74    }
75
76    /// Add an attribute to the log record
77    pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
78        self.attributes.insert(key.into(), value.into());
79        self
80    }
81
82    /// Set the trace context
83    pub fn with_trace_context(mut self, trace_id: String, span_id: String) -> Self {
84        self.trace_id = Some(trace_id);
85        self.span_id = Some(span_id);
86        self
87    }
88
89    /// Set the resource for the log record
90    pub fn with_resource(mut self, resource: super::Resource) -> Self {
91        self.resource = Some(resource);
92        self
93    }
94}
95
96impl SeverityLevel {
97    /// Convert severity level to string
98    pub fn as_str(&self) -> &'static str {
99        match self {
100            Self::Trace => "TRACE",
101            Self::Debug => "DEBUG",
102            Self::Info => "INFO",
103            Self::Warn => "WARN",
104            Self::Error => "ERROR",
105            Self::Fatal => "FATAL",
106        }
107    }
108
109    /// Convert from integer representation
110    pub fn from_i32(value: i32) -> Option<Self> {
111        match value {
112            1 => Some(Self::Trace),
113            5 => Some(Self::Debug),
114            9 => Some(Self::Info),
115            13 => Some(Self::Warn),
116            17 => Some(Self::Error),
117            21 => Some(Self::Fatal),
118            _ => None,
119        }
120    }
121
122    /// Convert to integer representation
123    pub fn to_i32(self) -> i32 {
124        self as i32
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn test_log_record_creation() {
134        let log = LogRecord::new(SeverityLevel::Info, "Application started", 1234567890);
135        assert_eq!(log.severity, SeverityLevel::Info);
136        assert_eq!(log.body, "Application started");
137        assert_eq!(log.timestamp, 1234567890);
138    }
139
140    #[test]
141    fn test_log_with_attributes() {
142        let log = LogRecord::new(SeverityLevel::Error, "Connection failed", 1234567890)
143            .with_attribute("error.type", "NetworkError")
144            .with_attribute("retry.count", "3");
145
146        assert_eq!(log.attributes.len(), 2);
147        assert_eq!(
148            log.attributes.get("error.type"),
149            Some(&"NetworkError".to_string())
150        );
151    }
152
153    #[test]
154    fn test_log_with_trace_context() {
155        let log = LogRecord::new(SeverityLevel::Debug, "Processing request", 1234567890)
156            .with_trace_context("trace123".to_string(), "span456".to_string());
157
158        assert_eq!(log.trace_id, Some("trace123".to_string()));
159        assert_eq!(log.span_id, Some("span456".to_string()));
160    }
161
162    #[test]
163    fn test_severity_ordering() {
164        assert!(SeverityLevel::Trace < SeverityLevel::Debug);
165        assert!(SeverityLevel::Debug < SeverityLevel::Info);
166        assert!(SeverityLevel::Info < SeverityLevel::Warn);
167        assert!(SeverityLevel::Warn < SeverityLevel::Error);
168        assert!(SeverityLevel::Error < SeverityLevel::Fatal);
169    }
170
171    #[test]
172    fn test_severity_as_str() {
173        assert_eq!(SeverityLevel::Info.as_str(), "INFO");
174        assert_eq!(SeverityLevel::Error.as_str(), "ERROR");
175    }
176}