1use opentelemetry::{global, KeyValue};
9use opentelemetry_otlp::WithExportConfig;
10use opentelemetry_sdk::{
11 propagation::TraceContextPropagator,
12 runtime,
13 trace::{self},
14 Resource,
15};
16use opentelemetry_semantic_conventions as semconv;
17use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry};
18
19#[derive(Clone, Debug)]
21pub struct TracingConfig {
22 pub service_name: String,
24
25 pub service_version: String,
27
28 pub environment: String,
30
31 pub otlp_endpoint: String,
33
34 pub json_logging: bool,
36
37 pub log_level: String,
39}
40
41impl Default for TracingConfig {
42 fn default() -> Self {
43 Self {
44 service_name: "uvb-api".to_string(),
45 service_version: env!("CARGO_PKG_VERSION").to_string(),
46 environment: "development".to_string(),
47 otlp_endpoint: "http://localhost:4317".to_string(),
48 json_logging: false,
49 log_level: "info".to_string(),
50 }
51 }
52}
53
54pub fn init_tracing(config: TracingConfig) -> Result<(), Box<dyn std::error::Error>> {
73 global::set_text_map_propagator(TraceContextPropagator::new());
75
76 let tracer = opentelemetry_otlp::new_pipeline()
78 .tracing()
79 .with_exporter(
80 opentelemetry_otlp::new_exporter()
81 .tonic()
82 .with_endpoint(&config.otlp_endpoint),
83 )
84 .with_trace_config(trace::config().with_resource(Resource::new(vec![
85 KeyValue::new(semconv::resource::SERVICE_NAME, config.service_name.clone()),
86 KeyValue::new(semconv::resource::SERVICE_VERSION, config.service_version),
87 KeyValue::new(
88 semconv::resource::DEPLOYMENT_ENVIRONMENT,
89 config.environment,
90 ),
91 ])))
92 .install_batch(runtime::Tokio)?;
93
94 let telemetry_layer = tracing_opentelemetry::layer().with_tracer(tracer);
96
97 let env_filter =
99 EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&config.log_level));
100
101 let subscriber = Registry::default().with(env_filter).with(telemetry_layer);
103
104 if config.json_logging {
106 let fmt_layer = tracing_subscriber::fmt::layer()
107 .json()
108 .with_current_span(true)
109 .with_span_list(true);
110
111 subscriber.with(fmt_layer).try_init()?;
112 } else {
113 let fmt_layer = tracing_subscriber::fmt::layer()
114 .with_target(true)
115 .with_level(true)
116 .with_thread_ids(true);
117
118 subscriber.with(fmt_layer).try_init()?;
119 }
120
121 Ok(())
122}
123
124pub fn shutdown_tracing() {
128 global::shutdown_tracer_provider();
129}
130
131pub mod attributes {
133 pub const TENANT_ID: &str = "uvb.tenant_id";
135
136 pub const USER_ID: &str = "uvb.user_id";
138
139 pub const APPLICATION_ID: &str = "uvb.application_id";
141
142 pub const TRANSACTION_ID: &str = "uvb.transaction_id";
144
145 pub const FACTOR_ID: &str = "uvb.factor_id";
147
148 pub const CHALLENGE_ID: &str = "uvb.challenge_id";
150
151 pub const ENROLLMENT_ID: &str = "uvb.enrollment_id";
153
154 pub const SESSION_ID: &str = "uvb.session_id";
156
157 pub const INTENT: &str = "uvb.intent";
159
160 pub const VERIFICATION_STATUS: &str = "uvb.verification_status";
162
163 pub const ASSURANCE_LEVEL: &str = "uvb.assurance_level";
165
166 pub const POLICY_ID: &str = "uvb.policy_id";
168}
169
170#[macro_export]
172macro_rules! span_with_tenant {
173 ($name:expr, $tenant_id:expr) => {
174 tracing::info_span!(
175 $name,
176 { $crate::attributes::TENANT_ID } = %$tenant_id
177 )
178 };
179}
180
181#[macro_export]
182macro_rules! span_with_transaction {
183 ($name:expr, $transaction_id:expr, $tenant_id:expr) => {
184 tracing::info_span!(
185 $name,
186 { $crate::attributes::TRANSACTION_ID } = %$transaction_id,
187 { $crate::attributes::TENANT_ID } = %$tenant_id
188 )
189 };
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn test_default_config() {
198 let config = TracingConfig::default();
199 assert_eq!(config.service_name, "uvb-api");
200 assert_eq!(config.environment, "development");
201 }
202
203 #[test]
204 fn test_config_builder() {
205 let config = TracingConfig {
206 service_name: "test-service".to_string(),
207 environment: "test".to_string(),
208 ..Default::default()
209 };
210
211 assert_eq!(config.service_name, "test-service");
212 assert_eq!(config.environment, "test");
213 }
214}