uhg_custom_appollo_roouter/plugins/telemetry/
span_factory.rs1use schemars::JsonSchema;
2use serde::Deserialize;
3use tracing::error_span;
4use tracing::info_span;
5
6use crate::context::OPERATION_NAME;
7use crate::plugins::telemetry::Telemetry;
8use crate::plugins::telemetry::consts::REQUEST_SPAN_NAME;
9use crate::plugins::telemetry::consts::ROUTER_SPAN_NAME;
10use crate::plugins::telemetry::consts::SUBGRAPH_SPAN_NAME;
11use crate::plugins::telemetry::consts::SUPERGRAPH_SPAN_NAME;
12use crate::services::SubgraphRequest;
13use crate::services::SupergraphRequest;
14use crate::tracer::TraceId;
15use crate::uplink::license_enforcement::LICENSE_EXPIRED_SHORT_MESSAGE;
16use crate::uplink::license_enforcement::LicenseState;
17
18#[derive(Debug, Copy, Clone, Deserialize, JsonSchema, Default, Eq, PartialEq)]
19#[serde(rename_all = "snake_case")]
21pub(crate) enum SpanMode {
22 #[default]
24 Deprecated,
25 SpecCompliant,
27}
28
29impl SpanMode {
30 pub(crate) fn create_request<B>(
31 &self,
32 request: &http::Request<B>,
33 license_state: LicenseState,
34 ) -> ::tracing::span::Span {
35 let (azure_region, consumer_name, role_id, correlation_id, cid) = crate::uhg_custom::get_uhg_labels(Some(request.headers()), None);
37
38 match self {
39 SpanMode::Deprecated => {
40 if matches!(
41 license_state,
42 LicenseState::LicensedWarn | LicenseState::LicensedHalt
43 ) {
44 error_span!(
45 REQUEST_SPAN_NAME,
46 "consumerName" = consumer_name,
48 "roles" = role_id,
49 "correlationId" = correlation_id,
50 "cid" = cid,
51 "azure_region" = azure_region,
52 "http.method" = %request.method(),
54 "http.request.method" = %request.method(),
55 "http.route" = %request.uri().path(),
56 "http.flavor" = ?request.version(),
57 "http.status" = 500, "otel.name" = ::tracing::field::Empty,
59 "otel.kind" = "SERVER",
60 "graphql.operation.name" = ::tracing::field::Empty,
61 "graphql.operation.type" = ::tracing::field::Empty,
62 "apollo_router.license" = LICENSE_EXPIRED_SHORT_MESSAGE,
63 "apollo_private.request" = true,
64 )
65 } else {
66 info_span!(
67 REQUEST_SPAN_NAME,
68 "consumerName" = consumer_name,
70 "roles" = role_id,
71 "correlationId" = correlation_id,
72 "cid" = cid,
73 "azure_region" = azure_region,
74 "http.method" = %request.method(),
76 "http.request.method" = %request.method(),
77 "http.route" = %request.uri().path(),
78 "http.flavor" = ?request.version(),
79 "otel.name" = ::tracing::field::Empty,
80 "otel.kind" = "SERVER",
81 "graphql.operation.name" = ::tracing::field::Empty,
82 "graphql.operation.type" = ::tracing::field::Empty,
83 "apollo_private.request" = true,
84 )
85 }
86 }
87 SpanMode::SpecCompliant => {
88 unreachable!("this code path should not be reachable, this is a bug!")
89 }
90 }
91 }
92
93 pub(crate) fn create_router<B>(&self, request: &http::Request<B>) -> ::tracing::span::Span {
94 match self {
95 SpanMode::Deprecated => {
96 let trace_id = TraceId::maybe_new()
97 .map(|t| t.to_string())
98 .unwrap_or_default();
99 let span = info_span!(ROUTER_SPAN_NAME,
100 "http.method" = %request.method(),
101 "http.request.method" = %request.method(),
102 "http.route" = %request.uri().path(),
103 "http.flavor" = ?request.version(),
104 "trace_id" = %trace_id,
105 "client.name" = ::tracing::field::Empty,
106 "client.version" = ::tracing::field::Empty,
107 "otel.kind" = "INTERNAL",
108 "otel.status_code" = ::tracing::field::Empty,
109 "apollo_private.duration_ns" = ::tracing::field::Empty,
110 "apollo_private.http.request_headers" = ::tracing::field::Empty,
111 "apollo_private.http.response_headers" = ::tracing::field::Empty
112 );
113 span
114 }
115 SpanMode::SpecCompliant => {
116 info_span!(ROUTER_SPAN_NAME,
117 "http.route" = %request.uri().path(),
119 "http.request.method" = %request.method(),
120 "otel.name" = ::tracing::field::Empty,
121 "otel.kind" = "SERVER",
122 "otel.status_code" = ::tracing::field::Empty,
123 "apollo_router.license" = ::tracing::field::Empty,
124 "apollo_private.duration_ns" = ::tracing::field::Empty,
125 "apollo_private.http.request_headers" = ::tracing::field::Empty,
126 "apollo_private.http.response_headers" = ::tracing::field::Empty,
127 "apollo_private.request" = true,
128 )
129 }
130 }
131 }
132
133 pub(crate) fn create_supergraph(
134 &self,
135 config: &crate::plugins::telemetry::apollo::Config,
136 request: &SupergraphRequest,
137 field_level_instrumentation_ratio: f64,
138 ) -> ::tracing::span::Span {
139 match self {
140 SpanMode::Deprecated => {
141 let send_variable_values = config.send_variable_values.clone();
142 let span = info_span!(
143 SUPERGRAPH_SPAN_NAME,
144 otel.kind = "INTERNAL",
145 graphql.operation.name = ::tracing::field::Empty,
146 graphql.document = request
147 .supergraph_request
148 .body()
149 .query
150 .as_deref()
151 .unwrap_or_default(),
152 apollo_private.field_level_instrumentation_ratio =
153 field_level_instrumentation_ratio,
154 apollo_private.operation_signature = ::tracing::field::Empty,
155 apollo_private.graphql.variables = Telemetry::filter_variables_values(
156 &request.supergraph_request.body().variables,
157 &send_variable_values,
158 ),
159 );
160
161 if let Some(operation_name) = request
162 .context
163 .get::<_, String>(OPERATION_NAME)
164 .unwrap_or_default()
165 {
166 span.record("graphql.operation.name", operation_name);
167 }
168 span
169 }
170 SpanMode::SpecCompliant => {
171 let send_variable_values = config.send_variable_values.clone();
172 info_span!(
173 SUPERGRAPH_SPAN_NAME,
174 "otel.kind" = "INTERNAL",
175 apollo_private.field_level_instrumentation_ratio =
176 field_level_instrumentation_ratio,
177 apollo_private.operation_signature = ::tracing::field::Empty,
178 apollo_private.graphql.variables = Telemetry::filter_variables_values(
179 &request.supergraph_request.body().variables,
180 &send_variable_values,
181 )
182 )
183 }
184 }
185 }
186
187 pub(crate) fn create_subgraph(
188 &self,
189 subgraph_name: &str,
190 req: &SubgraphRequest,
191 ) -> ::tracing::span::Span {
192 let (azure_region, consumer_name, _, _, _) = crate::uhg_custom::get_uhg_labels(None, Some(&req.context));
194
195 match self {
196 SpanMode::Deprecated => {
197 let query = req
198 .subgraph_request
199 .body()
200 .query
201 .as_deref()
202 .unwrap_or_default();
203 let operation_name = req
204 .subgraph_request
205 .body()
206 .operation_name
207 .as_deref()
208 .unwrap_or_default();
209
210 info_span!(
211 SUBGRAPH_SPAN_NAME,
212 "apollo.subgraph.name" = subgraph_name,
213 graphql.document = query,
214 graphql.operation.name = operation_name,
215 "otel.kind" = "INTERNAL",
216 "apollo_private.ftv1" = ::tracing::field::Empty,
217 "otel.status_code" = ::tracing::field::Empty,
218 azure_region = %azure_region,
220 consumer_name = %consumer_name,
221 )
223 }
224 SpanMode::SpecCompliant => {
225 info_span!(
226 SUBGRAPH_SPAN_NAME,
227 "otel.kind" = "INTERNAL",
228 "apollo_private.ftv1" = ::tracing::field::Empty,
229 "otel.status_code" = ::tracing::field::Empty,
230 azure_region = %azure_region,
232 consumer_name = %consumer_name,
233 )
235 }
236 }
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use opentelemetry_api::Key;
243 use tracing_subscriber::layer::SubscriberExt;
244
245 use crate::plugins::telemetry::SpanMode;
246 use crate::plugins::telemetry::consts::REQUEST_SPAN_NAME;
247 use crate::plugins::telemetry::consts::ROUTER_SPAN_NAME;
248 use crate::plugins::telemetry::otel::layer;
249 use crate::plugins::telemetry::otel::layer::tests::TestTracer;
250 use crate::uplink::license_enforcement::LicenseState;
251
252 #[test]
253 fn test_specific_span() {
254 let tracer = TestTracer::default();
257 let subscriber = tracing_subscriber::registry()
258 .with(layer().force_sampling().with_tracer(tracer.clone()));
259
260 let request = http::Request::builder()
261 .method("GET")
262 .uri("http://example.com/path/to/location?with=query&another=UN1QU3_query")
263 .header("apollographql-client-name", "client")
264 .body("useful info")
265 .unwrap();
266
267 tracing::subscriber::with_default(subscriber, || {
268 let span = SpanMode::SpecCompliant.create_router(&request);
269 let _guard = span.enter();
270 tracing::info!("event");
271 });
272
273 let span = tracer.with_data(|data| data.builder.clone());
274 let span_attributes = span.attributes.unwrap();
275 let span_events = span.events.unwrap();
276 assert_eq!(span.name, "router");
277 assert_eq!(span_events[0].name, "event");
278
279 let get_attribute = |key| {
280 span_attributes
281 .get(&Key::from_static_str(key))
282 .unwrap()
283 .as_str()
284 };
285 assert_eq!(get_attribute("http.route"), "/path/to/location");
286 assert_eq!(get_attribute("http.request.method"), "GET");
287 assert_eq!(get_attribute("apollo_private.request"), "true");
288 }
289
290 #[test]
291 fn test_http_route_on_array_of_router_spans() {
292 let expected_routes = [
293 ("https://www.example.com/", "/"),
294 ("https://www.example.com/path", "/path"),
295 ("http://example.com/path/to/location", "/path/to/location"),
296 ("http://www.example.com/path?with=query", "/path"),
297 ("/foo/bar?baz", "/foo/bar"),
298 ];
299
300 let span_modes = [SpanMode::SpecCompliant, SpanMode::Deprecated];
301 let license_states = [LicenseState::LicensedHalt, LicenseState::Unlicensed];
302 let http_route_key = Key::from_static_str("http.route");
303
304 for (uri, expected_route) in expected_routes {
305 let request = http::Request::builder().uri(uri).body("").unwrap();
306
307 for license_state in license_states {
309 let tracer = TestTracer::default();
310 let subscriber = tracing_subscriber::registry()
311 .with(layer().force_sampling().with_tracer(tracer.clone()));
312 tracing::subscriber::with_default(subscriber, || {
313 let span = SpanMode::Deprecated.create_request(&request, license_state);
314 let _guard = span.enter();
315 });
316
317 let span = tracer.with_data(|data| data.builder.clone());
318 let span_attributes = span.attributes.unwrap();
319 let span_route = span_attributes.get(&http_route_key).unwrap();
320 assert_eq!(span_route.as_str(), expected_route);
321 assert_eq!(span.name, REQUEST_SPAN_NAME);
322 }
323
324 for span_mode in span_modes {
326 let tracer = TestTracer::default();
327 let subscriber = tracing_subscriber::registry()
328 .with(layer().force_sampling().with_tracer(tracer.clone()));
329 tracing::subscriber::with_default(subscriber, || {
330 let span = span_mode.create_router(&request);
331 let _guard = span.enter();
332 });
333
334 let span = tracer.with_data(|data| data.builder.clone());
335 let span_attributes = span.attributes.unwrap();
336 let span_route = span_attributes.get(&http_route_key).unwrap();
337 assert_eq!(span_route.as_str(), expected_route);
338 assert_eq!(span.name, ROUTER_SPAN_NAME);
339 }
340 }
341 }
342}