Skip to content

Commit

Permalink
feat(zipkin) b3 single header (#66)
Browse files Browse the repository at this point in the history
Implements #21

* refactor(zipkin) extract parse_http_req_headers to module

* feat(zipkin) parse b3 single header

* tests(zipkin) extract wait_for_spans to aux function
  • Loading branch information
kikito authored Feb 13, 2020
1 parent b564835 commit 20370d0
Show file tree
Hide file tree
Showing 5 changed files with 507 additions and 124 deletions.
1 change: 1 addition & 0 deletions kong-plugin-zipkin-0.2.1-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ build = {
["kong.plugins.zipkin.handler"] = "kong/plugins/zipkin/handler.lua";
["kong.plugins.zipkin.reporter"] = "kong/plugins/zipkin/reporter.lua";
["kong.plugins.zipkin.span"] = "kong/plugins/zipkin/span.lua";
["kong.plugins.zipkin.parse_http_req_headers"] = "kong/plugins/zipkin/parse_http_req_headers.lua";
["kong.plugins.zipkin.schema"] = "kong/plugins/zipkin/schema.lua";
};
}
87 changes: 2 additions & 85 deletions kong/plugins/zipkin/handler.lua
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
local new_zipkin_reporter = require "kong.plugins.zipkin.reporter".new
local new_span = require "kong.plugins.zipkin.span".new
local to_hex = require "resty.string".to_hex
local parse_http_req_headers = require "kong.plugins.zipkin.parse_http_req_headers"

local subsystem = ngx.config.subsystem
local fmt = string.format
local char = string.char

