From 0243c3d9367603105b215bce77e7d6e14ebed864 Mon Sep 17 00:00:00 2001 From: Sebastian Parschauer Date: Wed, 6 May 2026 14:39:47 +0200 Subject: [PATCH 1/2] http: Add W3C trace context support to `send_request()` NGINX can be used together with an NGINX tracer with W3C trace context support. That tracer can be based on OpenTracing or OpenTelemetry (OTel), such as the `nginx-otel` module. In the Observability space it is required that all HTTP request tracers in the line do trace propagation or trace forwarding at least. This is usually done by sending out an updated `traceparent` header. It contains for example a trace ID and a span ID so that the Observability backend such as Jaeger can correlate all spans to the same trace. To be able to trace HTTP requests sent from regular NGINX code and also the ones sent by LUA HTTP client code with the same NGINX tracer, the LUA HTTP client code has to be extended to add the `traceparent` header from `ngx.var.http_traceparent`. The value of this `$http_traceparent` variable is set by the NGINX tracer. It updates the span ID inside the `traceparent` value if the NGINX tracer is set to propagation mode. Example with `nginx-otel` config: `otel_trace_context propagate;`. So extend the `send_request()` function for this, because it already adds other headers as well and is used when calling `request_uri()` as well. Check if the `traceparent` header is not set yet but `ngx.var.http_traceparent` is set and set it from there. Also add a code comment describing this as well. References: * https://www.w3.org/TR/trace-context/ * https://github.com/nginxinc/nginx-otel * https://nginx.org/en/docs/ngx_otel_module.html * https://nginx.org/en/docs/http/ngx_http_core_module.html#var_http_ --- lib/resty/http.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/resty/http.lua b/lib/resty/http.lua index a85f85a..6deb629 100644 --- a/lib/resty/http.lua +++ b/lib/resty/http.lua @@ -738,6 +738,10 @@ function _M.send_request(self, params) if params.version == 1.0 and not headers["Connection"] then headers["Connection"] = "Keep-Alive" end + -- W3C trace context support with NGINX tracer + if not headers["traceparent"] and ngx.var.http_traceparent then + headers["traceparent"] = ngx.var.http_traceparent + end params.headers = headers From 81d90b7383c9dd050b7e135ae994b54955f18fbb Mon Sep 17 00:00:00 2001 From: Sebastian Parschauer Date: Thu, 7 May 2026 14:20:54 +0200 Subject: [PATCH 2/2] t: Add 3 traceparent header tests The new `traceparent` header auto-injection from `ngx.var.http_traceparent` has to be tested. So add a new file `t/21-traceparent-header.t` containing 3 tests for that: * TEST 1: No traceparent header is set * TEST 2: The traceparent header is correctly added when ngx.var.http_traceparent is used * TEST 3: The traceparent header is not modified from ngx.var.http_traceparent if it is already set Use an NGINX directive `set $http_traceparent '00-000...-01';` in front of the LUA block to emulate `nginx-otel` behavior. The case that there is a `traceparent` header added with `request_uri()` and `ngx.var.http_traceparent` is not set, is already covered by tests 1 and 3. --- t/21-traceparent-header.t | 92 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 t/21-traceparent-header.t diff --git a/t/21-traceparent-header.t b/t/21-traceparent-header.t new file mode 100644 index 0000000..fcb42de --- /dev/null +++ b/t/21-traceparent-header.t @@ -0,0 +1,92 @@ +use Test::Nginx::Socket 'no_plan'; +use Cwd qw(cwd); + +my $pwd = cwd(); + +$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; +$ENV{TEST_NGINX_PWD} ||= $pwd; +$ENV{TEST_COVERAGE} ||= 0; + +our $HttpConfig = qq{ + lua_package_path "$pwd/lib/?.lua;/usr/local/share/lua/5.1/?.lua;;"; + error_log logs/error.log debug; + resolver 8.8.8.8 ipv6=off; + + init_by_lua_block { + if $ENV{TEST_COVERAGE} == 1 then + jit.off() + require("luacov.runner").init() + end + + require("resty.http").debug(true) + } +}; + +no_long_string(); +#no_diff(); + +run_tests(); + +__DATA__ +=== TEST 1: No traceparent header is set +--- http_config eval: $::HttpConfig +--- config + location /lua { + content_by_lua ' + require("resty.http").debug(true) + local http = require "resty.http" + local httpc = http.new() + + local res, err = httpc:request_uri("http://www.google.com") + '; + } +--- request +GET /lua +--- no_error_log +[error] +traceparent: + + +=== TEST 2: The traceparent header is correctly added when ngx.var.http_traceparent is used +--- http_config eval: $::HttpConfig +--- config + location /lua { + # emulate nginx-otel behavior here + set $http_traceparent '00-000000000000000019f4e02c82857913-11488c6e00d1d248-01'; + content_by_lua ' + require("resty.http").debug(true) + local http = require "resty.http" + local httpc = http.new() + local res, err = httpc:request_uri("http://www.google.com") + '; + } +--- request +GET /lua +--- no_error_log +[error] +--- error_log +traceparent: 00-000000000000000019f4e02c82857913-11488c6e00d1d248-01 + + +=== TEST 3: The traceparent header is not modified from ngx.var.http_traceparent if it is already set +--- http_config eval: $::HttpConfig +--- config + location /lua { + # emulate nginx-otel behavior here + set $http_traceparent '00-000000000000000019f4e02c82857913-11488c6e00d1d248-01'; + content_by_lua ' + require("resty.http").debug(true) + local http = require "resty.http" + local httpc = http.new() + local req_headers = {} + req_headers["traceparent"] = "00-00000000000000006633c2d00527dd33-1af98f7e6ecd16ff-01" + local res, err = httpc:request_uri("http://www.google.com", {method = GET, headers = req_headers}) + '; + } +--- request +GET /lua +--- no_error_log +[error] +traceparent: 00-000000000000000019f4e02c82857913-11488c6e00d1d248-01 +--- error_log +traceparent: 00-00000000000000006633c2d00527dd33-1af98f7e6ecd16ff-01