opentelemetry_sdk/logs/
record.rs

1use crate::growable_array::GrowableArray;
2#[cfg(feature = "trace")]
3use opentelemetry::trace::SpanContext;
4use opentelemetry::{
5    logs::{AnyValue, Severity},
6    Key, SpanId, TraceFlags, TraceId,
7};
8use std::{borrow::Cow, time::SystemTime};
9
10// According to a Go-specific study mentioned on https://go.dev/blog/slog,
11// up to 5 attributes is the most common case.
12const PREALLOCATED_ATTRIBUTE_CAPACITY: usize = 5;
13
14/// Represents a collection of log record attributes with a predefined capacity.
15///
16/// This type uses `GrowableArray` to store key-value pairs of log attributes, where each attribute is an `Option<(Key, AnyValue)>`.
17/// The initial attributes are allocated in a fixed-size array of capacity `PREALLOCATED_ATTRIBUTE_CAPACITY`.
18/// If more attributes are added beyond this capacity, additional storage is handled by dynamically growing a vector.
19pub(crate) type LogRecordAttributes =
20    GrowableArray<Option<(Key, AnyValue)>, PREALLOCATED_ATTRIBUTE_CAPACITY>;
21
22#[derive(Debug, Clone, PartialEq)]
23#[non_exhaustive]
24/// LogRecord represents all data carried by a log record, and
25/// is provided to `LogExporter`s as input.
26pub struct SdkLogRecord {
27    /// Event name. Optional as not all the logging API support it.
28    pub(crate) event_name: Option<&'static str>,
29
30    /// Target of the log record
31    pub(crate) target: Option<Cow<'static, str>>,
32
33    /// Record timestamp
34    pub(crate) timestamp: Option<SystemTime>,
35
36    /// Timestamp for when the record was observed by OpenTelemetry
37    pub(crate) observed_timestamp: Option<SystemTime>,
38
39    /// Trace context for logs associated with spans
40    pub(crate) trace_context: Option<TraceContext>,
41
42    /// The original severity string from the source
43    pub(crate) severity_text: Option<&'static str>,
44
45    /// The corresponding severity value, normalized
46    pub(crate) severity_number: Option<Severity>,
47
48    /// Record body
49    pub(crate) body: Option<AnyValue>,
50
51    /// Additional attributes associated with this record
52    pub(crate) attributes: LogRecordAttributes,
53}
54
55impl opentelemetry::logs::LogRecord for SdkLogRecord {
56    fn set_event_name(&mut self, name: &'static str) {
57        self.event_name = Some(name);
58    }
59
60    // Sets the `target` of a record
61    fn set_target<T>(&mut self, _target: T)
62    where
63        T: Into<Cow<'static, str>>,
64    {
65        self.target = Some(_target.into());
66    }
67
68    fn set_timestamp(&mut self, timestamp: SystemTime) {
69        self.timestamp = Some(timestamp);
70    }
71
72    fn set_observed_timestamp(&mut self, timestamp: SystemTime) {
73        self.observed_timestamp = Some(timestamp);
74    }
75
76    fn set_severity_text(&mut self, severity_text: &'static str) {
77        self.severity_text = Some(severity_text);
78    }
79
80    fn set_severity_number(&mut self, severity_number: Severity) {
81        self.severity_number = Some(severity_number);
82    }
83
84    fn set_body(&mut self, body: AnyValue) {
85        self.body = Some(body);
86    }
87
88    fn add_attributes<I, K, V>(&mut self, attributes: I)
89    where
90        I: IntoIterator<Item = (K, V)>,
91        K: Into<Key>,
92        V: Into<AnyValue>,
93    {
94        for (key, value) in attributes.into_iter() {
95            self.add_attribute(key, value);
96        }
97    }
98
99    fn add_attribute<K, V>(&mut self, key: K, value: V)
100    where
101        K: Into<Key>,
102        V: Into<AnyValue>,
103    {
104        self.attributes.push(Some((key.into(), value.into())));
105    }
106
107    fn set_trace_context(
108        &mut self,
109        trace_id: TraceId,
110        span_id: SpanId,
111        trace_flags: Option<TraceFlags>,
112    ) {
113        self.trace_context = Some(TraceContext {
114            trace_id,
115            span_id,
116            trace_flags,
117        });
118    }
119}
120
121impl SdkLogRecord {
122    /// Crate only default constructor
123    pub(crate) fn new() -> Self {
124        SdkLogRecord {
125            event_name: None,
126            target: None,
127            timestamp: None,
128            observed_timestamp: None,
129            trace_context: None,
130            severity_text: None,
131            severity_number: None,
132            body: None,
133            attributes: LogRecordAttributes::default(),
134        }
135    }
136
137    /// Returns the event name
138    #[inline]
139    pub fn event_name(&self) -> Option<&'static str> {
140        self.event_name
141    }
142
143    /// Returns the target
144    #[inline]
145    pub fn target(&self) -> Option<&Cow<'static, str>> {
146        self.target.as_ref()
147    }
148
149    /// Returns the timestamp
150    #[inline]
151    pub fn timestamp(&self) -> Option<SystemTime> {
152        self.timestamp
153    }
154
155    /// Returns the observed timestamp
156    #[inline]
157    pub fn observed_timestamp(&self) -> Option<SystemTime> {
158        self.observed_timestamp
159    }
160
161    /// Returns the trace context
162    #[inline]
163    pub fn trace_context(&self) -> Option<&TraceContext> {
164        self.trace_context.as_ref()
165    }
166
167    /// Returns the severity text
168    #[inline]
169    pub fn severity_text(&self) -> Option<&'static str> {
170        self.severity_text
171    }
172
173    /// Returns the severity number
174    #[inline]
175    pub fn severity_number(&self) -> Option<Severity> {
176        self.severity_number
177    }
178
179    /// Returns the body
180    #[inline]
181    pub fn body(&self) -> Option<&AnyValue> {
182        self.body.as_ref()
183    }
184
185    /// Provides an iterator over the attributes.
186    #[inline]
187    pub fn attributes_iter(&self) -> impl Iterator<Item = &(Key, AnyValue)> {
188        self.attributes.iter().filter_map(|opt| opt.as_ref())
189    }
190
191    #[allow(dead_code)]
192    /// Returns the number of attributes in the `LogRecord`.
193    pub(crate) fn attributes_len(&self) -> usize {
194        self.attributes.len()
195    }
196
197    #[allow(dead_code)]
198    /// Checks if the `LogRecord` contains the specified attribute.
199    pub(crate) fn attributes_contains(&self, key: &Key, value: &AnyValue) -> bool {
200        self.attributes
201            .iter()
202            .flatten()
203            .any(|(k, v)| k == key && v == value)
204    }
205}
206
207/// TraceContext stores the trace context for logs that have an associated
208/// span.
209#[derive(Debug, Clone, PartialEq)]
210#[non_exhaustive]
211pub struct TraceContext {
212    /// Trace id
213    pub trace_id: TraceId,
214    /// Span Id
215    pub span_id: SpanId,
216    /// Trace flags
217    pub trace_flags: Option<TraceFlags>,
218}
219
220#[cfg(feature = "trace")]
221impl From<&SpanContext> for TraceContext {
222    fn from(span_context: &SpanContext) -> Self {
223        TraceContext {
224            trace_id: span_context.trace_id(),
225            span_id: span_context.span_id(),
226            trace_flags: Some(span_context.trace_flags()),
227        }
228    }
229}
230
231#[cfg(all(test, feature = "testing"))]
232mod tests {
233    use super::*;
234    use opentelemetry::logs::{AnyValue, LogRecord as _, Severity};
235    use opentelemetry::time::now;
236    use std::borrow::Cow;
237
238    #[test]
239    fn test_set_eventname() {
240        let mut log_record = SdkLogRecord::new();
241        log_record.set_event_name("test_event");
242        assert_eq!(log_record.event_name, Some("test_event"));
243    }
244
245    #[test]
246    fn test_set_target() {
247        let mut log_record = SdkLogRecord::new();
248        log_record.set_target("foo::bar");
249        assert_eq!(log_record.target, Some(Cow::Borrowed("foo::bar")));
250    }
251
252    #[test]
253    fn test_set_timestamp() {
254        let mut log_record = SdkLogRecord::new();
255        let now = now();
256        log_record.set_timestamp(now);
257        assert_eq!(log_record.timestamp, Some(now));
258    }
259
260    #[test]
261    fn test_set_observed_timestamp() {
262        let mut log_record = SdkLogRecord::new();
263        let now = now();
264        log_record.set_observed_timestamp(now);
265        assert_eq!(log_record.observed_timestamp, Some(now));
266    }
267
268    #[test]
269    fn test_set_severity_text() {
270        let mut log_record = SdkLogRecord::new();
271        log_record.set_severity_text("ERROR");
272        assert_eq!(log_record.severity_text, Some("ERROR"));
273    }
274
275    #[test]
276    fn test_set_severity_number() {
277        let mut log_record = SdkLogRecord::new();
278        let severity_number = Severity::Error;
279        log_record.set_severity_number(severity_number);
280        assert_eq!(log_record.severity_number, Some(Severity::Error));
281    }
282
283    #[test]
284    fn test_set_body() {
285        let mut log_record = SdkLogRecord::new();
286        let body = AnyValue::String("Test body".into());
287        log_record.set_body(body.clone());
288        assert_eq!(log_record.body, Some(body));
289    }
290
291    #[test]
292    fn test_set_attributes() {
293        let mut log_record = SdkLogRecord::new();
294        let attributes = vec![(Key::new("key"), AnyValue::String("value".into()))];
295        log_record.add_attributes(attributes.clone());
296        for (key, value) in attributes {
297            assert!(log_record.attributes_contains(&key, &value));
298        }
299    }
300
301    #[test]
302    fn test_set_attribute() {
303        let mut log_record = SdkLogRecord::new();
304        log_record.add_attribute("key", "value");
305        let key = Key::new("key");
306        let value = AnyValue::String("value".into());
307        assert!(log_record.attributes_contains(&key, &value));
308    }
309
310    #[test]
311    fn compare_trace_context() {
312        let trace_context = TraceContext {
313            trace_id: TraceId::from(1),
314            span_id: SpanId::from(1),
315            trace_flags: Some(TraceFlags::default()),
316        };
317
318        let trace_context_cloned = trace_context.clone();
319
320        assert_eq!(trace_context, trace_context_cloned);
321
322        let trace_context_different = TraceContext {
323            trace_id: TraceId::from(2),
324            span_id: SpanId::from(2),
325            trace_flags: Some(TraceFlags::default()),
326        };
327
328        assert_ne!(trace_context, trace_context_different);
329    }
330
331    #[test]
332    fn compare_log_record() {
333        let mut log_record = SdkLogRecord {
334            event_name: Some("test_event"),
335            target: Some(Cow::Borrowed("foo::bar")),
336            timestamp: Some(now()),
337            observed_timestamp: Some(now()),
338            severity_text: Some("ERROR"),
339            severity_number: Some(Severity::Error),
340            body: Some(AnyValue::String("Test body".into())),
341            attributes: LogRecordAttributes::new(),
342            trace_context: Some(TraceContext {
343                trace_id: TraceId::from(1),
344                span_id: SpanId::from(1),
345                trace_flags: Some(TraceFlags::default()),
346            }),
347        };
348        log_record.add_attribute(Key::new("key"), AnyValue::String("value".into()));
349
350        let log_record_cloned = log_record.clone();
351
352        assert_eq!(log_record, log_record_cloned);
353
354        let mut log_record_different = log_record.clone();
355        log_record_different.event_name = Some("different_event");
356
357        assert_ne!(log_record, log_record_different);
358    }
359
360    #[test]
361    fn compare_log_record_target_borrowed_eq_owned() {
362        let log_record_borrowed = SdkLogRecord {
363            event_name: Some("test_event"),
364            ..SdkLogRecord::new()
365        };
366
367        let log_record_owned = SdkLogRecord {
368            event_name: Some("test_event"),
369            ..SdkLogRecord::new()
370        };
371
372        assert_eq!(log_record_borrowed, log_record_owned);
373    }
374}