Skip to main content

redline_core/
record.rs

1use alloc::{format, string::String, vec::Vec};
2use core::{fmt, str};
3use smallvec::SmallVec;
4use tracing_core::{
5    Event, Level,
6    field::{Field, Visit},
7    span,
8};
9
10/// UTF-8 string storage optimized for short values.
11#[derive(Clone, Default, PartialEq, Eq)]
12pub struct InlineString(SmallVec<[u8; 24]>);
13
14impl InlineString {
15    /// Returns the value as `&str`.
16    pub fn as_str(&self) -> &str {
17        str::from_utf8(&self.0).expect("inline string must always contain valid utf-8")
18    }
19}
20
21impl From<&str> for InlineString {
22    fn from(value: &str) -> Self {
23        Self(SmallVec::from_slice(value.as_bytes()))
24    }
25}
26
27impl From<String> for InlineString {
28    fn from(value: String) -> Self {
29        Self(SmallVec::from_slice(value.as_bytes()))
30    }
31}
32
33impl fmt::Debug for InlineString {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        fmt::Debug::fmt(self.as_str(), f)
36    }
37}
38
39impl fmt::Display for InlineString {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        f.write_str(self.as_str())
42    }
43}
44
45/// Owned field value captured from a `tracing` event or span.
46#[derive(Clone, Debug, PartialEq)]
47pub enum FieldValue {
48    Bool(bool),
49    I64(i64),
50    U64(u64),
51    I128(i128),
52    U128(u128),
53    F64(f64),
54    Str(InlineString),
55    Bytes(Vec<u8>),
56    Debug(InlineString),
57}
58
59/// One named structured field.
60#[derive(Clone, Debug, PartialEq)]
61pub struct OwnedField {
62    pub name: &'static str,
63    pub value: FieldValue,
64}
65
66/// Small-vector field storage used by records and spans.
67pub type OwnedFields = SmallVec<[OwnedField; 8]>;
68
69/// Unix timestamp used by `redline`.
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub struct Timestamp {
72    /// Whole seconds since the Unix epoch.
73    pub unix_seconds: u64,
74    /// Nanoseconds within `unix_seconds`.
75    pub subsec_nanos: u32,
76}
77
78impl Timestamp {
79    /// Creates a timestamp from seconds and nanoseconds.
80    pub const fn new(unix_seconds: u64, subsec_nanos: u32) -> Self {
81        Self {
82            unix_seconds,
83            subsec_nanos,
84        }
85    }
86
87    /// Returns the timestamp as Unix nanoseconds.
88    pub const fn unix_nanos(&self) -> u128 {
89        (self.unix_seconds as u128) * 1_000_000_000 + self.subsec_nanos as u128
90    }
91}
92
93/// Snapshot of one span included in an encoded record.
94#[derive(Clone, Debug, PartialEq)]
95pub struct SpanSnapshot {
96    pub id: u64,
97    pub metadata_id: u32,
98    pub name: &'static str,
99    pub target: &'static str,
100    pub level: Level,
101    pub fields: OwnedFields,
102}
103
104/// Owned event record ready for encoding.
105#[derive(Clone, Debug, PartialEq)]
106pub struct OwnedRecord {
107    pub timestamp: Timestamp,
108    pub metadata_id: u32,
109    pub name: &'static str,
110    pub target: &'static str,
111    pub level: Level,
112    pub fields: OwnedFields,
113    pub current_span: Option<SpanSnapshot>,
114    pub spans: SmallVec<[SpanSnapshot; 4]>,
115}
116
117impl OwnedRecord {
118    /// Captures an event into an owned record.
119    pub fn from_event(
120        timestamp: Timestamp,
121        metadata_id: u32,
122        event: &Event<'_>,
123        current_span: Option<SpanSnapshot>,
124        spans: SmallVec<[SpanSnapshot; 4]>,
125    ) -> Self {
126        let mut capture = FieldCapture::default();
127        event.record(&mut capture);
128        let metadata = event.metadata();
129        Self {
130            timestamp,
131            metadata_id,
132            name: metadata.name(),
133            target: metadata.target(),
134            level: *metadata.level(),
135            fields: capture.finish(),
136            current_span,
137            spans,
138        }
139    }
140}
141
142/// `tracing` field visitor that captures owned values.
143#[derive(Default)]
144pub struct FieldCapture {
145    fields: OwnedFields,
146}
147
148impl FieldCapture {
149    /// Finishes capture and returns the collected fields.
150    pub fn finish(self) -> OwnedFields {
151        self.fields
152    }
153
154    fn push(&mut self, field: &Field, value: FieldValue) {
155        self.fields.push(OwnedField {
156            name: field.name(),
157            value,
158        });
159    }
160}
161
162impl Visit for FieldCapture {
163    fn record_f64(&mut self, field: &Field, value: f64) {
164        self.push(field, FieldValue::F64(value));
165    }
166
167    fn record_i64(&mut self, field: &Field, value: i64) {
168        self.push(field, FieldValue::I64(value));
169    }
170
171    fn record_u64(&mut self, field: &Field, value: u64) {
172        self.push(field, FieldValue::U64(value));
173    }
174
175    fn record_i128(&mut self, field: &Field, value: i128) {
176        self.push(field, FieldValue::I128(value));
177    }
178
179    fn record_u128(&mut self, field: &Field, value: u128) {
180        self.push(field, FieldValue::U128(value));
181    }
182
183    fn record_bool(&mut self, field: &Field, value: bool) {
184        self.push(field, FieldValue::Bool(value));
185    }
186
187    fn record_str(&mut self, field: &Field, value: &str) {
188        self.push(field, FieldValue::Str(value.into()));
189    }
190
191    fn record_bytes(&mut self, field: &Field, value: &[u8]) {
192        self.push(field, FieldValue::Bytes(value.to_vec()));
193    }
194
195    fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
196        self.push(field, FieldValue::Debug(format!("{value:?}").into()));
197    }
198}
199
200/// Captures fields from span attributes.
201pub fn capture_span_fields(attributes: &span::Attributes<'_>) -> OwnedFields {
202    let mut capture = FieldCapture::default();
203    attributes.record(&mut capture);
204    capture.finish()
205}
206
207/// Captures fields from a span record update.
208pub fn capture_record_fields(values: &span::Record<'_>) -> OwnedFields {
209    let mut capture = FieldCapture::default();
210    values.record(&mut capture);
211    capture.finish()
212}
213
214/// Applies field updates, replacing existing values with the same name.
215pub fn merge_fields(existing: &mut OwnedFields, updates: OwnedFields) {
216    for update in updates {
217        if let Some(slot) = existing.iter_mut().find(|field| field.name == update.name) {
218            slot.value = update.value;
219        } else {
220            existing.push(update);
221        }
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228    use smallvec::smallvec;
229
230    #[test]
231    fn merge_fields_overwrites_existing_keys() {
232        let mut fields = smallvec![OwnedField {
233            name: "message",
234            value: FieldValue::Str("before".into()),
235        }];
236        merge_fields(
237            &mut fields,
238            smallvec![OwnedField {
239                name: "message",
240                value: FieldValue::Str("after".into()),
241            }],
242        );
243
244        assert_eq!(fields.len(), 1);
245        assert_eq!(fields[0].value, FieldValue::Str("after".into()));
246    }
247}