beep_telemetry/
telemetry.rs1use opentelemetry::{global, trace::TracerProvider as _, KeyValue};
10use opentelemetry_sdk::{
11 metrics::{MeterProviderBuilder, PeriodicReader, SdkMeterProvider},
12 trace::{RandomIdGenerator, Sampler, SdkTracerProvider},
13 Resource,
14};
15use opentelemetry_semantic_conventions::{
16 attribute::{DEPLOYMENT_ENVIRONMENT_NAME, SERVICE_VERSION},
17 SCHEMA_URL,
18};
19use tracing_core::Level;
20use tracing_opentelemetry::{MetricsLayer, OpenTelemetryLayer};
21use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
22use tracing_subscriber::util::SubscriberInitExt;
23
24use crate::domain::models::errors::TelemetryError;
25use crate::domain::models::config::Config;
26
27fn resource() -> Resource {
29 Resource::builder()
30 .with_service_name(env!("CARGO_PKG_NAME"))
31 .with_schema_url(
32 [
33 KeyValue::new(SERVICE_VERSION, env!("CARGO_PKG_VERSION")),
34 KeyValue::new(DEPLOYMENT_ENVIRONMENT_NAME, "develop"),
35 ],
36 SCHEMA_URL,
37 )
38 .build()
39}
40
41fn init_meter_provider() -> Result<SdkMeterProvider, TelemetryError> {
43 let exporter = opentelemetry_otlp::MetricExporter::builder()
44 .with_tonic()
45 .with_temporality(opentelemetry_sdk::metrics::Temporality::default())
46 .build()
47 .map_err(|e| TelemetryError::OpenTelemetry(format!("failed to build OTLP metric exporter: {}", e)))?;
48
49 let reader = PeriodicReader::builder(exporter)
50 .with_interval(std::time::Duration::from_secs(30))
51 .build();
52
53 let stdout_reader =
54 PeriodicReader::builder(opentelemetry_stdout::MetricExporter::default()).build();
55
56 let meter_provider = MeterProviderBuilder::default()
57 .with_resource(resource())
58 .with_reader(reader)
59 .with_reader(stdout_reader)
60 .build();
61
62 global::set_meter_provider(meter_provider.clone());
63
64 Ok(meter_provider)
65}
66
67fn init_tracer_provider() -> Result<SdkTracerProvider, TelemetryError> {
69 let exporter = opentelemetry_otlp::SpanExporter::builder()
70 .with_tonic()
71 .build()
72 .map_err(|e| TelemetryError::OpenTelemetry(format!("failed to build OTLP span exporter: {}", e)))?;
73 Ok(SdkTracerProvider::builder()
74 .with_sampler(Sampler::ParentBased(Box::new(Sampler::TraceIdRatioBased(
75 1.0,
76 ))))
77 .with_id_generator(RandomIdGenerator::default())
78 .with_resource(resource())
79 .with_batch_exporter(exporter)
80 .build())
81}
82
83fn init_tracing_subscriber() -> Result<OtelGuard, TelemetryError> {
114 let tracer_provider = init_tracer_provider()?;
115 let meter_provider = init_meter_provider()?;
116
117 let tracer = tracer_provider.tracer("tracing-otel-subscriber");
118
119 tracing_subscriber::registry()
120 .with(tracing_subscriber::filter::LevelFilter::from_level(
121 Level::INFO,
122 ))
123 .with(tracing_subscriber::fmt::layer())
124 .with(MetricsLayer::new(meter_provider.clone()))
125 .with(OpenTelemetryLayer::new(tracer))
126 .init();
127
128 Ok(OtelGuard {
129 tracer_provider,
130 meter_provider,
131 })
132}
133
134pub struct OtelGuard {
135 tracer_provider: SdkTracerProvider,
136 meter_provider: SdkMeterProvider,
137}
138
139impl OtelGuard {
140 pub async fn shutdown(self) {
142 let tracer_provider = self.tracer_provider;
143 let meter_provider = self.meter_provider;
144
145 let _ = tokio::task::spawn_blocking(move || {
146 if let Err(err) = tracer_provider.shutdown() {
147 eprintln!("tracer shutdown error: {err:?}");
148 }
149 if let Err(err) = meter_provider.shutdown() {
150 eprintln!("meter shutdown error: {err:?}");
151 }
152 })
153 .await;
154 }
155}
156
157pub fn init(_config: &Config) -> Result<OtelGuard, TelemetryError> {
160 let guard = init_tracing_subscriber()?;
161
162 Ok(guard)
163}