opentelemetry_sdk/logs/
record.rs

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