local ZipkinLogHandler = {
VERSION = "0.2.1",
Expand All @@ -17,89 +17,6 @@ local reporter_cache = setmetatable({}, { __mode = "k" })

local math_random = math.random

local baggage_mt = {
__newindex = function()
error("attempt to set immutable baggage", 2)
end,
}


local function hex_to_char(c)
return char(tonumber(c, 16))
end


local function from_hex(str)
if str ~= nil then -- allow nil to pass through
str = str:gsub("%x%x", hex_to_char)
end
return str
end


local function parse_http_headers(headers)
local warn = kong.log.warn
-- X-B3-Sampled: if an upstream decided to sample this request, we do too.
local should_sample = headers["x-b3-sampled"]
if should_sample == "1" or should_sample == "true" then
should_sample = true
elseif should_sample == "0" or should_sample == "false" then
should_sample = false
elseif should_sample ~= nil then
warn("x-b3-sampled header invalid; ignoring.")
should_sample = nil
end

-- X-B3-Flags: if it equals '1' then it overrides sampling policy
-- We still want to warn on invalid sample header, so do this after the above
local debug = headers["x-b3-flags"]
if debug == "1" then
should_sample = true
elseif debug ~= nil then
warn("x-b3-flags header invalid; ignoring.")
end

local had_invalid_id = false

local trace_id = headers["x-b3-traceid"]
if trace_id and ((#trace_id ~= 16 and #trace_id ~= 32) or trace_id:match("%X")) then
warn("x-b3-traceid header invalid; ignoring.")
had_invalid_id = true
end

local parent_id = headers["x-b3-parentspanid"]
if parent_id and (#parent_id ~= 16 or parent_id:match("%X")) then
warn("x-b3-parentspanid header invalid; ignoring.")
had_invalid_id = true
end

local span_id = headers["x-b3-spanid"]
if span_id and (#span_id ~= 16 or span_id:match("%X")) then
warn("x-b3-spanid header invalid; ignoring.")
had_invalid_id = true
end

if trace_id == nil or had_invalid_id then
return nil
end

local baggage = {}
trace_id = from_hex(trace_id)
parent_id = from_hex(parent_id)
span_id = from_hex(span_id)

-- Process jaegar baggage header
for k, v in pairs(headers) do
local baggage_key = k:match("^uberctx%-(.*)$")
if baggage_key then
baggage[baggage_key] = ngx.unescape_uri(v)
end
end
setmetatable(baggage, baggage_mt)

return trace_id, span_id, parent_id, should_sample, baggage
end


local function get_reporter(conf)
if reporter_cache[conf] == nil then
Expand Down Expand Up @@ -171,7 +88,7 @@ if subsystem == "http" then
local req = kong.request

local trace_id, span_id, parent_id, should_sample, baggage =
parse_http_headers(req.get_headers())
parse_http_req_headers(req.get_headers())
local method = req.get_method()

if should_sample == nil then
Expand Down
182 changes: 182 additions & 0 deletions kong/plugins/zipkin/parse_http_req_headers.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
local unescape_uri = ngx.unescape_uri
local char = string.char
local match = string.match
local gsub = string.gsub


local baggage_mt = {
__newindex = function()
error("attempt to set immutable baggage", 2)
end,
}

local B3_SINGLE_PATTERN =
"^(%x+)%-(%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x)%-?([01d]?)%-?(%x*)$"


local function hex_to_char(c)
return char(tonumber(c, 16))
end


local function from_hex(str)
if str ~= nil then -- allow nil to pass through
str = gsub(str, "%x%x", hex_to_char)
end
return str
end


local function parse_jaeger_baggage_headers(headers)
local baggage
for k, v in pairs(headers) do
local baggage_key = match(k, "^uberctx%-(.*)$")
if baggage_key then
if baggage then
baggage[baggage_key] = unescape_uri(v)
else
baggage = { [baggage_key] = unescape_uri(v) }
end
end
end

if baggage then
return setmetatable(baggage, baggage_mt)
end
end


local function parse_zipkin_b3_headers(headers)
local warn = kong.log.warn

-- X-B3-Sampled: if an upstream decided to sample this request, we do too.
local should_sample = headers["x-b3-sampled"]
if should_sample == "1" or should_sample == "true" then
should_sample = true
elseif should_sample == "0" or should_sample == "false" then
should_sample = false
elseif should_sample ~= nil then
warn("x-b3-sampled header invalid; ignoring.")
should_sample = nil
end

-- X-B3-Flags: if it equals '1' then it overrides sampling policy
-- We still want to warn on invalid sample header, so do this after the above
local debug_header = headers["x-b3-flags"]
if debug_header == "1" then
should_sample = true
elseif debug_header ~= nil then
warn("x-b3-flags header invalid; ignoring.")
end

local trace_id, span_id, sampled, parent_id
local had_invalid_id = false

-- B3 single header
-- The docs for this header are located here:
-- https://github.com/openzipkin/b3-propagation/blob/master/RATIONALE.md#b3-single-header-format
--
-- This code makes some assumptions about edge cases not covered on those docs:
-- * X-B3-Sampled and X-B3-Flags can be used in conjunction with B3 single. This doesn't raise any errors.
-- * The id headers (X-B3-TraceId, X-B3-SpanId, X-B3-ParentId) can also be used along with B3 but:
-- * When both the single header an a id header specify an id, the single header "wins" (overrides)
-- * All provided id headers are parsed (even those overriden by B3 single)
-- * An erroneous formatting on *any* header (even those overriden by B3 single) results
-- in rejection (ignoring) of all headers. This rejection is logged.
-- * The "sampled" section activates sampling with both "1" and "d". This is to match the
-- behavior of the X-B3-Flags header
-- * For speed, the "-" separators between sampled and parent_id are optional on this implementation
-- This is not guaranteed to happen in future versions and won't be considered a breaking change
local b3_single_header = headers["b3"]
if not b3_single_header then
local tracestate_header = headers["tracestate"]
if tracestate_header then
b3_single_header = match(tracestate_header, "^b3=(.+)$")
end
end

if b3_single_header and type(b3_single_header) == "string" then
if b3_single_header == "1" or b3_single_header == "d" then
should_sample = true

elseif b3_single_header == "0" then
should_sample = should_sample or false

else
trace_id, span_id, sampled, parent_id =
match(b3_single_header, B3_SINGLE_PATTERN)

local trace_id_len = trace_id and #trace_id or 0
if trace_id
and (trace_id_len == 16 or trace_id_len == 32)
and (parent_id == "" or #parent_id == 16)
then

if should_sample or sampled == "1" or sampled == "d" then
should_sample = true
elseif sampled == "0" then
should_sample = false
end

if parent_id == "" then
parent_id = nil
end

else
warn("b3 single header invalid; ignoring.")
had_invalid_id = true
end
end
end

local trace_id_header = headers["x-b3-traceid"]
if trace_id_header and ((#trace_id_header ~= 16 and #trace_id_header ~= 32)
or trace_id_header:match("%X")) then
warn("x-b3-traceid header invalid; ignoring.")
had_invalid_id = true
else
trace_id = trace_id or trace_id_header -- b3 single header overrides x-b3-traceid
end

local span_id_header = headers["x-b3-spanid"]
if span_id_header and (#span_id_header ~= 16 or span_id_header:match("%X")) then
warn("x-b3-spanid header invalid; ignoring.")
had_invalid_id = true
else
span_id = span_id or span_id_header -- b3 single header overrides x-b3-spanid
end

local parent_id_header = headers["x-b3-parentspanid"]
if parent_id_header and (#parent_id_header ~= 16 or parent_id_header:match("%X")) then
warn("x-b3-parentspanid header invalid; ignoring.")
had_invalid_id = true
else
parent_id = parent_id or parent_id_header -- b3 single header overrides x-b3-parentid
end

if trace_id == nil or had_invalid_id then
return nil, nil, nil, should_sample
end

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


local function parse_http_req_headers(headers)
local trace_id, span_id, parent_id, should_sample = parse_zipkin_b3_headers(headers)

if not trace_id then
return trace_id, span_id, parent_id, should_sample
end

local baggage = parse_jaeger_baggage_headers(headers)

return trace_id, span_id, parent_id, should_sample, baggage
end


return parse_http_req_headers
Loading

0 comments on commit 20370d0

Please sign in to comment.