veecle-telemetry 0.1.0

Veecle OS telemetry
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
//! Telemetry protocol types and message definitions.
//!
//! This module defines the core data structures used for telemetry message exchange.
//! It includes message types for logging, tracing, and time synchronization, as well
//! as supporting types for execution tracking and attribute management.
//!
//! # Message Types
//!
//! The protocol supports several categories of telemetry messages:
//!
//! - **Log Messages** - Structured logging with severity levels and attributes
//! - **Tracing Messages** - Distributed tracing with spans, events, and links
//! - **Time Sync Messages** - Time synchronization between systems
//!
//! # Execution Tracking
//!
//! Each message is associated with an [`ExecutionId`] that uniquely identifies
//! the execution context.
//! This allows telemetry data from multiple executions to be correlated and analyzed separately.
//!
//! # Serialization
//!
//! All protocol types implement [`serde::Serialize`] and optionally [`serde::Deserialize`]
//! (when the `alloc` feature is enabled) for easy serialization to various formats.

#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::ops::Deref;

use serde::{Deserialize, Serialize};

use crate::SpanContext;
use crate::id::{SpanId, TraceId};
#[cfg(feature = "alloc")]
use crate::to_static::ToStatic;
use crate::types::{ListType, StringType, list_from_slice};
use crate::value::KeyValue;

/// A specialised form of [`list_from_slice`] for attributes.
pub fn attribute_list_from_slice<'a>(slice: &'a [KeyValue<'a>]) -> AttributeListType<'a> {
    list_from_slice::<KeyValue<'a>>(slice)
}

/// Type alias for a list of attributes.
pub type AttributeListType<'a> = ListType<'a, KeyValue<'a>>;

#[cfg(feature = "alloc")]
impl ToStatic for AttributeListType<'_> {
    type Static = AttributeListType<'static>;

    fn to_static(&self) -> Self::Static {
        self.iter()
            .map(|item| item.to_static())
            .collect::<Vec<_>>()
            .into()
    }
}

/// An Id uniquely identifying an execution.
///
/// An [`ExecutionId`] should never be re-used as it's used to collect metadata about the execution and to generate
/// [`TraceId`]s which need to be globally unique.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default, Serialize, Deserialize)]
pub struct ExecutionId(u128);

impl ExecutionId {
    /// Uses a random generator to generate the [`ExecutionId`].
    pub fn random(rng: &mut impl rand::Rng) -> Self {
        Self(rng.random())
    }

    /// Creates an [`ExecutionId`] from a raw value, extra care needs to be taken that this is not a constant value or
    /// re-used in any way.
    ///
    /// When possible prefer using [`ExecutionId::random`].
    pub const fn from_raw(raw: u128) -> Self {
        Self(raw)
    }
}

impl Deref for ExecutionId {
    type Target = u128;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

/// A telemetry message associated with a specific execution.
///
/// This structure wraps a telemetry message with its execution context,
/// allowing messages from different executions to be properly correlated.
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(feature = "alloc", derive(Deserialize))]
pub struct InstanceMessage<'a> {
    /// The execution ID this message belongs to
    pub execution: ExecutionId,

    /// The telemetry message content
    #[serde(borrow)]
    pub message: TelemetryMessage<'a>,
}

#[cfg(feature = "alloc")]
impl ToStatic for InstanceMessage<'_> {
    type Static = InstanceMessage<'static>;

    fn to_static(&self) -> Self::Static {
        InstanceMessage {
            execution: self.execution,
            message: self.message.to_static(),
        }
    }
}

