diff --git a/.changesets/feat_propagation_format.md b/.changesets/feat_propagation_format.md new file mode 100644 index 0000000000..9aaad0efb2 --- /dev/null +++ b/.changesets/feat_propagation_format.md @@ -0,0 +1,22 @@ +### Add `format` for trace ID propagation. ([PR #5803](https://github.com/apollographql/router/pull/5803)) + +The router now supports specifying the format of trace IDs that are propagated to subgraphs via headers. + +You can configure the format with the `format` option: + +```yaml +telemetry: + exporters: + tracing: + propagation: + request: + header_name: "my_header" + # Must be in UUID form, with or without dashes + format: uuid +``` + +Note that incoming requests must be some form of UUID, either with or without dashes. + +To learn about supported formats, go to [`request` configuration reference](https://apollographql.com/docs/router/configuration/telemetry/exporters/tracing/overview#request-configuration-reference) docs. + +By [@BrynCooke](https://github.com/BrynCooke) in https://github.com/apollographql/router/pull/5803 diff --git a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap index 74294b07ba..e615c2b834 100644 --- a/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap +++ b/apollo-router/src/configuration/snapshots/apollo_router__configuration__tests__schema_generation.snap @@ -4625,12 +4625,17 @@ expression: "&schema" "RequestPropagation": { "additionalProperties": false, "properties": { + "format": { + "$ref": "#/definitions/TraceIdFormat", + "description": "#/definitions/TraceIdFormat" + }, "header_name": { "description": "Choose the header name to expose trace_id (default: apollo-trace-id)", "type": "string" } }, "required": [ + "format", "header_name" ], "type": "object" diff --git a/apollo-router/src/plugins/telemetry/config.rs b/apollo-router/src/plugins/telemetry/config.rs index 42c6090b3b..5a506401a8 100644 --- a/apollo-router/src/plugins/telemetry/config.rs +++ b/apollo-router/src/plugins/telemetry/config.rs @@ -322,6 +322,9 @@ pub(crate) struct RequestPropagation { #[schemars(with = "String")] #[serde(deserialize_with = "deserialize_option_header_name")] pub(crate) header_name: Option, + + /// The trace ID format that will be used when propagating to subgraph services. + pub(crate) format: TraceIdFormat, } #[derive(Debug, Clone, Deserialize, JsonSchema)] diff --git a/apollo-router/src/plugins/telemetry/mod.rs b/apollo-router/src/plugins/telemetry/mod.rs index c50f98bd00..6fea2af4b1 100644 --- a/apollo-router/src/plugins/telemetry/mod.rs +++ b/apollo-router/src/plugins/telemetry/mod.rs @@ -939,6 +939,7 @@ impl Telemetry { if let Some(from_request_header) = &propagation.request.header_name { propagators.push(Box::new(CustomTraceIdPropagator::new( from_request_header.to_string(), + propagation.request.format.clone(), ))); } @@ -2020,13 +2021,15 @@ fn store_ftv1(subgraph_name: &ByteString, resp: SubgraphResponse) -> SubgraphRes struct CustomTraceIdPropagator { header_name: String, fields: [String; 1], + format: TraceIdFormat, } impl CustomTraceIdPropagator { - fn new(header_name: String) -> Self { + fn new(header_name: String, format: TraceIdFormat) -> Self { Self { fields: [header_name.clone()], header_name, + format, } } @@ -2058,9 +2061,9 @@ impl TextMapPropagator for CustomTraceIdPropagator { fn inject_context(&self, cx: &opentelemetry::Context, injector: &mut dyn Injector) { let span = cx.span(); let span_context = span.span_context(); - if span_context.is_valid() { - let header_value = format!("{}", span_context.trace_id()); - injector.set(&self.header_name, header_value); + if span_context.trace_id() != TraceId::INVALID { + let formatted_trace_id = self.format.format(span_context.trace_id()); + injector.set(&self.header_name, formatted_trace_id); } } @@ -2130,6 +2133,14 @@ mod tests { use http::StatusCode; use insta::assert_snapshot; use itertools::Itertools; + use opentelemetry_api::propagation::Injector; + use opentelemetry_api::propagation::TextMapPropagator; + use opentelemetry_api::trace::SpanContext; + use opentelemetry_api::trace::SpanId; + use opentelemetry_api::trace::TraceContextExt; + use opentelemetry_api::trace::TraceFlags; + use opentelemetry_api::trace::TraceId; + use opentelemetry_api::trace::TraceState; use serde_json::Value; use serde_json_bytes::json; use serde_json_bytes::ByteString; @@ -2159,6 +2170,7 @@ mod tests { use crate::plugin::test::MockSubgraphService; use crate::plugin::test::MockSupergraphService; use crate::plugin::DynPlugin; + use crate::plugins::telemetry::config::TraceIdFormat; use crate::plugins::telemetry::handle_error_internal; use crate::services::router::body::get_body_bytes; use crate::services::RouterRequest; @@ -3195,11 +3207,37 @@ mod tests { let trace_id = String::from("04f9e396-465c-4840-bc2b-f493b8b1a7fc"); let expected_trace_id = String::from("04f9e396465c4840bc2bf493b8b1a7fc"); - let propagator = CustomTraceIdPropagator::new(header.clone()); + let propagator = CustomTraceIdPropagator::new(header.clone(), TraceIdFormat::Uuid); let mut headers: HashMap = HashMap::new(); headers.insert(header, trace_id); let span = propagator.extract_span_context(&headers); assert!(span.is_some()); assert_eq!(span.unwrap().trace_id().to_string(), expected_trace_id); } + + #[test] + fn test_header_propagation_format() { + struct Injected(HashMap); + impl Injector for Injected { + fn set(&mut self, key: &str, value: String) { + self.0.insert(key.to_string(), value); + } + } + let mut injected = Injected(HashMap::new()); + let _ctx = opentelemetry::Context::new() + .with_remote_span_context(SpanContext::new( + TraceId::from_u128(0x04f9e396465c4840bc2bf493b8b1a7fc), + SpanId::INVALID, + TraceFlags::default(), + false, + TraceState::default(), + )) + .attach(); + let propagator = CustomTraceIdPropagator::new("my_header".to_string(), TraceIdFormat::Uuid); + propagator.inject_context(&opentelemetry::Context::current(), &mut injected); + assert_eq!( + injected.0.get("my_header").unwrap(), + "04f9e396-465c-4840-bc2b-f493b8b1a7fc" + ); + } } diff --git a/docs/source/configuration/telemetry/exporters/tracing/overview.mdx b/docs/source/configuration/telemetry/exporters/tracing/overview.mdx index 1bab45a2c2..c7b81cca70 100644 --- a/docs/source/configuration/telemetry/exporters/tracing/overview.mdx +++ b/docs/source/configuration/telemetry/exporters/tracing/overview.mdx @@ -144,9 +144,32 @@ telemetry: # If you have your own way to generate a trace id and you want to pass it via a custom request header request: + # The name of the header to read the trace id from header_name: my-trace-id + # The format of the trace when propagating to subgraphs. + format: uuid ``` +#### `request` configuration reference + +| Option | Values | Default | Description | +|---------------|---------------------------------------------------------------|-----------------------------------|-------------------------------------| +| `header_name` | | | The name of the http header to use for propagation. | +| `format` | `hexadecimal`\|`open_telemetry`\|`decimal`\|`datadog`\|`uuid` | `hexadecimal` | The output format of the `trace_id` | + +Valid values for `format`: +* `hexadecimal` - 32-character hexadecimal string (e.g. `0123456789abcdef0123456789abcdef`) +* `open_telemetry` - 32-character hexadecimal string (e.g. `0123456789abcdef0123456789abcdef`) +* `decimal` - 16-character decimal string (e.g. `1234567890123456`) +* `datadog` - 16-character decimal string (e.g. `1234567890123456`) +* `uuid` - 36-character UUID string (e.g. `01234567-89ab-cdef-0123-456789abcdef`) + + + +Incoming trace IDs must be in `open_telemetry` or `uuid` format. + + + ### Limits You may set limits on spans to prevent sending too much data to your APM. For example: