Skip to content
This repository has been archived by the owner on Nov 30, 2021. It is now read-only.

Add support for jaeger style uber-trace-id propagation #80

Closed
wants to merge 6 commits into from
Closed
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
4 changes: 2 additions & 2 deletions kong/plugins/zipkin/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ return {
{ include_credential = { type = "boolean", required = true, default = true } },
{ traceid_byte_count = { type = "integer", required = true, default = 16, one_of = { 8, 16 } } },
{ header_type = { type = "string", required = true, default = "preserve",
one_of = { "preserve", "b3", "b3-single", "w3c" } } },
one_of = { "preserve", "b3", "b3-single", "w3c", "jaeger" } } },
{ default_header_type = { type = "string", required = true, default = "b3",
one_of = { "b3", "b3-single", "w3c" } } },
one_of = { "b3", "b3-single", "w3c", "jaeger" } } },
{ static_tags = { type = "array", elements = static_tag,
custom_validator = validate_static_tags } }
},
Expand Down
68 changes: 68 additions & 0 deletions kong/plugins/zipkin/tracing_headers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ local B3_SINGLE_PATTERN =

local W3C_TRACECONTEXT_PATTERN = "^(%x+)%-(%x+)%-(%x+)%-(%x+)$"

local JAEGER_TRACECONTEXT_PATTERN = "^(%x+):(%x+):(%x+):(%x+)$"

local function hex_to_char(c)
return char(tonumber(c, 16))
end
Expand Down Expand Up @@ -203,6 +205,57 @@ local function parse_w3c_trace_context_headers(w3c_header)
end


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

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

local trace_id, span_id, parent_id, trace_flags = match(jaeger_header, JAEGER_TRACECONTEXT_PATTERN)

-- values are not parsable hexidecimal and therefore invalid.
if trace_id == nil or span_id == nil or parent_id == nil or trace_flags == nil then
warn("invalid jaeger uber-trace-id header; ignoring.")
return nil, nil, nil, nil
end

-- valid trace_id is required.
if (#trace_id ~= 16 and #trace_id ~= 32) or tonumber(trace_id, 16) == 0 then
warn("invalid jaeger trace ID; ignoring.")
return nil, nil, nil, nil
end

-- valid span_id is required.
if #span_id ~= 16 or tonumber(parent_id, 16) == 0 then
warn("invalid jaeger span ID; ignoring.")
return nil, nil, nil, nil
end

-- valid parent_id is required.
if #parent_id ~= 16 then
warn("invalid jaeger parent ID; ignoring.")
return nil, nil, nil, nil
end

-- valid flags are required
if #trace_flags ~= 1 and #trace_flags ~= 2 then
warn("invalid jaeger flags; ignoring.")
return nil, nil, nil, nil
end

-- Jaeger sampled flag: https://www.jaegertracing.io/docs/1.17/client-libraries/#tracespan-identity
local should_sample = tonumber(trace_flags, 16) % 2 == 1

trace_id = from_hex(trace_id)
span_id = from_hex(span_id)
parent_id = from_hex(parent_id)

return trace_id, span_id, parent_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)
-- * Zipkin B3 "single header" (a single header called "B3", composed of several fields)
Expand Down Expand Up @@ -249,6 +302,11 @@ local function find_header_type(headers)
if w3c_header then
return "w3c", w3c_header
end

local jaeger_header = headers["uber-trace-id"]
if jaeger_header then
return "jaeger", jaeger_header
end
end


Expand All @@ -261,6 +319,8 @@ local function parse(headers)
trace_id, span_id, parent_id, should_sample = parse_zipkin_b3_headers(headers, composed_header)
elseif header_type == "w3c" then
trace_id, parent_id, should_sample = parse_w3c_trace_context_headers(composed_header)
elseif header_type == "jaeger" then
trace_id, span_id, parent_id, should_sample = parse_jaeger_trace_context_headers(composed_header)
end

if not trace_id then
Expand Down Expand Up @@ -316,6 +376,14 @@ local function set(conf_header_type, found_header_type, proxy_span, conf_default
proxy_span.should_sample and "01" or "00"))
end

if conf_header_type == "jaeger" or found_header_type == "jaeger" then
set_header("uber-trace-id", fmt("%s:%s:%s:%s",
to_hex(proxy_span.trace_id),
to_hex(proxy_span.span_id),
to_hex(proxy_span.parent_id),
proxy_span.should_sample and "01" or "00"))
end

for key, value in proxy_span:each_baggage_item() do
-- XXX: https://github.com/opentracing/specification/issues/117
set_header("uberctx-"..key, ngx.escape_uri(value))
Expand Down
191 changes: 191 additions & 0 deletions spec/tracing_headers_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,121 @@ describe("tracing_headers.parse", function()
end)
end)
end)


