1use crate::{
2 exporter::{ExporterConfig, TraceExporter},
3 sampler::TraceSampler,
4};
5use opentelemetry::trace::TracerProvider;
6use opentelemetry_sdk::trace::{SdkTracerProvider, TraceError};
7use tracing_subscriber::{EnvFilter, Registry, layer::SubscriberExt, util::SubscriberInitExt};
8
9#[derive(Debug, Clone)]
11pub struct TracerConfig {
12 pub service_name: String,
14 pub service_version: String,
16 pub environment: String,
18 pub sampler: TraceSampler,
20 pub exporter_config: ExporterConfig,
22 pub enable_logging: bool,
24 pub log_filter: String,
26}
27
28impl Default for TracerConfig {
29 fn default() -> Self {
30 Self {
31 service_name: "revoke-service".to_string(),
32 service_version: env!("CARGO_PKG_VERSION").to_string(),
33 environment: "development".to_string(),
34 sampler: TraceSampler::always_on(),
35 exporter_config: ExporterConfig::default(),
36 enable_logging: true,
37 log_filter: "info".to_string(),
38 }
39 }
40}
41
42pub struct RevokeTracer {
44 provider: SdkTracerProvider,
45 config: TracerConfig,
46}
47
48impl RevokeTracer {
49 pub async fn new(config: TracerConfig) -> Result<Self, TraceError> {
51 let exporter = {
53 #[cfg(feature = "otlp")]
54 {
55 TraceExporter::otlp(config.exporter_config.clone())
56 }
57 #[cfg(not(feature = "otlp"))]
58 {
59 TraceExporter::console()
60 }
61 };
62
63 let _tracer = exporter.build(&config.service_name).await?;
65
66 use opentelemetry::KeyValue;
68 use opentelemetry_sdk::Resource;
69
70 let resource = Resource::builder()
71 .with_service_name(config.service_name.clone())
72 .with_attribute(KeyValue::new(
73 "service.version",
74 config.service_version.clone(),
75 ))
76 .build();
77
78 let provider = SdkTracerProvider::builder().with_resource(resource).build();
80
81 Ok(Self { provider, config })
82 }
83
84 pub fn install_global(self) -> Result<(), TraceError> {
86 opentelemetry::global::set_tracer_provider(self.provider.clone());
88
89 if self.config.enable_logging {
91 self.setup_tracing_subscriber()?;
92 }
93
94 Ok(())
95 }
96
97 fn setup_tracing_subscriber(&self) -> Result<(), TraceError> {
99 let tracer = self.provider.tracer("revoke-trace");
101 let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer);
102
103 let fmt_layer = tracing_subscriber::fmt::layer()
105 .with_target(true)
106 .with_thread_ids(true)
107 .with_level(true);
108
109 let filter = EnvFilter::try_from_default_env()
111 .unwrap_or_else(|_| EnvFilter::new(&self.config.log_filter));
112
113 Registry::default()
115 .with(filter)
116 .with(fmt_layer)
117 .with(otel_layer)
118 .try_init()
119 .map_err(|e| TraceError::Other(Box::new(e)))?;
120
121 Ok(())
122 }
123
124 pub fn tracer(&self, name: &str) -> opentelemetry_sdk::trace::Tracer {
126 self.provider.tracer(name.to_string())
127 }
128
129 pub fn force_flush(&self) -> Result<(), TraceError> {
131 let _ = self.provider.force_flush();
133 Ok(())
134 }
135
136 pub fn shutdown(self) -> Result<(), TraceError> {
138 drop(self.provider);
140 Ok(())
141 }
142}
143
144pub struct TracerBuilder {
146 config: TracerConfig,
147}
148
149impl TracerBuilder {
150 pub fn new(service_name: impl Into<String>) -> Self {
152 let mut config = TracerConfig::default();
153 config.service_name = service_name.into();
154
155 Self { config }
156 }
157
158 pub fn with_version(mut self, version: impl Into<String>) -> Self {
160 self.config.service_version = version.into();
161 self
162 }
163
164 pub fn with_environment(mut self, env: impl Into<String>) -> Self {
166 self.config.environment = env.into();
167 self
168 }
169
170 pub fn with_sampler(mut self, sampler: TraceSampler) -> Self {
172 self.config.sampler = sampler;
173 self
174 }
175
176 pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
178 self.config.exporter_config.endpoint = endpoint.into();
179 self
180 }
181
182 pub fn with_logging(mut self, enable: bool) -> Self {
184 self.config.enable_logging = enable;
185 self
186 }
187
188 pub fn with_log_filter(mut self, filter: impl Into<String>) -> Self {
190 self.config.log_filter = filter.into();
191 self
192 }
193
194 pub async fn build(self) -> Result<RevokeTracer, TraceError> {
196 RevokeTracer::new(self.config).await
197 }
198}
199
200#[macro_export]
202macro_rules! trace_span {
203 ($name:expr) => {
204 $crate::span::SpanBuilder::new($name).start()
205 };
206 ($name:expr, $($key:ident = $value:expr),*) => {
207 $crate::span::SpanBuilder::new($name)
208 $(.with_attribute(stringify!($key), $value))*
209 .start()
210 };
211}
212
213#[macro_export]
215macro_rules! trace_error {
216 ($span:expr, $error:expr) => {{
217 use $crate::span::SpanExt;
218 $span.record_exception(&$error);
219 $span.set_status($crate::span::SpanStatus::error($error.to_string()));
220 }};
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[tokio::test]
228 async fn test_tracer_builder() {
229 let tracer = TracerBuilder::new("test-service")
230 .with_version("1.0.0")
231 .with_environment("test")
232 .with_sampler(TraceSampler::always_off())
233 .with_endpoint("http://localhost:4317")
234 .with_logging(false)
235 .build()
236 .await;
237
238 assert!(tracer.is_ok());
239
240 let tracer = tracer.unwrap();
241 assert_eq!(tracer.config.service_name, "test-service");
242 assert_eq!(tracer.config.service_version, "1.0.0");
243 assert_eq!(tracer.config.environment, "test");
244 }
245
246 #[test]
247 fn test_trace_span_macro() {
248 let _span = tracing::info_span!("test_operation");
250 let _span_with_attrs =
252 tracing::info_span!("test_operation", method = "GET", path = "/api/test");
253 }
255}