cat_gateway/
telemetry.rs

1//! Open Telemetry support
2use std::sync::OnceLock;
3
4use opentelemetry::{InstrumentationScope, global, trace::TracerProvider};
5//use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
6use 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
16/// Return the common `Resource`
17fn get_resource() -> Resource {
18    static RESOURCE: OnceLock<Resource> = OnceLock::new();
19    RESOURCE.get_or_init(|| Resource::builder().build()).clone()
20}
21
22/// Telemetry Handle
23pub(crate) struct TelemetryGuard {
24    /// Logging handle
25    logging: SdkLoggerProvider,
26    /// Traces handle
27    traces: SdkTracerProvider,
28    /// Metrics handle
29    metrics: SdkMeterProvider,
30}
31
32impl TelemetryGuard {
33    /// Return Log Provider
34    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
79/// Initialize telemetry.
80pub(crate) fn init(log_level: LogLevel) -> anyhow::Result<TelemetryGuard> {
81    let telemetry_provider = TelemetryGuard::init()?;
82    // NOTE: OTEL log storage need to be configured with the batch exporter for them to work.
83    //       OTEL logs are currently disabled.
84    // let otel_log_layer = OpenTelemetryTracingBridge::new(&telemetry_provider.logging);
85    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    // Create a new tracing::Fmt layer to print the logs to stdout. It has a
95    // default filter of `info` level and above, and `debug` and above for logs
96    // from OpenTelemetry crates. The filter levels can be customized as needed.
97    let filter_layer = super::logger::build_reloadable_filter(log_level);
98    let fmt_layer = super::logger::build_fmt_layer();
99
100    // Initialize the tracing subscriber with the OpenTelemetry layer and the
101    // Fmt layer.
102    tracing_subscriber::registry()
103        .with(filter_layer)
104        .with(tracer_layer)
105        .with(metrics_layer)
106        //.with(otel_log_layer)
107        .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    // At this point Logs (OTel Logs and Fmt Logs) are initialized, which will
118    // allow internal-logs from Tracing/Metrics initializer to be captured.
119
120    Ok(telemetry_provider)
121}