From 1c339b5703fb542996dbe5f58fa861825e6bae82 Mon Sep 17 00:00:00 2001 From: Vladimir Kuznichenkov Date: Tue, 1 Feb 2022 18:53:10 +0200 Subject: [PATCH] Add support for B3Multi headers propagation Some services expect [B3 Multi][1] headers as input information. To support that we need to be able to Inject them into upstream requests. Lowercase headers used to be [compatible][2] with Istio Envoy. Tests will be added as a separate commit later on. Solving https://github.com/open-telemetry/opentelemetry-cpp-contrib/issues/36 [1]: https://github.com/openzipkin/b3-propagation#multiple-headers [2]: https://github.com/open-telemetry/opentelemetry-go/issues/765 --- instrumentation/nginx/README.md | 3 +- instrumentation/nginx/src/otel_ngx_module.cpp | 58 +++++++++++++++++++ instrumentation/nginx/src/propagate.cpp | 7 +++ instrumentation/nginx/src/trace_context.h | 1 + 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/instrumentation/nginx/README.md b/instrumentation/nginx/README.md index 7a746c1c6..ef5fb5b02 100644 --- a/instrumentation/nginx/README.md +++ b/instrumentation/nginx/README.md @@ -171,7 +171,7 @@ be started. The default propagator is W3C. The same inheritance rules as [`proxy_set_header`](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header) apply, which means this directive is applied at the current configuration level if and only if there are no `proxy_set_header` directives defined on a lower level. - **required**: `false` -- **syntax**: `opentelemetry_propagate` or `opentelemetry_propagate b3` +- **syntax**: `opentelemetry_propagate` or `opentelemetry_propagate b3` or `opentelemetry_propagate b3multi` - **block**: `http`, `server`, `location` ### `opentelemetry_capture_headers` @@ -232,6 +232,7 @@ The following nginx variables are set by the instrumentation: context](https://www.w3.org/TR/trace-context/#trace-context-http-headers-format), e.g.: `00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01` - `opentelemetry_context_b3` - Trace context in the [B3 format](https://github.com/openzipkin/b3-propagation#single-header). Only set when using `opentelemetry_propagate b3`. +- `opentelemetry_sampled` - does current Span records information, "1" or "0" - `opentelemetry_trace_id` - Trace Id of the current span - `opentelemetry_span_id` - Span Id of the current span diff --git a/instrumentation/nginx/src/otel_ngx_module.cpp b/instrumentation/nginx/src/otel_ngx_module.cpp index c8b24efe7..815ce0d87 100644 --- a/instrumentation/nginx/src/otel_ngx_module.cpp +++ b/instrumentation/nginx/src/otel_ngx_module.cpp @@ -136,6 +136,9 @@ OtelGetTraceId(ngx_http_request_t* req, ngx_http_variable_value_t* v, uintptr_t static ngx_int_t OtelGetSpanId(ngx_http_request_t* req, ngx_http_variable_value_t* v, uintptr_t data); +static ngx_int_t +OtelGetSampled(ngx_http_request_t* req, ngx_http_variable_value_t* v, uintptr_t data); + static ngx_http_variable_t otel_ngx_variables[] = { { ngx_string("otel_ctx"), @@ -169,6 +172,14 @@ static ngx_http_variable_t otel_ngx_variables[] = { NGX_HTTP_VAR_NOCACHEABLE | NGX_HTTP_VAR_NOHASH, 0, }, + { + ngx_string("opentelemetry_sampled"), + nullptr, + OtelGetSampled, + 0, + NGX_HTTP_VAR_NOCACHEABLE | NGX_HTTP_VAR_NOHASH, + 0, + }, ngx_http_null_variable, }; @@ -217,6 +228,38 @@ nostd::string_view WithoutOtelVarPrefix(ngx_str_t value) { return {(const char*)value.data + prefixLength, value.len - prefixLength}; } +static ngx_int_t +OtelGetSampled(ngx_http_request_t* req, ngx_http_variable_value_t* v, uintptr_t data) { + TraceContext* traceContext = GetTraceContext(req); + + if (traceContext == nullptr || !traceContext->request_span) { + ngx_log_error( + NGX_LOG_ERR, req->connection->log, 0, + "Unable to get trace context when getting span id"); + return NGX_OK; + } + + trace::SpanContext spanContext = traceContext->request_span->GetContext(); + + if (spanContext.IsValid()) { + u_char* isSampled = spanContext.trace_flags().IsSampled() ? (u_char*) "1" : (u_char*) "0"; + + v->len = strlen((const char*)isSampled); + v->valid = 1; + v->no_cacheable = 1; + v->not_found = 0; + v->data = isSampled; + } else { + v->len = 0; + v->valid = 0; + v->no_cacheable = 1; + v->not_found = 1; + v->data = nullptr; + } + + return NGX_OK; +} + static ngx_int_t OtelGetTraceContextVar(ngx_http_request_t* req, ngx_http_variable_value_t* v, uintptr_t data) { if (!IsOtelEnabled(req)) { @@ -758,6 +801,17 @@ std::vector B3PropagationVars() { }; } +std::vector B3MultiPropagationVars() { + return { + {"proxy_set_header", "x-b3-traceid", "$opentelemetry_trace_id"}, + {"proxy_set_header", "x-b3-spanid", "$opentelemetry_span_id"}, + {"proxy_set_header", "x-b3-sampled", "$opentelemetry_sampled"}, + {"fastcgi_param", "HTTP_B3_TRACEID", "$opentelemetry_trace_id"}, + {"fastcgi_param", "HTTP_B3_SPANID", "$opentelemetry_span_id"}, + {"fastcgi_param", "HTTP_B3_SAMPLED", "$opentelemetry_sampled"}, + }; +} + std::vector OtelPropagationVars() { return { {"proxy_set_header", "traceparent", "$opentelemetry_context_traceparent"}, @@ -778,6 +832,8 @@ char* OtelNgxSetPropagation(ngx_conf_t* conf, ngx_command_t*, void* locConf) { if (propagationType == "b3") { locationConf->propagationType = TracePropagationB3; + } else if (propagationType == "b3multi") { + locationConf->propagationType = TracePropagationB3Multi; } else if (propagationType == "w3c") { locationConf->propagationType = TracePropagationW3C; } else { @@ -791,6 +847,8 @@ char* OtelNgxSetPropagation(ngx_conf_t* conf, ngx_command_t*, void* locConf) { std::vector propagationVars; if (locationConf->propagationType == TracePropagationB3) { propagationVars = B3PropagationVars(); + } else if (locationConf->propagationType == TracePropagationB3Multi) { + propagationVars = B3MultiPropagationVars(); } else { propagationVars = OtelPropagationVars(); } diff --git a/instrumentation/nginx/src/propagate.cpp b/instrumentation/nginx/src/propagate.cpp index 4154af935..a51e5c50e 100644 --- a/instrumentation/nginx/src/propagate.cpp +++ b/instrumentation/nginx/src/propagate.cpp @@ -79,6 +79,9 @@ opentelemetry::context::Context ExtractContext(OtelCarrier* carrier) { case TracePropagationW3C: { return OtelW3CPropagator().Extract(textMapCarrier, root); } + case TracePropagationB3Multi: { + return OtelB3MultiPropagator().Extract(textMapCarrier, root); + } case TracePropagationB3: { if (HasHeader(carrier->req, "b3")) { return OtelB3Propagator().Extract(textMapCarrier, root); @@ -104,6 +107,10 @@ void InjectContext(OtelCarrier* carrier, opentelemetry::context::Context context OtelB3Propagator().Inject(textMapCarrier, context); break; } + case TracePropagationB3Multi: { + OtelB3MultiPropagator().Inject(textMapCarrier, context); + break; + } default: break; } diff --git a/instrumentation/nginx/src/trace_context.h b/instrumentation/nginx/src/trace_context.h index de128e024..ef957cd1d 100644 --- a/instrumentation/nginx/src/trace_context.h +++ b/instrumentation/nginx/src/trace_context.h @@ -17,6 +17,7 @@ enum TracePropagationType { TracePropagationUnset, TracePropagationW3C, TracePropagationB3, + TracePropagationB3Multi, }; struct TraceContext {