Skip to main content

udb/runtime/
observability.rs

1use serde::{Deserialize, Serialize};
2#[cfg(feature = "runtime-logging")]
3use tracing_subscriber::EnvFilter;
4
5#[cfg(feature = "runtime-logging")]
6use super::pretty_log::PrettyLog;
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
9pub struct TraceContext {
10    pub trace_id: String,
11    pub span_id: String,
12    pub parent_span_id: String,
13    pub correlation_id: String,
14}
15
16#[cfg(feature = "runtime-logging")]
17pub fn init_observability() {
18    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
19
20    // JSON mode: opt-in via UDB_LOG_JSON=1 (e.g. in production / log aggregation).
21    // Default: human-readable pretty output.
22    let json = std::env::var("UDB_LOG_JSON")
23        .map(|v| match v.to_ascii_lowercase().as_str() {
24            "1" | "true" | "yes" | "on" => true,
25            "0" | "false" | "no" | "off" => false,
26            other => {
27                eprintln!(
28                    "unrecognized UDB_LOG_JSON value '{other}'; expected 1/0, true/false, yes/no, or on/off"
29                );
30                false
31            }
32        })
33        .unwrap_or(false);
34
35    if json {
36        let _ = tracing_subscriber::fmt()
37            .with_env_filter(filter)
38            .json()
39            .try_init();
40    } else {
41        let colors = std::env::var("NO_COLOR").is_err()
42            && std::env::var("UDB_NO_COLOR")
43                .map(|v| match v.to_ascii_lowercase().as_str() {
44                    "1" | "true" | "yes" | "on" => false,
45                    "0" | "false" | "no" | "off" => true,
46                    other => {
47                        eprintln!(
48                            "unrecognized UDB_NO_COLOR value '{other}'; expected 1/0, true/false, yes/no, or on/off"
49                        );
50                        true
51                    }
52                })
53                .unwrap_or(true);
54        let _ = tracing_subscriber::fmt()
55            .with_env_filter(filter)
56            .event_format(PrettyLog { colors })
57            .try_init();
58    }
59}
60
61#[cfg(not(feature = "runtime-logging"))]
62pub fn init_observability() {}
63
64impl TraceContext {
65    pub fn from_correlation(correlation_id: impl Into<String>) -> Self {
66        Self {
67            correlation_id: correlation_id.into(),
68            ..Self::default()
69        }
70    }
71
72    pub fn is_present(&self) -> bool {
73        !self.trace_id.is_empty()
74            || !self.span_id.is_empty()
75            || !self.parent_span_id.is_empty()
76            || !self.correlation_id.is_empty()
77    }
78
79    pub fn is_complete(&self) -> bool {
80        !self.trace_id.is_empty() && !self.span_id.is_empty()
81    }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
85pub struct MetricLabels {
86    pub tier: String,
87    pub backend: String,
88    pub resource_kind: String,
89    pub operation: String,
90}