Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .changesets/feat_propagation_format.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions apollo-router/src/plugins/telemetry/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@ pub(crate) struct RequestPropagation {
#[schemars(with = "String")]
#[serde(deserialize_with = "deserialize_option_header_name")]
pub(crate) header_name: Option<HeaderName>,

/// The trace ID format that will be used when propagating to subgraph services.
pub(crate) format: TraceIdFormat,
}

#[derive(Debug, Clone, Deserialize, JsonSchema)]
Expand Down
48 changes: 43 additions & 5 deletions apollo-router/src/plugins/telemetry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)));
}

Expand Down Expand Up @@ -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,
}
}

Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<String, String> = 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<String, String>);
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"
);
}
}
23 changes: 23 additions & 0 deletions docs/source/configuration/telemetry/exporters/tracing/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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`)

<Note>

Incoming trace IDs must be in `open_telemetry` or `uuid` format.

</Note>

### Limits

You may set limits on spans to prevent sending too much data to your APM. For example:
Expand Down