Skip to content

Commit

Permalink
feat(tracing): support aws xray propagation
Browse files Browse the repository at this point in the history
  • Loading branch information
antoine-labarussias-wmx committed Jun 21, 2023
1 parent b7ac176 commit 2ea5244
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 3 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@

#### Plugins

- **OpenTelemetry**: Support AWS X-Ray propagation header
The field `header_type`now accepts the `aws` value to handle this specific
propagation header.
[11075](https://github.com/Kong/kong/pull/11075)

#### PDK

#### Performance
Expand Down
2 changes: 1 addition & 1 deletion kong/plugins/opentelemetry/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ return {
{ read_timeout = typedefs.timeout { default = 5000 } },
{ http_response_header_for_traceid = { type = "string", default = nil }},
{ header_type = { type = "string", required = false, default = "preserve",
one_of = { "preserve", "ignore", "b3", "b3-single", "w3c", "jaeger", "ot" } } },
one_of = { "preserve", "ignore", "b3", "b3-single", "w3c", "jaeger", "ot", "aws" } } },
},
entity_checks = {
{ custom_entity_check = {
Expand Down
4 changes: 2 additions & 2 deletions kong/plugins/zipkin/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ return {
{ include_credential = { description = "Specify whether the credential of the currently authenticated consumer should be included in metadata sent to the Zipkin server.", type = "boolean", required = true, default = true } },
{ traceid_byte_count = { description = "The length in bytes of each request's Trace ID.", type = "integer", required = true, default = 16, one_of = { 8, 16 } } },
{ header_type = { description = "All HTTP requests going through the plugin are tagged with a tracing HTTP request. This property codifies what kind of tracing header the plugin expects on incoming requests", type = "string", required = true, default = "preserve",
one_of = { "preserve", "ignore", "b3", "b3-single", "w3c", "jaeger", "ot" } } },
one_of = { "preserve", "ignore", "b3", "b3-single", "w3c", "jaeger", "ot", "aws" } } },
{ default_header_type = { description = "Allows specifying the type of header to be added to requests with no pre-existing tracing headers and when `config.header_type` is set to `\"preserve\"`. When `header_type` is set to any other value, `default_header_type` is ignored.", type = "string", required = true, default = "b3",
one_of = { "b3", "b3-single", "w3c", "jaeger", "ot" } } },
one_of = { "b3", "b3-single", "w3c", "jaeger", "ot", "aws" } } },
{ tags_header = { description = "The Zipkin plugin will add extra headers to the tags associated with any HTTP requests that come with a header named as configured by this property.", type = "string", required = true, default = "Zipkin-Tags" } },
{ static_tags = { description = "The tags specified on this property will be added to the generated request traces.", type = "array", elements = static_tag,
custom_validator = validate_static_tags } },
Expand Down
86 changes: 86 additions & 0 deletions kong/tracing/propagation.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
local to_hex = require "resty.string".to_hex
local table_merge = require "kong.tools.utils".table_merge
local split = require "kong.tools.utils".split
local strip = require "kong.tools.utils".strip
local unescape_uri = ngx.unescape_uri
local char = string.char
local match = string.match
local sub = string.sub
local gsub = string.gsub
local fmt = string.format
local concat = table.concat
local ipairs = ipairs
local to_ot_trace_id


Expand All @@ -23,6 +27,18 @@ local JAEGER_BAGGAGE_PATTERN = "^uberctx%-(.*)$"
local OT_BAGGAGE_PATTERN = "^ot%-baggage%-(.*)$"
local W3C_TRACEID_LEN = 16

local AWS_KV_PAIR_DELIM = ";"
local AWS_KV_DELIM = "="
local AWS_TRACE_ID_KEY = "Root"
local AWS_TRACE_ID_LEN = 35
local AWS_TRACE_ID_PATTERN = "^(%x+)%-(%x+)%-(%x+)$"
local AWS_TRACE_ID_VERSION = "1"
local AWS_TRACE_ID_TIMESTAMP_LEN = 8
local AWS_TRACE_ID_UNIQUE_ID_LEN = 24
local AWS_PARENT_ID_KEY = "Parent"
local AWS_PARENT_ID_LEN = 16
local AWS_SAMPLED_FLAG_KEY = "Sampled"