/// An enumeration of all possible telemetry message types.
///
/// This enum represents the different categories of telemetry data that can be
/// collected and exported by the system.
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(feature = "alloc", derive(Deserialize))]
pub enum TelemetryMessage<'a> {
    /// A structured log message with severity and attributes
    Log(#[serde(borrow)] LogMessage<'a>),
    /// A time synchronization message for clock coordination
    TimeSync(TimeSyncMessage),
    /// A distributed tracing message (spans, events, links)
    Tracing(#[serde(borrow)] TracingMessage<'a>),
}

#[cfg(feature = "alloc")]
impl ToStatic for TelemetryMessage<'_> {
    type Static = TelemetryMessage<'static>;

    fn to_static(&self) -> Self::Static {
        match self {
            TelemetryMessage::Log(msg) => TelemetryMessage::Log(msg.to_static()),
            TelemetryMessage::TimeSync(msg) => TelemetryMessage::TimeSync(msg.clone()),
            TelemetryMessage::Tracing(msg) => TelemetryMessage::Tracing(msg.to_static()),
        }
    }
}

/// Log message severity levels.
///
/// These levels follow standard logging conventions, ordered from most verbose
/// to most critical.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum Severity {
    /// The "trace" level.
    ///
    /// Designates very low priority, often extremely verbose, information.
    Trace,
    /// The "debug" level.
    ///
    /// Designates lower priority information.
    Debug,
    /// The "info" level.
    ///
    /// Designates useful information.
    Info,
    /// The "warn" level.
    ///
    /// Designates hazardous situations.
    Warn,
    /// The "error" level.
    ///
    /// Designates very serious errors.
    Error,
    /// The "fatal" level.
    ///
    /// Designates critical failures that might crash the program.
    Fatal,
}

/// A structured log message with severity, timestamp, and attributes.
///
/// Log messages can be optionally correlated with traces by including trace and span IDs when available.
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(feature = "alloc", derive(Deserialize))]
pub struct LogMessage<'a> {
    /// Timestamp in nanoseconds since Unix epoch (or system start)
    pub time_unix_nano: u64,
    /// The severity level of this log message
    pub severity: Severity,

    /// The message body
    #[serde(borrow)]
    pub body: StringType<'a>,

    /// Key-value attributes providing additional context
    #[serde(borrow)]
    pub attributes: AttributeListType<'a>,

    /// Optional trace ID for correlation with traces
    pub trace_id: Option<TraceId>,
    /// Optional span ID for correlation with traces
    pub span_id: Option<SpanId>,
}

#[cfg(feature = "alloc")]
impl ToStatic for LogMessage<'_> {
    type Static = LogMessage<'static>;

    fn to_static(&self) -> Self::Static {
        LogMessage {
            time_unix_nano: self.time_unix_nano,
            severity: self.severity,
            body: self.body.to_static(),
            attributes: self.attributes.to_static(),
            trace_id: self.trace_id,
            span_id: self.span_id,
        }
    }
}

/// A time synchronization message for coordinating clocks between systems.
///
/// This message contains both local time and absolute time information,
/// allowing downstream systems to correlate local timestamps with real-world time.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TimeSyncMessage {
    /// Local timestamp in system-specific units
    pub local_timestamp: u64,
    /// Time since Unix epoch in nanoseconds
    pub since_epoch: u64,
}

