Skip to content

Commit 16cf1ca

Browse files
author
bryn
committed
refactor: move activation logic into a separate file
1 parent 5216dbe commit 16cf1ca

File tree

3 files changed

+228
-138
lines changed

3 files changed

+228
-138
lines changed
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
//! Telemetry activation and reloading logic.
2+
//!
3+
//! This module contains all the complex logic for activating telemetry components
4+
//! and handling reloads while maintaining metric continuity.
5+
6+
use opentelemetry::trace::TracerProvider;
7+
use opentelemetry_sdk::trace::Builder;
8+
use tower::BoxError;
9+
10+
use crate::metrics::aggregation::MeterProviderType;
11+
use crate::metrics::filter::FilterMeterProvider;
12+
use crate::metrics::meter_provider_internal;
13+
use crate::plugins::telemetry::Telemetry;
14+
use crate::plugins::telemetry::config::MetricsCommon;
15+
use crate::plugins::telemetry::config::TracingCommon;
16+
use crate::plugins::telemetry::config_new::spans::Spans;
17+
use crate::plugins::telemetry::metrics::MetricsBuilder;
18+
use crate::plugins::telemetry::metrics::MetricsConfigurator;
19+
use crate::plugins::telemetry::metrics::prometheus::commit_prometheus;
20+
use crate::plugins::telemetry::reload::OPENTELEMETRY_TRACER_HANDLE;
21+
use crate::plugins::telemetry::tracing::TracingConfigurator;
22+
23+
pub(crate) const GLOBAL_TRACER_NAME: &str = "apollo-router";
24+
25+
/// Manages the activation state of telemetry components.
26+
///
27+
/// This struct holds all the meter providers and tracer providers that need to be
28+
/// managed during telemetry reloads. It ensures proper cleanup of old providers
29+
/// and atomic activation of new ones.
30+
#[derive(Default)]
31+
pub(crate) struct TelemetryActivation {
32+
pub(crate) tracer_provider: Option<opentelemetry_sdk::trace::TracerProvider>,
33+
// We have to have separate meter providers for prometheus metrics so that they don't get zapped on router reload.
34+
pub(crate) public_meter_provider: Option<FilterMeterProvider>,
35+
pub(crate) public_prometheus_meter_provider: Option<FilterMeterProvider>,
36+
pub(crate) private_meter_provider: Option<FilterMeterProvider>,
37+
pub(crate) private_realtime_meter_provider: Option<FilterMeterProvider>,
38+
// Store whether we should refresh the tracer provider during activation
39+
pub(crate) should_refresh_tracer: bool,
40+
pub(crate) is_active: bool,
41+
}
42+
43+
impl TelemetryActivation {
44+
/// Creates a new activation state from the built metrics and tracing components.
45+
pub(crate) fn from_builders(
46+
public_meter_provider_builder: opentelemetry_sdk::metrics::MeterProviderBuilder,
47+
apollo_meter_provider_builder: opentelemetry_sdk::metrics::MeterProviderBuilder,
48+
apollo_realtime_meter_provider_builder: opentelemetry_sdk::metrics::MeterProviderBuilder,
49+
prometheus_meter_provider: Option<opentelemetry_sdk::metrics::SdkMeterProvider>,
50+
tracer_provider: opentelemetry_sdk::trace::TracerProvider,
51+
should_refresh_tracer: bool,
52+
) -> Self {
53+
Self {
54+
tracer_provider: Some(tracer_provider),
55+
public_meter_provider: Some(FilterMeterProvider::public(
56+
public_meter_provider_builder.build(),
57+
)),
58+
public_prometheus_meter_provider: prometheus_meter_provider
59+
.map(FilterMeterProvider::public),
60+
private_meter_provider: Some(FilterMeterProvider::private(
61+
apollo_meter_provider_builder.build(),
62+
)),
63+
private_realtime_meter_provider: Some(FilterMeterProvider::private_realtime(
64+
apollo_realtime_meter_provider_builder.build(),
65+
)),
66+
should_refresh_tracer,
67+
is_active: false,
68+
}
69+
}
70+
71+
/// Reloads all meter providers, replacing the global meter providers with new ones
72+
/// and properly shutting down the old ones.
73+
pub(crate) fn reload_metrics(&mut self) {
74+
let meter_provider = meter_provider_internal();
75+
commit_prometheus();
76+
let mut old_meter_providers: [Option<FilterMeterProvider>; 4] = Default::default();
77+
78+
old_meter_providers[0] = meter_provider.set(
79+
MeterProviderType::PublicPrometheus,
80+
self.public_prometheus_meter_provider.take(),
81+
);
82+
83+
old_meter_providers[1] = meter_provider.set(
84+
MeterProviderType::Apollo,
85+
self.private_meter_provider.take(),
86+
);
87+
88+
old_meter_providers[2] = meter_provider.set(
89+
MeterProviderType::ApolloRealtime,
90+
self.private_realtime_meter_provider.take(),
91+
);
92+
93+
old_meter_providers[3] =
94+
meter_provider.set(MeterProviderType::Public, self.public_meter_provider.take());
95+
96+
Self::checked_meter_shutdown(old_meter_providers);
97+
}
98+
99+
/// Activates the tracer provider by setting it as the global tracer provider
100+
/// and updating the hot tracer reload handle. Only refreshes if needed.
101+
pub(crate) fn activate_tracer(&mut self) -> Result<(), BoxError> {
102+
if !self.should_refresh_tracer {
103+
::tracing::debug!("Skipping tracer refresh - tracing configuration unchanged");
104+
return Ok(());
105+
}
106+
107+
if let Some(hot_tracer) = OPENTELEMETRY_TRACER_HANDLE.get() {
108+
let tracer_provider = self
109+
.tracer_provider
110+
.take()
111+
.expect("must have new tracer_provider");
112+
113+
let tracer = tracer_provider
114+
.tracer_builder(GLOBAL_TRACER_NAME)
115+
.with_version(env!("CARGO_PKG_VERSION"))
116+
.build();
117+
hot_tracer.reload(tracer);
118+
119+
let last_provider = opentelemetry::global::set_tracer_provider(tracer_provider);
120+
121+
// Shut down the old provider in the background
122+
Telemetry::checked_global_tracer_shutdown(last_provider);
123+
124+
::tracing::debug!("Tracer provider refreshed due to configuration changes");
125+
}
126+
Ok(())
127+
}
128+
129+
/// Safely shuts down meter providers in the background to avoid blocking activation.
130+
pub(crate) fn checked_meter_shutdown(meters: [Option<FilterMeterProvider>; 4]) {
131+
for meter_provider in meters.into_iter().flatten() {
132+
Telemetry::checked_spawn_task(Box::new(move || {
133+
if let Err(e) = meter_provider.shutdown() {
134+
::tracing::error!(error = %e, "failed to shutdown meter provider")
135+
}
136+
}));
137+
}
138+
}
139+
}
140+
141+
/// Sets up a tracing exporter by applying its configuration to the tracer builder.
142+
///
143+
/// This is a generic function that works with any type implementing TracingConfigurator.
144+
/// It only applies the configuration if the exporter is enabled.
145+
pub(crate) fn setup_tracing<T: TracingConfigurator>(
146+
mut builder: Builder,
147+
configurator: &T,
148+
tracing_config: &TracingCommon,
149+
spans_config: &Spans,
150+
) -> Result<Builder, BoxError> {
151+
if configurator.enabled() {
152+
builder = configurator.apply(builder, tracing_config, spans_config)?;
153+
}
154+
Ok(builder)
155+
}
156+
157+
/// Sets up a metrics exporter by applying its configuration to the metrics builder.
158+
///
159+
/// This is a generic function that works with any type implementing MetricsConfigurator.
160+
/// It only applies the configuration if the exporter is enabled.
161+
pub(crate) fn setup_metrics_exporter<T: MetricsConfigurator>(
162+
mut builder: MetricsBuilder,
163+
configurator: &T,
164+
metrics_common: &MetricsCommon,
165+
) -> Result<MetricsBuilder, BoxError> {
166+
if configurator.enabled() {
167+
builder = configurator.apply(builder, metrics_common)?;
168+
}
169+
Ok(builder)
170+
}

0 commit comments

Comments
 (0)