spring_opentelemetry/
lib.rs1#![doc(html_favicon_url = "https://spring-rs.github.io/favicon.ico")]
3#![doc(html_logo_url = "https://spring-rs.github.io/logo.svg")]
4
5pub mod metrics;
6pub mod trace;
7pub mod util;
8
9use opentelemetry_otlp::{LogExporter, MetricExporter, SpanExporter};
10#[rustfmt::skip]
11pub use opentelemetry_otlp::{
12 OTEL_EXPORTER_OTLP_COMPRESSION,
13 OTEL_EXPORTER_OTLP_ENDPOINT,
14 OTEL_EXPORTER_OTLP_HEADERS,
15 OTEL_EXPORTER_OTLP_TIMEOUT,
16 OTEL_EXPORTER_OTLP_LOGS_COMPRESSION,
18 OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
19 OTEL_EXPORTER_OTLP_LOGS_HEADERS,
20 OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
21 OTEL_EXPORTER_OTLP_METRICS_COMPRESSION,
23 OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
24 OTEL_EXPORTER_OTLP_METRICS_HEADERS,
25 OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
26 OTEL_EXPORTER_OTLP_TRACES_COMPRESSION,
28 OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
29 OTEL_EXPORTER_OTLP_TRACES_HEADERS,
30 OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
31};
32pub use opentelemetry::{global, KeyValue};
33pub use opentelemetry_sdk::Resource;
34pub use opentelemetry_semantic_conventions::resource::*;
35
36use anyhow::Context;
37use opentelemetry::propagation::TextMapCompositePropagator;
38use opentelemetry::trace::TracerProvider;
39use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
40use opentelemetry_sdk::logs::SdkLoggerProvider;
41use opentelemetry_sdk::metrics::SdkMeterProvider;
42use opentelemetry_sdk::propagation::{BaggagePropagator, TraceContextPropagator};
43use opentelemetry_sdk::trace::SdkTracerProvider;
44use opentelemetry_semantic_conventions::attribute;
45use spring::plugin::component::ComponentRef;
46use spring::plugin::{ComponentRegistry, MutableComponentRegistry};
47use spring::{app::AppBuilder, error::Result, plugin::Plugin};
48use tracing_opentelemetry::{MetricsLayer, OpenTelemetryLayer};
49
50pub type KeyValues = Vec<KeyValue>;
52
53pub struct OpenTelemetryPlugin;
54
55impl Plugin for OpenTelemetryPlugin {
56 fn immediately_build(&self, app: &mut AppBuilder) {
57 let resource = Self::build_resource(app);
58 let log_provider = Self::init_logs(resource.clone());
59 let meter_provider = Self::init_metrics(resource.clone());
60 let tracer_provider = Self::init_tracer(resource);
61
62 let tracer = tracer_provider.tracer(env!("CARGO_PKG_NAME"));
63
64 let log_layer = OpenTelemetryTracingBridge::new(&log_provider);
65 let metric_layer = MetricsLayer::new(meter_provider.clone());
66 let trace_layer = OpenTelemetryLayer::new(tracer);
67
68 app.add_layer(trace_layer)
69 .add_layer(log_layer)
70 .add_layer(metric_layer)
71 .add_shutdown_hook(move |_| {
72 Box::new(Self::shutdown(
73 tracer_provider,
74 meter_provider,
75 log_provider,
76 ))
77 });
78 }
79
80 fn immediately(&self) -> bool {
81 true
82 }
83}
84
85impl OpenTelemetryPlugin {
86 fn init_logs(resource: Resource) -> SdkLoggerProvider {
87 let exporter = {
88 #[cfg(feature = "http")]
89 {
90 LogExporter::builder()
91 .with_http()
92 .build()
93 .expect("build http log exporter failed")
94 }
95
96 #[cfg(all(not(feature = "http"), feature = "grpc"))]
97 {
98 LogExporter::builder()
99 .with_tonic()
100 .build()
101 .expect("build grpc log exporter failed")
102 }
103
104 #[cfg(not(any(feature = "http", feature = "grpc")))]
105 compile_error!(
106 "You must enable either the 'http' or 'grpc' feature for the log exporter."
107 );
108 };
109 SdkLoggerProvider::builder()
110 .with_resource(resource)
111 .with_batch_exporter(exporter)
112 .build()
113 }
114
115 fn init_metrics(resource: Resource) -> SdkMeterProvider {
116 let exporter = {
117 #[cfg(feature = "http")]
118 {
119 MetricExporter::builder()
120 .with_http()
121 .build()
122 .expect("build http metric exporter failed")
123 }
124
125 #[cfg(all(not(feature = "http"), feature = "grpc"))]
126 {
127 MetricExporter::builder()
128 .with_tonic()
129 .build()
130 .expect("build grpc metric exporter failed")
131 }
132
133 #[cfg(not(any(feature = "http", feature = "grpc")))]
134 compile_error!(
135 "You must enable either the 'http' or 'grpc' feature for the log exporter."
136 );
137 };
138
139 let provider = SdkMeterProvider::builder()
140 .with_resource(resource)
141 .with_periodic_exporter(exporter)
142 .build();
143
144 global::set_meter_provider(provider.clone());
145 tracing::debug!("metrics provider installed");
146
147 provider
148 }
149
150 fn init_tracer(resource: Resource) -> SdkTracerProvider {
151 let exporter = {
152 #[cfg(feature = "http")]
153 {
154 SpanExporter::builder()
155 .with_http()
156 .build()
157 .expect("build http span exporter failed")
158 }
159
160 #[cfg(all(not(feature = "http"), feature = "grpc"))]
161 {
162 SpanExporter::builder()
163 .with_tonic()
164 .build()
165 .expect("build grpc span exporter failed")
166 }
167
168 #[cfg(not(any(feature = "http", feature = "grpc")))]
169 compile_error!(
170 "You must enable either the 'http' or 'grpc' feature for the log exporter."
171 );
172 };
173
174 global::set_text_map_propagator(TextMapCompositePropagator::new(vec![
175 Box::new(BaggagePropagator::new()),
176 Box::new(TraceContextPropagator::new()),
177 ]));
178 #[cfg(feature = "jaeger")]
179 global::set_text_map_propagator(opentelemetry_jaeger_propagator::Propagator::new());
180 #[cfg(feature = "zipkin")]
181 global::set_text_map_propagator(opentelemetry_zipkin::Propagator::new());
182
183 let provider = SdkTracerProvider::builder()
184 .with_resource(resource)
185 .with_batch_exporter(exporter)
186 .build();
187
188 global::set_tracer_provider(provider.clone());
189 tracing::debug!("tracer provider installed");
190
191 provider
192 }
193
194 fn build_resource(app: &AppBuilder) -> Resource {
195 let mut key_values = app.get_component::<KeyValues>().unwrap_or_default();
196 key_values.push(KeyValue::new(
197 attribute::DEPLOYMENT_ENVIRONMENT_NAME,
198 format!("{:?}", app.get_env()),
199 ));
200 let mut builder = Resource::builder();
201 #[cfg(feature = "more-resource")]
202 {
203 builder = builder.with_detectors(&[
204 Box::new(opentelemetry_resource_detectors::HostResourceDetector::default()),
205 Box::new(opentelemetry_resource_detectors::OsResourceDetector),
206 Box::new(opentelemetry_resource_detectors::ProcessResourceDetector),
207 ]);
208 }
209 builder = builder.with_attributes(key_values);
210 builder.build()
211 }
212
213 async fn shutdown(
214 tracer_provider: SdkTracerProvider,
215 meter_provider: SdkMeterProvider,
216 log_provider: SdkLoggerProvider,
217 ) -> Result<String> {
218 tracer_provider
219 .shutdown()
220 .context("shutdown tracer provider failed")?;
221 meter_provider
222 .shutdown()
223 .context("shutdown meter provider failed")?;
224 log_provider
225 .shutdown()
226 .context("shutdown log provider failed")?;
227 Ok("OpenTelemetry shutdown successful".into())
228 }
229}
230
231pub trait ResourceConfigurator {
232 fn opentelemetry_attrs<KV>(&mut self, kvs: KV) -> &mut Self
233 where
234 KV: IntoIterator<Item = KeyValue>;
235}
236
237impl ResourceConfigurator for AppBuilder {
238 fn opentelemetry_attrs<KV>(&mut self, kvs: KV) -> &mut Self
239 where
240 KV: IntoIterator<Item = KeyValue>,
241 {
242 if let Some(key_values) = self.get_component_ref::<KeyValues>() {
243 unsafe {
244 let raw_ptr = ComponentRef::into_raw(key_values);
245 let key_values = &mut *(raw_ptr as *mut KeyValues);
246 key_values.extend(kvs);
247 }
248 self
249 } else {
250 let mut key_values: KeyValues = vec![];
251 key_values.extend(kvs);
252 self.add_component(key_values)
253 }
254 }
255}