/// Messages related to distributed tracing operations.
///
/// This enum encompasses all the different types of tracing messages that can be
/// generated during span lifecycle management and tracing operations.
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(feature = "alloc", derive(Deserialize))]
pub enum TracingMessage<'a> {
    /// A new span has been created
    CreateSpan(#[serde(borrow)] SpanCreateMessage<'a>),
    /// A span has been entered (made current)
    EnterSpan(SpanEnterMessage),
    /// A span has been exited (no longer current)
    ExitSpan(SpanExitMessage),
    /// A span has been closed (completed)
    CloseSpan(SpanCloseMessage),
    /// An event has been added to a span
    AddEvent(#[serde(borrow)] SpanAddEventMessage<'a>),
    /// A link has been added to a span
    AddLink(SpanAddLinkMessage),
    /// An attribute has been set on a span
    SetAttribute(#[serde(borrow)] SpanSetAttributeMessage<'a>),
}

#[cfg(feature = "alloc")]
impl ToStatic for TracingMessage<'_> {
    type Static = TracingMessage<'static>;

    fn to_static(&self) -> Self::Static {
        match self {
            TracingMessage::CreateSpan(msg) => TracingMessage::CreateSpan(msg.to_static()),
            TracingMessage::EnterSpan(msg) => TracingMessage::EnterSpan(*msg),
            TracingMessage::ExitSpan(msg) => TracingMessage::ExitSpan(*msg),
            TracingMessage::CloseSpan(msg) => TracingMessage::CloseSpan(*msg),
            TracingMessage::AddEvent(msg) => TracingMessage::AddEvent(msg.to_static()),
            TracingMessage::AddLink(msg) => TracingMessage::AddLink(*msg),
            TracingMessage::SetAttribute(msg) => TracingMessage::SetAttribute(msg.to_static()),
        }
    }
}

/// Message indicating the creation of a new span.
///
/// This message provides all the information needed to create a new span
/// in the trace, including its identity, timing, and initial attributes.
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(feature = "alloc", derive(Deserialize))]
pub struct SpanCreateMessage<'a> {
    /// The trace this span belongs to
    pub trace_id: TraceId,
    /// The unique identifier for this span
    pub span_id: SpanId,
    /// The parent span ID, if this is a child span
    pub parent_span_id: Option<SpanId>,

    /// The name of the span
    #[serde(borrow)]
    pub name: StringType<'a>,

    /// Timestamp when the span was started
    pub start_time_unix_nano: u64,

    /// Initial attributes attached to the span
    #[serde(borrow)]
    pub attributes: AttributeListType<'a>,
}

#[cfg(feature = "alloc")]
impl ToStatic for SpanCreateMessage<'_> {
    type Static = SpanCreateMessage<'static>;

    fn to_static(&self) -> Self::Static {
        SpanCreateMessage {
            trace_id: self.trace_id,
            span_id: self.span_id,
            parent_span_id: self.parent_span_id,
            name: self.name.to_static(),
            start_time_unix_nano: self.start_time_unix_nano,
            attributes: self.attributes.to_static(),
        }
    }
}

/// Message indicating a span has been entered.
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct SpanEnterMessage {
    /// The trace this span belongs to
    pub trace_id: TraceId,
    /// The span being entered
    pub span_id: SpanId,

    /// Timestamp when the span was entered
    pub time_unix_nano: u64,
}

/// Message indicating a span has been exited.
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct SpanExitMessage {
    /// The trace this span belongs to
    pub trace_id: TraceId,
    /// The span being exited
    pub span_id: SpanId,

    /// Timestamp when the span was exited
    pub time_unix_nano: u64,
}

/// Message indicating a span has been closed (completed).
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct SpanCloseMessage {
    /// The trace this span belongs to
    pub trace_id: TraceId,
    /// The span being closed
    pub span_id: SpanId,

    /// Timestamp when the span was closed
    pub end_time_unix_nano: u64,
}

/// Message indicating an attribute has been set on a span.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SpanSetAttributeMessage<'a> {
    /// The trace this span belongs to
    pub trace_id: TraceId,
    /// The span the attribute is being set on
    pub span_id: SpanId,

    /// The attribute being set
    #[serde(borrow)]
    pub attribute: KeyValue<'a>,
}

#[cfg(feature = "alloc")]
impl ToStatic for SpanSetAttributeMessage<'_> {
    type Static = SpanSetAttributeMessage<'static>;

    fn to_static(&self) -> Self::Static {
        SpanSetAttributeMessage {
            trace_id: self.trace_id,
            span_id: self.span_id,
            attribute: self.attribute.to_static(),
        }
    }
}

