mockforge_tracing/
tracer.rs1use crate::exporter::ExporterType;
4use opentelemetry::global;
5use opentelemetry::KeyValue;
6use opentelemetry_otlp::WithExportConfig;
7use opentelemetry_sdk::trace::Tracer;
8use opentelemetry_sdk::Resource;
9use std::error::Error;
10use std::time::Duration;
11
12#[derive(Debug, Clone)]
14pub struct TracingConfig {
15 pub service_name: String,
17 pub exporter_type: ExporterType,
19 pub jaeger_endpoint: Option<String>,
21 pub otlp_endpoint: Option<String>,
23 pub sampling_rate: f64,
25 pub environment: String,
27 pub service_version: Option<String>,
29}
30
31impl Default for TracingConfig {
32 fn default() -> Self {
33 Self {
34 service_name: "mockforge".to_string(),
35 exporter_type: ExporterType::Jaeger,
36 jaeger_endpoint: Some("http://localhost:14268/api/traces".to_string()),
37 otlp_endpoint: None,
38 sampling_rate: 1.0,
39 environment: "development".to_string(),
40 service_version: None,
41 }
42 }
43}
44
45impl TracingConfig {
46 pub fn with_jaeger(service_name: String, endpoint: String) -> Self {
48 Self {
49 service_name,
50 exporter_type: ExporterType::Jaeger,
51 jaeger_endpoint: Some(endpoint),
52 otlp_endpoint: None,
53 ..Default::default()
54 }
55 }
56
57 pub fn with_otlp(service_name: String, endpoint: String) -> Self {
59 Self {
60 service_name,
61 exporter_type: ExporterType::Otlp,
62 jaeger_endpoint: None,
63 otlp_endpoint: Some(endpoint),
64 ..Default::default()
65 }
66 }
67
68 pub fn with_sampling_rate(mut self, rate: f64) -> Self {
70 self.sampling_rate = rate;
71 self
72 }
73
74 pub fn with_environment(mut self, env: String) -> Self {
76 self.environment = env;
77 self
78 }
79
80 pub fn with_service_version(mut self, version: String) -> Self {
82 self.service_version = Some(version);
83 self
84 }
85}
86
87pub fn init_tracer(config: TracingConfig) -> Result<Tracer, Box<dyn Error + Send + Sync>> {
89 match config.exporter_type {
90 ExporterType::Jaeger => init_jaeger_tracer(config),
91 ExporterType::Otlp => init_otlp_tracer(config),
92 }
93}
94
95fn init_jaeger_tracer(config: TracingConfig) -> Result<Tracer, Box<dyn Error + Send + Sync>> {
97 let endpoint = config.jaeger_endpoint.ok_or("Jaeger endpoint not configured")?;
98
99 let tracer = opentelemetry_jaeger::new_agent_pipeline()
100 .with_service_name(&config.service_name)
101 .with_endpoint(&endpoint)
102 .install_batch(opentelemetry_sdk::runtime::Tokio)?;
103
104 Ok(tracer)
105}
106
107fn init_otlp_tracer(config: TracingConfig) -> Result<Tracer, Box<dyn Error + Send + Sync>> {
109 let endpoint = config.otlp_endpoint.ok_or("OTLP endpoint not configured")?;
110
111 let mut resource_attrs = vec![
113 KeyValue::new("service.name", config.service_name.clone()),
114 KeyValue::new("deployment.environment", config.environment.clone()),
115 ];
116
117 if let Some(version) = config.service_version {
118 resource_attrs.push(KeyValue::new("service.version", version));
119 }
120
121 let resource = Resource::new(resource_attrs);
122
123 let exporter = opentelemetry_otlp::new_exporter()
125 .tonic()
126 .with_endpoint(&endpoint)
127 .with_timeout(Duration::from_secs(10));
128
129 let tracer = opentelemetry_otlp::new_pipeline()
131 .tracing()
132 .with_exporter(exporter)
133 .with_trace_config(
134 opentelemetry_sdk::trace::Config::default()
135 .with_resource(resource)
136 .with_sampler(opentelemetry_sdk::trace::Sampler::TraceIdRatioBased(
137 config.sampling_rate,
138 )),
139 )
140 .install_batch(opentelemetry_sdk::runtime::Tokio)?;
141
142 Ok(tracer)
143}
144
145pub fn shutdown_tracer() {
147 global::shutdown_tracer_provider();
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn test_default_config() {
156 let config = TracingConfig::default();
157 assert_eq!(config.service_name, "mockforge");
158 assert_eq!(config.sampling_rate, 1.0);
159 assert_eq!(config.environment, "development");
160 assert_eq!(config.exporter_type, ExporterType::Jaeger);
161 assert!(config.jaeger_endpoint.is_some());
162 assert!(config.otlp_endpoint.is_none());
163 }
164
165 #[test]
166 fn test_jaeger_config() {
167 let config = TracingConfig::with_jaeger(
168 "test-service".to_string(),
169 "http://custom:14268/api/traces".to_string(),
170 )
171 .with_sampling_rate(0.5)
172 .with_environment("staging".to_string())
173 .with_service_version("1.0.0".to_string());
174
175 assert_eq!(config.service_name, "test-service");
176 assert_eq!(config.exporter_type, ExporterType::Jaeger);
177 assert_eq!(config.jaeger_endpoint, Some("http://custom:14268/api/traces".to_string()));
178 assert_eq!(config.sampling_rate, 0.5);
179 assert_eq!(config.environment, "staging");
180 assert_eq!(config.service_version, Some("1.0.0".to_string()));
181 }
182
183 #[test]
184 fn test_otlp_config() {
185 let config = TracingConfig::with_otlp(
186 "test-service".to_string(),
187 "http://otel-collector:4317".to_string(),
188 )
189 .with_sampling_rate(0.8)
190 .with_environment("production".to_string())
191 .with_service_version("2.0.0".to_string());
192
193 assert_eq!(config.service_name, "test-service");
194 assert_eq!(config.exporter_type, ExporterType::Otlp);
195 assert_eq!(config.otlp_endpoint, Some("http://otel-collector:4317".to_string()));
196 assert!(config.jaeger_endpoint.is_none());
197 assert_eq!(config.sampling_rate, 0.8);
198 assert_eq!(config.environment, "production");
199 assert_eq!(config.service_version, Some("2.0.0".to_string()));
200 }
201}