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
7#[rustfmt::skip]
8pub use opentelemetry_otlp::{
9 OTEL_EXPORTER_OTLP_COMPRESSION,
10 OTEL_EXPORTER_OTLP_ENDPOINT,
11 OTEL_EXPORTER_OTLP_HEADERS,
12 OTEL_EXPORTER_OTLP_TIMEOUT,
13 OTEL_EXPORTER_OTLP_LOGS_COMPRESSION,
15 OTEL_EXPORTER_OTLP_LOGS_ENDPOINT,
16 OTEL_EXPORTER_OTLP_LOGS_HEADERS,
17 OTEL_EXPORTER_OTLP_LOGS_TIMEOUT,
18 OTEL_EXPORTER_OTLP_METRICS_COMPRESSION,
20 OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
21 OTEL_EXPORTER_OTLP_METRICS_HEADERS,
22 OTEL_EXPORTER_OTLP_METRICS_TIMEOUT,
23 OTEL_EXPORTER_OTLP_TRACES_COMPRESSION,
25 OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
26 OTEL_EXPORTER_OTLP_TRACES_HEADERS,
27 OTEL_EXPORTER_OTLP_TRACES_TIMEOUT,
28};
29pub use opentelemetry::{global, KeyValue};
30pub use opentelemetry_sdk::Resource;
31pub use opentelemetry_semantic_conventions::resource::*;
32
33use anyhow::Context;
34use opentelemetry::trace::TracerProvider;
35use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
36use opentelemetry_sdk::logs::LoggerProvider;
37use opentelemetry_sdk::metrics::SdkMeterProvider;
38use opentelemetry_sdk::propagation::TraceContextPropagator;
39use opentelemetry_sdk::trace::{self as sdktrace, BatchConfig};
40use opentelemetry_sdk::{resource, runtime};
41use opentelemetry_semantic_conventions::attribute;
42use spring::async_trait;
43use spring::config::env::Env;
44use spring::plugin::component::ComponentRef;
45use spring::plugin::{ComponentRegistry, MutableComponentRegistry};
46use spring::{app::AppBuilder, error::Result, plugin::Plugin};
47use std::time::Duration;
48use tracing_opentelemetry::{MetricsLayer, OpenTelemetryLayer};
49
50pub struct OpenTelemetryPlugin;
51
52#[async_trait]
53impl Plugin for OpenTelemetryPlugin {
54 fn immediately_build(&self, app: &mut AppBuilder) {
55 let resource = Self::get_resource_attr(app.get_env());
56 let resource = match app.get_component_ref::<Resource>() {
57 Some(r) => resource.merge(r),
58 None => resource,
59 };
60 let log_provider = Self::init_logs(resource.clone());
61 let meter_provider = Self::init_metrics(resource.clone());
62 let tracer = Self::init_tracer(resource);
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 |_| Box::new(Self::shutdown(meter_provider, log_provider)));
72 }
73
74 fn immediately(&self) -> bool {
75 true
76 }
77}
78
79impl OpenTelemetryPlugin {
80 fn init_logs(resource: Resource) -> LoggerProvider {
81 opentelemetry_otlp::new_pipeline()
82 .logging()
83 .with_exporter(opentelemetry_otlp::new_exporter().tonic())
84 .with_resource(resource)
85 .install_batch(runtime::Tokio)
86 .expect("build LogProvider failed")
87 }
88
89 fn init_metrics(resource: Resource) -> SdkMeterProvider {
90 let provider = opentelemetry_otlp::new_pipeline()
91 .metrics(runtime::Tokio)
92 .with_exporter(opentelemetry_otlp::new_exporter().tonic())
93 .with_resource(resource)
94 .build()
95 .expect("build MeterProvider failed");
96
97 global::set_meter_provider(provider.clone());
98 tracing::debug!("metrics provider installed");
99
100 provider
101 }
102
103 fn init_tracer(resource: Resource) -> sdktrace::Tracer {
104 global::set_text_map_propagator(TraceContextPropagator::new());
105 #[cfg(feature = "jaeger")]
106 global::set_text_map_propagator(opentelemetry_jaeger_propagator::Propagator::new());
107 #[cfg(feature = "zipkin")]
108 global::set_text_map_propagator(opentelemetry_zipkin::Propagator::new());
109
110 let provider = opentelemetry_otlp::new_pipeline()
111 .tracing()
112 .with_exporter(opentelemetry_otlp::new_exporter().tonic())
113 .with_trace_config(sdktrace::Config::default().with_resource(resource))
114 .with_batch_config(BatchConfig::default())
115 .install_batch(runtime::Tokio)
116 .expect("build TraceProvider failed");
117
118 let tracer = provider.tracer(env!("CARGO_PKG_NAME"));
119 global::set_tracer_provider(provider);
120 tracing::debug!("tracer provider installed");
121
122 tracer
123 }
124
125 fn get_resource_attr(env: Env) -> Resource {
126 Self::infra_resource().merge(&Self::app_resource(env))
127 }
128
129 fn app_resource(env: Env) -> Resource {
130 Resource::from_schema_url(
131 [KeyValue::new(
132 attribute::DEPLOYMENT_ENVIRONMENT_NAME,
133 format!("{:?}", env),
134 )],
135 opentelemetry_semantic_conventions::SCHEMA_URL,
136 )
137 }
138
139 fn infra_resource() -> Resource {
140 Resource::from_detectors(
141 Duration::from_secs(0),
142 vec![
143 #[cfg(feature = "more-resource")]
144 Box::new(opentelemetry_resource_detectors::HostResourceDetector::default()),
145 #[cfg(feature = "more-resource")]
146 Box::new(opentelemetry_resource_detectors::OsResourceDetector),
147 #[cfg(feature = "more-resource")]
148 Box::new(opentelemetry_resource_detectors::ProcessResourceDetector),
149 Box::new(resource::SdkProvidedResourceDetector),
150 Box::new(resource::TelemetryResourceDetector),
151 Box::new(resource::EnvResourceDetector::new()),
152 ],
153 )
154 }
155
156 async fn shutdown(
157 meter_provider: SdkMeterProvider,
158 log_provider: LoggerProvider,
159 ) -> Result<String> {
160 global::shutdown_tracer_provider();
161 meter_provider
162 .shutdown()
163 .context("shutdown meter provider failed")?;
164 log_provider
165 .shutdown()
166 .context("shutdown log provider failed")?;
167 Ok("OpenTelemetry shutdown successful".into())
168 }
169}
170
171pub trait ResourceConfigurator {
172 fn opentelemetry_attrs<KV>(&mut self, kvs: KV) -> &mut Self
173 where
174 KV: IntoIterator<Item = KeyValue>,
175 {
176 self.merge_resource(Resource::from_schema_url(
177 kvs,
178 opentelemetry_semantic_conventions::SCHEMA_URL,
179 ))
180 }
181
182 fn merge_resource(&mut self, resource: Resource) -> &mut Self;
183}
184
185impl ResourceConfigurator for AppBuilder {
186 fn merge_resource(&mut self, resource: Resource) -> &mut Self {
187 if let Some(old_resource) = self.get_component_ref::<Resource>() {
188 unsafe {
189 let raw_ptr = ComponentRef::into_raw(old_resource) as *mut Resource;
190 let old_resource = &mut *(raw_ptr);
191 std::ptr::write(raw_ptr, old_resource.merge(&resource));
192 }
193 self
194 } else {
195 self.add_component(resource)
196 }
197 }
198}