Skip to main content

plexor_core/
logging.rs

1// Copyright 2025 Alecks Gates
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7use crate::payload::{Payload, PayloadRaw};
8use std::sync::Arc;
9use uuid::Uuid;
10
11/// A standalone container for tracing context.
12/// Useful for initializing a trace before passing it to an Axon.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14pub struct TraceContext {
15    pub correlation_id: Uuid,
16    pub span_id: u64,
17    pub parent_id: Option<u64>,
18}
19
20impl TraceContext {
21    /// Start a new root trace.
22    pub fn new() -> Self {
23        let correlation_id = Uuid::now_v7();
24        let span_id = correlation_id.as_u128() as u64; // Deterministic first span
25        Self {
26            correlation_id,
27            span_id,
28            parent_id: None,
29        }
30    }
31
32    /// Create a child context from this one.
33    pub fn new_child(&self) -> Self {
34        Self {
35            correlation_id: self.correlation_id,
36            span_id: Uuid::now_v7().as_u128() as u64, // New random span ID
37            parent_id: Some(self.span_id),
38        }
39    }
40
41    /// Create a context from explicit parts.
42    pub fn from_parts(correlation_id: Uuid, span_id: u64, parent_id: Option<u64>) -> Self {
43        Self {
44            correlation_id,
45            span_id,
46            parent_id,
47        }
48    }
49}
50
51impl Default for TraceContext {
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57/// Trait for types that can provide tracing context for logging.
58pub trait LogTrace {
59    fn correlation_id(&self) -> Uuid;
60    fn span_id(&self) -> u64;
61    fn parent_id(&self) -> Option<u64>;
62    fn neuron_name(&self) -> String;
63
64    /// Returns the full trace context.
65    fn trace_context(&self) -> TraceContext {
66        TraceContext::from_parts(self.correlation_id(), self.span_id(), self.parent_id())
67    }
68
69    /// Returns a tracing span with the tracing fields attached at INFO level.
70    fn span_info(&self, name: &'static str) -> tracing::Span {
71        let correlation_id = self.correlation_id().to_string();
72        let span_id = self.span_id().to_string();
73        let parent_id = self
74            .parent_id()
75            .map(|id| id.to_string())
76            .unwrap_or_default();
77        let neuron = self.neuron_name();
78
79        tracing::info_span!(
80            target: "plexor::trace",
81            "plexor",
82            log_name = name,
83            correlation_id = %correlation_id,
84            span_id = %span_id,
85            parent_id = %parent_id,
86            neuron = %neuron
87        )
88    }
89
90    /// Returns a tracing span with the tracing fields attached at DEBUG level.
91    fn span_debug(&self, name: &'static str) -> tracing::Span {
92        let correlation_id = self.correlation_id().to_string();
93        let span_id = self.span_id().to_string();
94        let parent_id = self
95            .parent_id()
96            .map(|id| id.to_string())
97            .unwrap_or_default();
98        let neuron = self.neuron_name();
99
100        tracing::debug_span!(
101            target: "plexor::trace",
102            "plexor",
103            log_name = name,
104            correlation_id = %correlation_id,
105            span_id = %span_id,
106            parent_id = %parent_id,
107            neuron = %neuron
108        )
109    }
110}
111
112impl LogTrace for TraceContext {
113    fn correlation_id(&self) -> Uuid {
114        self.correlation_id
115    }
116    fn span_id(&self) -> u64 {
117        self.span_id
118    }
119    fn parent_id(&self) -> Option<u64> {
120        self.parent_id
121    }
122    fn neuron_name(&self) -> String {
123        "app".to_string()
124    }
125    fn trace_context(&self) -> TraceContext {
126        *self
127    }
128}
129
130impl LogTrace for Uuid {
131    fn correlation_id(&self) -> Uuid {
132        Uuid::nil()
133    }
134    fn span_id(&self) -> u64 {
135        0
136    }
137    fn parent_id(&self) -> Option<u64> {
138        None
139    }
140    fn neuron_name(&self) -> String {
141        "none".to_string()
142    }
143
144    fn span_info(&self, name: &'static str) -> tracing::Span {
145        tracing::info_span!(
146            target: "plexor::trace",
147            "plexor",
148            log_name = name,
149            ganglion_id = %self
150        )
151    }
152
153    fn span_debug(&self, name: &'static str) -> tracing::Span {
154        tracing::debug_span!(
155            target: "plexor::trace",
156            "plexor",
157            log_name = name,
158            ganglion_id = %self
159        )
160    }
161}
162
163impl<T, C> LogTrace for Payload<T, C>
164where
165    T: Send + Sync + 'static,
166    C: crate::codec::Codec<T> + crate::codec::CodecName + Send + Sync + 'static,
167{
168    fn correlation_id(&self) -> Uuid {
169        self.trace.correlation_id
170    }
171    fn span_id(&self) -> u64 {
172        self.trace.span_id
173    }
174    fn parent_id(&self) -> Option<u64> {
175        self.trace.parent_id
176    }
177    fn neuron_name(&self) -> String {
178        self.neuron.name()
179    }
180    fn trace_context(&self) -> TraceContext {
181        self.trace
182    }
183}
184
185impl<T, C> LogTrace for PayloadRaw<T, C>
186where
187    T: Send + Sync + 'static,
188    C: crate::codec::Codec<T> + crate::codec::CodecName + Send + Sync + 'static,
189{
190    fn correlation_id(&self) -> Uuid {
191        self.trace.correlation_id
192    }
193    fn span_id(&self) -> u64 {
194        self.trace.span_id
195    }
196    fn parent_id(&self) -> Option<u64> {
197        self.trace.parent_id
198    }
199    fn neuron_name(&self) -> String {
200        self.neuron
201            .as_ref()
202            .map(|n| n.name())
203            .unwrap_or_else(|| "unknown".to_string())
204    }
205    fn trace_context(&self) -> TraceContext {
206        self.trace
207    }
208}
209
210impl LogTrace for crate::erasure::payload::SimplePayloadRawErased {
211    fn correlation_id(&self) -> Uuid {
212        self.trace.correlation_id
213    }
214    fn span_id(&self) -> u64 {
215        self.trace.span_id
216    }
217    fn parent_id(&self) -> Option<u64> {
218        self.trace.parent_id
219    }
220    fn neuron_name(&self) -> String {
221        self.neuron_name.clone()
222    }
223    fn trace_context(&self) -> TraceContext {
224        self.trace
225    }
226}
227
228impl<T, C> LogTrace for crate::erasure::payload::PayloadErasedWrapper<T, C>
229where
230    T: Send + Sync + 'static,
231    C: crate::codec::Codec<T> + crate::codec::CodecName + Send + Sync + 'static,
232{
233    fn correlation_id(&self) -> Uuid {
234        self.get_typed_payload().trace.correlation_id
235    }
236    fn span_id(&self) -> u64 {
237        self.get_typed_payload().trace.span_id
238    }
239    fn parent_id(&self) -> Option<u64> {
240        self.get_typed_payload().trace.parent_id
241    }
242    fn neuron_name(&self) -> String {
243        self.get_typed_payload().neuron.name()
244    }
245    fn trace_context(&self) -> TraceContext {
246        self.get_typed_payload().trace
247    }
248}
249
250impl<T, C> LogTrace for crate::erasure::payload::PayloadRawErasedWrapper<T, C>
251where
252    T: Send + Sync + 'static,
253    C: crate::codec::Codec<T> + crate::codec::CodecName + Send + Sync + 'static,
254{
255    fn correlation_id(&self) -> Uuid {
256        self.get_payload_raw().trace.correlation_id
257    }
258    fn span_id(&self) -> u64 {
259        self.get_payload_raw().trace.span_id
260    }
261    fn parent_id(&self) -> Option<u64> {
262        self.get_payload_raw().trace.parent_id
263    }
264    fn neuron_name(&self) -> String {
265        self.get_payload_raw()
266            .neuron
267            .as_ref()
268            .map(|n| n.name())
269            .unwrap_or_else(|| "unknown".to_string())
270    }
271    fn trace_context(&self) -> TraceContext {
272        self.get_payload_raw().trace
273    }
274}
275
276impl<P: LogTrace + ?Sized> LogTrace for Arc<P> {
277    fn correlation_id(&self) -> Uuid {
278        (**self).correlation_id()
279    }
280    fn span_id(&self) -> u64 {
281        (**self).span_id()
282    }
283    fn parent_id(&self) -> Option<u64> {
284        (**self).parent_id()
285    }
286    fn neuron_name(&self) -> String {
287        (**self).neuron_name()
288    }
289}