/// Message indicating an event has been added to a span.
#[derive(Clone, Debug, Serialize)]
#[cfg_attr(feature = "alloc", derive(Deserialize))]
pub struct SpanAddEventMessage<'a> {
    /// The trace this span belongs to
    pub trace_id: TraceId,
    /// The span the event is being added to
    pub span_id: SpanId,

    /// The name of the event
    #[serde(borrow)]
    pub name: StringType<'a>,
    /// Timestamp when the event occurred
    pub time_unix_nano: u64,

    /// Attributes providing additional context for the event
    #[serde(borrow)]
    pub attributes: AttributeListType<'a>,
}

#[cfg(feature = "alloc")]
impl ToStatic for SpanAddEventMessage<'_> {
    type Static = SpanAddEventMessage<'static>;

    fn to_static(&self) -> Self::Static {
        SpanAddEventMessage {
            trace_id: self.trace_id,
            span_id: self.span_id,
            name: self.name.to_static(),
            time_unix_nano: self.time_unix_nano,
            attributes: self.attributes.to_static(),
        }
    }
}

/// Message indicating a link has been added to a span.
///
/// Links connect spans across different traces, representing relationships
/// that are not parent-child hierarchies.
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct SpanAddLinkMessage {
    /// The trace this span belongs to
    pub trace_id: TraceId,
    /// The span the link is being added to
    pub span_id: SpanId,

    /// The span context being linked to
    pub link: SpanContext,
}

#[cfg(test)]
mod tests {
    #[cfg(feature = "alloc")]
    use alloc::string::String;

    use super::*;

    #[test]
    fn string_type_conversions() {
        let static_str: StringType<'static> = "static".into();

        let _event = SpanAddEventMessage {
            trace_id: TraceId(0),
            span_id: SpanId(0),
            name: static_str,
            time_unix_nano: 0,
            attributes: attribute_list_from_slice(&[]),
        };

        let borrowed_str: StringType = "borrowed".into();

        let _event = SpanAddEventMessage {
            trace_id: TraceId(0),
            span_id: SpanId(0),
            name: borrowed_str,
            time_unix_nano: 0,
            attributes: attribute_list_from_slice(&[]),
        };
    }

    #[cfg(any(feature = "std", feature = "alloc"))]
    #[test]
    fn string_type_with_owned_strings() {
        let string = String::from("owned");
        let owned: StringType<'static> = StringType::from(string);

        let _event = SpanAddEventMessage {
            trace_id: TraceId(0),
            span_id: SpanId(0),
            name: owned,
            time_unix_nano: 0,
            attributes: attribute_list_from_slice(&[]),
        };
    }

    #[cfg(feature = "alloc")]
    #[test]
    fn to_static_conversion() {
        use alloc::string::String;

        use crate::value::Value;

        // Create some data with non-static lifetime
        let borrowed_name_str = "test_span";
        let borrowed_name: StringType = borrowed_name_str.into();

        let owned_key = String::from("test_key");
        let owned_value = String::from("test_value");
        let attribute = KeyValue {
            key: owned_key.as_str().into(),
            value: Value::String(owned_value.as_str().into()),
        };

        let attributes = [attribute];
        let span_event = SpanAddEventMessage {
            trace_id: TraceId(0),
            span_id: SpanId(0),
            name: borrowed_name,
            time_unix_nano: 0,
            attributes: attribute_list_from_slice(&attributes),
        };

        let tracing_message = TracingMessage::AddEvent(span_event);
        let telemetry_message = TelemetryMessage::Tracing(tracing_message);
        let instance_message = InstanceMessage {
            execution: ExecutionId(999),
            message: telemetry_message,
        };

        let static_message: InstanceMessage<'static> = instance_message.to_static();

        // Verify the conversion worked - the static message should have the same data
        if let TelemetryMessage::Tracing(TracingMessage::AddEvent(span_event)) =
            &static_message.message
        {
            assert_eq!(span_event.name.as_ref(), "test_span");
        } else {
            panic!("Expected CreateSpan message");
        }
    }
}