describe("Jaeger header parsing", function()
local warn
before_each(function()
warn = spy.on(kong.log, "warn")
end)

it("valid uber-trace-id with sampling", function()
local ubertraceid = fmt("%s:%s:%s:%s", trace_id, span_id, parent_id, "1")
local t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger", trace_id, span_id, parent_id, true }, to_hex_ids(t))
assert.spy(warn).not_called()
end)

it("valid uber-trace-id without sampling", function()
local ubertraceid = fmt("%s:%s:%s:%s", trace_id, span_id, parent_id, "0")
local t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger", trace_id, span_id, parent_id, false }, to_hex_ids(t))
assert.spy(warn).not_called()
end)

it("valid uber-trace-id 128bit with sampling", function()
local ubertraceid = fmt("%s:%s:%s:%s", trace_id_32, span_id, parent_id, "1")
local t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger", trace_id_32, span_id, parent_id, true }, to_hex_ids(t))
assert.spy(warn).not_called()
end)

it("valid uber-trace-id 128bit without sampling", function()
local ubertraceid = fmt("%s:%s:%s:%s", trace_id_32, span_id, parent_id, "0")
local t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger", trace_id_32, span_id, parent_id, false }, to_hex_ids(t))
assert.spy(warn).not_called()
end)

describe("errors", function()
it("rejects invalid header", function()
local ubertraceid = fmt("vv:%s:%s:%s", span_id, parent_id, "0")
local t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger" }, t)
assert.spy(warn).was_called_with("invalid jaeger uber-trace-id header; ignoring.")

ubertraceid = fmt("%s:vv:%s:%s", trace_id, parent_id, "0")
t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger" }, t)
assert.spy(warn).was_called_with("invalid jaeger uber-trace-id header; ignoring.")

ubertraceid = fmt("%s:%s:vv:%s", trace_id, span_id, "0")
t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger" }, t)
assert.spy(warn).was_called_with("invalid jaeger uber-trace-id header; ignoring.")

ubertraceid = fmt("%s:%s:%s:vv", trace_id, span_id, parent_id)
t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger" }, t)
assert.spy(warn).was_called_with("invalid jaeger uber-trace-id header; ignoring.")
end)

it("rejects invalid trace IDs", function()
local ubertraceid = fmt("%s:%s:%s:%s", too_short_id, span_id, parent_id, "1")
local t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger" }, t)
assert.spy(warn).was_called_with("invalid jaeger trace ID; ignoring.")

ubertraceid = fmt("%s:%s:%s:%s", too_long_id, span_id, parent_id, "1")
t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger" }, t)
assert.spy(warn).was_called_with("invalid jaeger trace ID; ignoring.")

-- cannot be all zeros
ubertraceid = fmt("%s:%s:%s:%s", "00000000000000000000000000000000", span_id, parent_id, "1")
t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger" }, t)
assert.spy(warn).was_called_with("invalid jaeger trace ID; ignoring.")
end)

it("rejects invalid parent IDs", function()
local ubertraceid = fmt("%s:%s:%s:%s", trace_id, span_id, too_short_id, "1")
local t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger" }, t)
assert.spy(warn).was_called_with("invalid jaeger parent ID; ignoring.")

ubertraceid = fmt("%s:%s:%s:%s", trace_id, span_id, too_long_id, "1")
t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger" }, t)
assert.spy(warn).was_called_with("invalid jaeger parent ID; ignoring.")
end)

it("rejects invalid span IDs", function()
local ubertraceid = fmt("%s:%s:%s:%s", trace_id, too_short_id, parent_id, "1")
local t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger" }, t)
assert.spy(warn).was_called_with("invalid jaeger span ID; ignoring.")

ubertraceid = fmt("%s:%s:%s:%s", trace_id, too_long_id, parent_id, "1")
t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger" }, t)
assert.spy(warn).was_called_with("invalid jaeger span ID; ignoring.")

-- cannot be all zeros
ubertraceid = fmt("%s:%s:%s:%s", trace_id, "00000000000000000000000000000000", parent_id, "1")
t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger" }, t)
assert.spy(warn).was_called_with("invalid jaeger span ID; ignoring.")
end)

it("rejects invalid trace flags", function()
local ubertraceid = fmt("%s:%s:%s:%s", trace_id, span_id, parent_id, "123")
local t = { parse({ ["uber-trace-id"] = ubertraceid }) }
assert.same({ "jaeger" }, t)
assert.spy(warn).was_called_with("invalid jaeger flags; ignoring.")
end)
end)
end)
end)