local function hex_to_char(c)
return char(tonumber(c, 16))
end
Expand Down Expand Up @@ -328,6 +344,58 @@ local function parse_jaeger_trace_context_headers(jaeger_header)
return trace_id, span_id, parent_id, should_sample
end

local function parse_aws_headers(aws_header)
-- allow testing to spy on this.
local warn = kong.log.warn

if type(aws_header) ~= "string" then
return nil, nil, nil
end

local trace_id = nil
local span_id = nil
local should_sample = nil

-- The AWS trace header consists of multiple `key=value` separated by a delimiter `;`
-- We can retrieve the trace id with the `Root` key, the span id with the `Parent`
-- key and the sampling parameter with the `Sampled` flag. Extra information should be ignored.
--
-- The trace id has a custom format: `version-timestamp-uniqueid` and an opentelemetry compatible
-- id can be deduced by concatenating the timestamp and uniqueid.
--
-- https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader
for _, key_pair in ipairs(split(aws_header, AWS_KV_PAIR_DELIM)) do
local key_pair_list = split(key_pair, AWS_KV_DELIM)
local key = strip(key_pair_list[1])
local value = strip(key_pair_list[2])

if key == AWS_TRACE_ID_KEY then
local version, timestamp_subset, unique_id_subset = match(value, AWS_TRACE_ID_PATTERN)

if #value ~= AWS_TRACE_ID_LEN or version ~= AWS_TRACE_ID_VERSION
or #timestamp_subset ~= AWS_TRACE_ID_TIMESTAMP_LEN
or #unique_id_subset ~= AWS_TRACE_ID_UNIQUE_ID_LEN then
warn("invalid aws header trace id; ignoring.")
return nil, nil, nil
end

trace_id = from_hex(timestamp_subset .. unique_id_subset)
elseif key == AWS_PARENT_ID_KEY then
if #value ~= AWS_PARENT_ID_LEN then
warn("invalid aws header parent id; ignoring.")
return nil, nil, nil
end
span_id = from_hex(value)
elseif key == AWS_SAMPLED_FLAG_KEY then
if value ~= "0" and value ~= "1" then
warn("invalid aws header sampled flag; ignoring.")
return nil, nil, nil
end
should_sample = value == "1"
end
end
return trace_id, span_id, should_sample
end

-- This plugin understands several tracing header types:
-- * Zipkin B3 headers (X-B3-TraceId, X-B3-SpanId, X-B3-ParentId, X-B3-Sampled, X-B3-Flags)
Expand Down Expand Up @@ -399,6 +467,11 @@ local function find_header_type(headers)
if ot_header then
return "ot", ot_header
end

local aws_header = headers["x-amzn-trace-id"]
if aws_header then
return "aws", aws_header
end
end


Expand All @@ -419,6 +492,8 @@ local function parse(headers, conf_header_type)
trace_id, span_id, parent_id, should_sample = parse_jaeger_trace_context_headers(composed_header)
elseif header_type == "ot" then
trace_id, parent_id, should_sample = parse_ot_headers(headers)
elseif header_type == "aws" then
trace_id, span_id, should_sample = parse_aws_headers(composed_header)
end

if not trace_id then
Expand Down Expand Up @@ -537,6 +612,17 @@ local function set(conf_header_type, found_header_type, proxy_span, conf_default
-- XXX: https://github.com/opentracing/specification/issues/117
set_header("uberctx-"..key, ngx.escape_uri(value))
end

if conf_header_type == "aws" or found_header_type == "aws" then
local trace_id = to_hex(proxy_span.trace_id)
set_header("x-amzn-trace-id", "Root=" .. AWS_TRACE_ID_VERSION .. "-" ..
sub(trace_id, 1, AWS_TRACE_ID_TIMESTAMP_LEN) .. "-" ..
sub(trace_id, AWS_TRACE_ID_TIMESTAMP_LEN + 1, #trace_id) ..
";Parent=" .. to_hex(proxy_span.span_id) .. ";Sampled=" ..
(proxy_span.should_sample and "1" or "0")
)
end

end


Expand Down

0 comments on commit 2ea5244

Please sign in to comment.