1use std::sync::OnceLock;
3
4use opentelemetry::{InstrumentationScope, global, trace::TracerProvider};
5use opentelemetry_otlp::{LogExporter, MetricExporter, SpanExporter};
7use opentelemetry_sdk::{
8 Resource, logs::SdkLoggerProvider, metrics::SdkMeterProvider, trace::SdkTracerProvider,
9};
10use tracing::{error, info, level_filters::LevelFilter};
11use tracing_opentelemetry::MetricsLayer;
12use tracing_subscriber::prelude::*;
13
14use crate::logger::LogLevel;
15
16fn get_resource() -> Resource {
18 static RESOURCE: OnceLock<Resource> = OnceLock::new();
19 RESOURCE.get_or_init(|| Resource::builder().build()).clone()
20}
21
22pub(crate) struct TelemetryGuard {
24 logging: SdkLoggerProvider,
26 traces: SdkTracerProvider,
28 metrics: SdkMeterProvider,
30}
31
32impl TelemetryGuard {
33 fn init() -> anyhow::Result<Self> {
35 let exporter = LogExporter::builder().with_tonic().build()?;
36 let logging = SdkLoggerProvider::builder()
37 .with_resource(get_resource())
38 .with_batch_exporter(exporter)
39 .build();
40 let exporter = SpanExporter::builder().with_tonic().build()?;
41 let traces = SdkTracerProvider::builder()
42 .with_resource(get_resource())
43 .with_batch_exporter(exporter)
44 .build();
45
46 global::set_tracer_provider(traces.clone());
47
48 let exporter = MetricExporter::builder().with_tonic().build()?;
49 let metrics = SdkMeterProvider::builder()
50 .with_periodic_exporter(exporter)
51 .with_resource(get_resource())
52 .build();
53
54 global::set_meter_provider(metrics.clone());
55
56 Ok(Self {
57 logging,
58 traces,
59 metrics,
60 })
61 }
62}
63
64impl Drop for TelemetryGuard {
65 fn drop(&mut self) {
66 info!("dropping telemetry providers");
67 if let Err(e) = self.logging.shutdown() {
68 error!("logger provider did not shutdown: {e}");
69 }
70 if let Err(e) = self.traces.shutdown() {
71 error!("tracer provider did not shutdown: {e}");
72 }
73 if let Err(e) = self.metrics.shutdown() {
74 error!("metrics provider did not shutdown: {e}");
75 }
76 }
77}
78
79pub(crate) fn init(log_level: LogLevel) -> anyhow::Result<TelemetryGuard> {
81 let telemetry_provider = TelemetryGuard::init()?;
82 let scope = InstrumentationScope::builder("cat-gateway telemetry")
86 .with_version("1.0")
87 .build();
88
89 let tracer = telemetry_provider.traces.tracer_with_scope(scope);
90 let tracer_layer = tracing_opentelemetry::layer().with_tracer(tracer.clone());
91
92 let metrics_layer = MetricsLayer::new(telemetry_provider.metrics.clone());
93
94 let filter_layer = super::logger::build_reloadable_filter(log_level);
98 let fmt_layer = super::logger::build_fmt_layer();
99
100 tracing_subscriber::registry()
103 .with(filter_layer)
104 .with(tracer_layer)
105 .with(metrics_layer)
106 .with(fmt_layer)
108 .with(
109 tracing_subscriber::EnvFilter::builder()
110 .with_default_directive(LevelFilter::DEBUG.into())
111 .from_env_lossy(),
112 )
113 .init();
114
115 super::logger::post_init(log_level);
116
117 Ok(telemetry_provider)
121}