describe("tracing_headers.set", function()
Expand Down Expand Up @@ -352,6 +467,10 @@ describe("tracing_headers.set", function()
traceparent = fmt("00-%s-%s-01", trace_id, span_id)
}

local jaeger_headers = {
["uber-trace-id"] = fmt("%s:%s:%s:%s", trace_id, span_id, parent_id, "01")
}

before_each(function()
headers = {}
warnings = {}
Expand All @@ -372,6 +491,11 @@ describe("tracing_headers.set", function()
set("preserve", "w3c", proxy_span)
assert.same(w3c_headers, headers)

headers = {}

set("preserve", "jaeger", proxy_span)
assert.same(jaeger_headers, headers)

assert.same({}, warnings)
end)

Expand All @@ -393,6 +517,11 @@ describe("tracing_headers.set", function()

set("preserve", "w3c", proxy_span, "w3c")
assert.same(w3c_headers, headers)

headers = {}

set("preserve", nil, proxy_span, "jaeger")
assert.same(jaeger_headers, headers)
end)
end)

Expand Down Expand Up @@ -426,6 +555,15 @@ describe("tracing_headers.set", function()
assert.equals(1, #warnings)
assert.matches("Mismatched header types", warnings[1])
end)

it("sets both the b3 and w3c headers when a jaeger header is encountered.", function()
set("b3", "jaeger", proxy_span)
assert.same(table_merge(b3_headers, jaeger_headers), headers)

-- but it generates a warning
assert.equals(1, #warnings)
assert.matches("Mismatched header types", warnings[1])
end)
end)

describe("conf.header_type = 'b3-single'", function()
Expand All @@ -452,6 +590,15 @@ describe("tracing_headers.set", function()
assert.equals(1, #warnings)
assert.matches("Mismatched header types", warnings[1])
end)

it("sets both the b3 and w3c headers when a w3c header is encountered.", function()
set("b3-single", "jaeger", proxy_span)
assert.same(table_merge(b3_single_headers, jaeger_headers), headers)

-- but it generates a warning
assert.equals(1, #warnings)
assert.matches("Mismatched header types", warnings[1])
end)
end)

describe("conf.header_type = 'w3c'", function()
Expand All @@ -478,6 +625,50 @@ describe("tracing_headers.set", function()
assert.equals(1, #warnings)
assert.matches("Mismatched header types", warnings[1])
end)

it("sets both the jaeger and w3c headers when a b3-single header is encountered.", function()
set("w3c", "jaeger", proxy_span)
assert.same(table_merge(jaeger_headers, w3c_headers), headers)

-- but it generates a warning
assert.equals(1, #warnings)
assert.matches("Mismatched header types", warnings[1])
end)
end)

describe("conf.header_type = 'jaeger'", function()
it("sets headers to jaeger when conf.header_type = jaeger", function()
set("jaeger", "jaeger", proxy_span)
assert.same(jaeger_headers, headers)
assert.same({}, warnings)
end)

it("sets both the b3 and jaeger headers when a jaeger header is encountered.", function()
set("jaeger", "b3", proxy_span)
assert.same(table_merge(b3_headers, jaeger_headers), headers)

-- but it generates a warning
assert.equals(1, #warnings)
assert.matches("Mismatched header types", warnings[1])
end)

it("sets both the b3-single and jaeger headers when a b3-single header is encountered.", function()
set("jaeger", "b3-single", proxy_span)
assert.same(table_merge(b3_single_headers, jaeger_headers), headers)

-- but it generates a warning
assert.equals(1, #warnings)
assert.matches("Mismatched header types", warnings[1])
end)

it("sets both the jaeger and w3c headers when a w3c header is encountered.", function()
set("jaeger", "w3c", proxy_span)
assert.same(table_merge(jaeger_headers, w3c_headers), headers)

-- but it generates a warning
assert.equals(1, #warnings)
assert.matches("Mismatched header types", warnings[1])
end)
end)
end)

16 changes: 16 additions & 0 deletions spec/zipkin_no_endpoint_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,22 @@ describe("http integration tests with zipkin server (no http_endpoint) [#"
local json = cjson.decode(body)
assert.matches("00%-" .. trace_id .. "%-%x+-01", json.headers.traceparent)
end)

it("propagates jaeger tracing headers", function()
local trace_id = gen_trace_id(traceid_byte_count)
local span_id = gen_span_id()
local parent_id = gen_span_id()

local r = proxy_client:get("/", {
headers = {
["uber-trace-id"] = fmt("%s:%s:%s:%s", trace_id, span_id, parent_id, "1"),
host = "http-route"
},
})
local body = assert.response(r).has.status(200)
local json = cjson.decode(body)
assert.matches(trace_id .. ":%x+:" .. span_id .. ":01", json.headers["uber-trace-id"])
end)
end)
end
end
Loading