1use opentelemetry_sdk::trace::TraceError;
2use std::time::Duration;
3
4#[derive(Debug, Clone)]
6pub struct ExporterConfig {
7 pub endpoint: String,
9 pub timeout: Duration,
11 pub use_tls: bool,
13 pub headers: Vec<(String, String)>,
15}
16
17impl Default for ExporterConfig {
18 fn default() -> Self {
19 Self {
20 endpoint: "http://localhost:4317".to_string(),
21 timeout: Duration::from_secs(10),
22 use_tls: false,
23 headers: Vec::new(),
24 }
25 }
26}
27
28pub enum TraceExporter {
30 #[cfg(feature = "otlp")]
31 Otlp,
32 Console,
34 Noop,
36}
37
38impl TraceExporter {
39 #[cfg(feature = "otlp")]
41 pub fn otlp(_config: ExporterConfig) -> Self {
42 Self::Otlp
43 }
44
45
46 pub fn console() -> Self {
48 Self::Console
49 }
50
51 pub fn noop() -> Self {
53 Self::Noop
54 }
55
56 pub async fn build(
58 self,
59 service_name: &str,
60 ) -> Result<opentelemetry_sdk::trace::Tracer, TraceError> {
61 use opentelemetry::KeyValue;
62 use opentelemetry::trace::TracerProvider;
63 use opentelemetry_sdk::Resource;
64
65 let resource = Resource::builder()
66 .with_service_name(service_name.to_string())
67 .with_attribute(KeyValue::new("service.version", env!("CARGO_PKG_VERSION")))
68 .build();
69
70 match self {
71 #[cfg(feature = "otlp")]
72 Self::Otlp => {
73 use opentelemetry_otlp::WithExportConfig;
74
75 opentelemetry_otlp::SpanExporter::builder()
76 .with_tonic()
77 .with_endpoint("http://localhost:4317")
78 .build()
79 .map_err(|e| TraceError::Other(Box::new(e)))
80 .and_then(|exporter| {
81 let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
82 .with_batch_exporter(exporter)
83 .with_resource(resource)
84 .build();
85 Ok(provider.tracer(service_name.to_string()))
86 })
87 }
88 Self::Console => {
89 let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
91 .with_resource(resource)
92 .build();
93
94 Ok(provider.tracer(service_name.to_string()))
95 }
96 Self::Noop => {
97 let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
99 .with_resource(resource)
100 .build();
101 Ok(provider.tracer(service_name.to_string()))
102 }
103 }
104 }
105}
106
107pub struct ExporterBuilder {
109 config: ExporterConfig,
110}
111
112impl ExporterBuilder {
113 pub fn new() -> Self {
115 Self {
116 config: ExporterConfig::default(),
117 }
118 }
119
120 pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
122 self.config.endpoint = endpoint.into();
123 self
124 }
125
126 pub fn with_timeout(mut self, timeout: Duration) -> Self {
128 self.config.timeout = timeout;
129 self
130 }
131
132 pub fn with_tls(mut self, use_tls: bool) -> Self {
134 self.config.use_tls = use_tls;
135 self
136 }
137
138 pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
140 self.config.headers.push((key.into(), value.into()));
141 self
142 }
143
144 #[cfg(feature = "otlp")]
146 pub fn build_otlp(self) -> TraceExporter {
147 TraceExporter::otlp(self.config)
148 }
149
150 pub fn build_console(self) -> TraceExporter {
152 TraceExporter::console()
153 }
154}
155
156impl Default for ExporterBuilder {
157 fn default() -> Self {
158 Self::new()
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_exporter_config() {
168 let config = ExporterConfig {
169 endpoint: "http://localhost:4318".to_string(),
170 timeout: Duration::from_secs(30),
171 use_tls: true,
172 ..Default::default()
173 };
174
175 assert_eq!(config.endpoint, "http://localhost:4318");
176 assert_eq!(config.timeout, Duration::from_secs(30));
177 assert!(config.use_tls);
178 }
179
180 #[test]
181 fn test_exporter_builder() {
182 let exporter = ExporterBuilder::new()
183 .with_endpoint("http://custom:4317")
184 .with_timeout(Duration::from_secs(20))
185 .with_tls(true)
186 .with_header("Authorization", "Bearer token")
187 .build_console();
188
189 match exporter {
190 TraceExporter::Console => {
191 }
193 _ => panic!("Expected console exporter"),
194 }
195 }
196}