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