428 lines
13 KiB
Lua
428 lines
13 KiB
Lua
local new_zipkin_reporter = require "kong.plugins.zipkin.reporter".new
|
|
local new_span = require "kong.plugins.zipkin.span".new
|
|
local propagation = require "kong.observability.tracing.propagation"
|
|
local request_tags = require "kong.plugins.zipkin.request_tags"
|
|
local kong_meta = require "kong.meta"
|
|
|
|
|
|
local ngx = ngx
|
|
local ngx_var = ngx.var
|
|
local subsystem = ngx.config.subsystem
|
|
local fmt = string.format
|
|
local rand_bytes = require("kong.tools.rand").get_rand_bytes
|
|
local splitn = require("kong.tools.string").splitn
|
|
local to_hex = require "resty.string".to_hex
|
|
|
|
local ZipkinLogHandler = {
|
|
VERSION = kong_meta.version,
|
|
-- We want to run first so that timestamps taken are at start of the phase
|
|
-- also so that other plugins might be able to use our structures
|
|
PRIORITY = 100000,
|
|
}
|
|
|
|
local reporter_cache = setmetatable({}, { __mode = "k" })
|
|
|
|
local math_random = math.random
|
|
local ngx_req_start_time = ngx.req.start_time
|
|
local ngx_now = ngx.now
|
|
|
|
|
|
-- ngx.now in microseconds
|
|
local function ngx_now_mu()
|
|
return ngx_now() * 1000000
|
|
end
|
|
|
|
|
|
-- ngx.req.start_time in microseconds
|
|
local function ngx_req_start_time_mu()
|
|
return ngx_req_start_time() * 1000000
|
|
end
|
|
|
|
|
|
local function get_reporter(conf)
|
|
if reporter_cache[conf] == nil then
|
|
reporter_cache[conf] = new_zipkin_reporter(conf)
|
|
end
|
|
return reporter_cache[conf]
|
|
end
|
|
|
|
|
|
local function tag_with_service_and_route(span)
|
|
local service = kong.router.get_service()
|
|
if service and service.id then
|
|
span:set_tag("kong.service", service.id)
|
|
if type(service.name) == "string" then
|
|
span.service_name = service.name
|
|
span:set_tag("kong.service_name", service.name)
|
|
end
|
|
end
|
|
|
|
local route = kong.router.get_route()
|
|
if route then
|
|
if route.id then
|
|
span:set_tag("kong.route", route.id)
|
|
end
|
|
if type(route.name) == "string" then
|
|
span:set_tag("kong.route_name", route.name)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-- adds the proxy span to the zipkin context, unless it already exists
|
|
local function get_or_add_proxy_span(zipkin, timestamp)
|
|
if not zipkin.proxy_span then
|
|
local request_span = zipkin.request_span
|
|
zipkin.proxy_span = request_span:new_child(
|
|
"CLIENT",
|
|
request_span.name .. " (proxy)",
|
|
timestamp
|
|
)
|
|
end
|
|
return zipkin.proxy_span
|
|
end
|
|
|
|
|
|
local initialize_request
|
|
|
|
|
|
local function get_context(conf, ctx, extracted_ctx)
|
|
local zipkin = ctx.zipkin
|
|
if not zipkin then
|
|
initialize_request(conf, ctx, extracted_ctx)
|
|
zipkin = ctx.zipkin
|
|
end
|
|
return zipkin
|
|
end
|
|
|
|
|
|
if subsystem == "http" then
|
|
initialize_request = function(conf, ctx, extracted_ctx)
|
|
local req = kong.request
|
|
|
|
local req_headers = req.get_headers()
|
|
|
|
extracted_ctx = extracted_ctx
|
|
or propagation.extract(propagation.get_plugin_params(conf))
|
|
or {}
|
|
local trace_id = extracted_ctx.trace_id
|
|
local should_sample = extracted_ctx.should_sample
|
|
local baggage = extracted_ctx.baggage
|
|
|
|
-- Some formats (e.g. W3C) only provide one span_id, which is the id of the
|
|
-- span that the header represents, it is meant to be used as the parent of
|
|
-- the server span (span generated by the receiver) and is in fact
|
|
-- sometimes called parent_id.
|
|
-- Other formats (e.g. B3) support two span IDs, usually span_id and
|
|
-- parent_id. In that case the span (and its ID) is shared between client
|
|
-- and server and the parent_id identifies its parent.
|
|
local parent_id, span_id
|
|
if extracted_ctx.reuse_span_id then
|
|
span_id = extracted_ctx.span_id
|
|
parent_id = extracted_ctx.parent_id
|
|
|
|
else
|
|
parent_id = extracted_ctx.span_id
|
|
end
|
|
|
|
local method = req.get_method()
|
|
|
|
if should_sample == nil then
|
|
should_sample = math_random() < conf.sample_ratio
|
|
end
|
|
|
|
if trace_id == nil then
|
|
trace_id = rand_bytes(conf.traceid_byte_count)
|
|
end
|
|
|
|
local span_name = method
|
|
local path = req.get_path()
|
|
if conf.http_span_name == "method_path" then
|
|
span_name = method .. ' ' .. path
|
|
end
|
|
|
|
local request_span = new_span(
|
|
"SERVER",
|
|
span_name,
|
|
ngx_req_start_time_mu(),
|
|
should_sample,
|
|
trace_id,
|
|
span_id,
|
|
parent_id,
|
|
baggage)
|
|
|
|
local http_version = req.get_http_version()
|
|
local protocol = http_version and 'HTTP/'..http_version or nil
|
|
|
|
request_span.ip = kong.client.get_forwarded_ip()
|
|
request_span.port = kong.client.get_forwarded_port()
|
|
|
|
request_span:set_tag("lc", "kong")
|
|
request_span:set_tag("http.method", method)
|
|
request_span:set_tag("http.host", req.get_host())
|
|
request_span:set_tag("http.path", path)
|
|
if protocol then
|
|
request_span:set_tag("http.protocol", protocol)
|
|
end
|
|
|
|
local static_tags = conf.static_tags
|
|
if type(static_tags) == "table" then
|
|
for i = 1, #static_tags do
|
|
local tag = static_tags[i]
|
|
request_span:set_tag(tag.name, tag.value)
|
|
end
|
|
end
|
|
|
|
local req_tags, err = request_tags.parse(req_headers[conf.tags_header])
|
|
if err then
|
|
-- log a warning in case there were erroneous request tags. Don't throw the tracing away & rescue with valid tags, if any
|
|
kong.log.warn(err)
|
|
end
|
|
if req_tags then
|
|
for tag_name, tag_value in pairs(req_tags) do
|
|
request_span:set_tag(tag_name, tag_value)
|
|
end
|
|
end
|
|
|
|
ctx.zipkin = {
|
|
request_span = request_span,
|
|
proxy_span = nil,
|
|
header_filter_finished = false,
|
|
}
|
|
end
|
|
|
|
|
|
local function get_inject_ctx(extract_ctx, conf)
|
|
local zipkin = get_context(conf, kong.ctx.plugin, extract_ctx)
|
|
local ngx_ctx = ngx.ctx
|
|
|
|
local access_start =
|
|
ngx_ctx.KONG_ACCESS_START and ngx_ctx.KONG_ACCESS_START * 1000
|
|
or ngx_now_mu()
|
|
|
|
local proxy_span = get_or_add_proxy_span(zipkin, access_start)
|
|
|
|
local inject_ctx = extract_ctx
|
|
inject_ctx.trace_id = proxy_span.trace_id or inject_ctx.trace_id or nil
|
|
inject_ctx.span_id = proxy_span.span_id or inject_ctx.span_id or nil
|
|
inject_ctx.parent_id = proxy_span.parent_id or inject_ctx.parent_id or nil
|
|
inject_ctx.should_sample = proxy_span.should_sample or inject_ctx.should_sample or nil
|
|
inject_ctx.baggage = proxy_span.baggage or inject_ctx.baggage or nil
|
|
return inject_ctx
|
|
end
|
|
|
|
|
|
function ZipkinLogHandler:access(conf) -- luacheck: ignore 212
|
|
propagation.propagate(
|
|
propagation.get_plugin_params(conf),
|
|
get_inject_ctx,
|
|
conf
|
|
)
|
|
end
|
|
|
|
|
|
function ZipkinLogHandler:header_filter(conf) -- luacheck: ignore 212
|
|
local zipkin = get_context(conf, kong.ctx.plugin)
|
|
local ngx_ctx = ngx.ctx
|
|
local header_filter_start_mu =
|
|
ngx_ctx.KONG_HEADER_FILTER_START and ngx_ctx.KONG_HEADER_FILTER_START * 1000
|
|
or ngx_now_mu()
|
|
|
|
local proxy_span = get_or_add_proxy_span(zipkin, header_filter_start_mu)
|
|
|
|
if conf.phase_duration_flavor == "annotations" then
|
|
proxy_span:annotate("khs", header_filter_start_mu)
|
|
end
|
|
|
|
if conf.http_response_header_for_traceid then
|
|
local trace_id = to_hex(proxy_span.trace_id)
|
|
kong.response.add_header(conf.http_response_header_for_traceid, trace_id)
|
|
end
|
|
end
|
|
|
|
|
|
function ZipkinLogHandler:body_filter(conf) -- luacheck: ignore 212
|
|
local zipkin = get_context(conf, kong.ctx.plugin)
|
|
|
|
-- Finish header filter when body filter starts
|
|
if not zipkin.header_filter_finished then
|
|
if conf.phase_duration_flavor == "annotations" then
|
|
local now_mu = ngx_now_mu()
|
|
zipkin.proxy_span:annotate("khf", now_mu)
|
|
zipkin.proxy_span:annotate("kbs", now_mu)
|
|
end
|
|
|
|
zipkin.header_filter_finished = true
|
|
end
|
|
end
|
|
|
|
elseif subsystem == "stream" then
|
|
|
|
initialize_request = function(conf, ctx)
|
|
local request_span = new_span(
|
|
"SERVER",
|
|
"stream",
|
|
ngx_req_start_time_mu(),
|
|
math_random() < conf.sample_ratio,
|
|
rand_bytes(conf.traceid_byte_count)
|
|
)
|
|
request_span.ip = kong.client.get_forwarded_ip()
|
|
request_span.port = kong.client.get_forwarded_port()
|
|
|
|
request_span:set_tag("lc", "kong")
|
|
|
|
local static_tags = conf.static_tags
|
|
if type(static_tags) == "table" then
|
|
for i = 1, #static_tags do
|
|
local tag = static_tags[i]
|
|
request_span:set_tag(tag.name, tag.value)
|
|
end
|
|
end
|
|
|
|
ctx.zipkin = {
|
|
request_span = request_span,
|
|
proxy_span = nil,
|
|
}
|
|
end
|
|
|
|
|
|
function ZipkinLogHandler:preread(conf) -- luacheck: ignore 212
|
|
local zipkin = get_context(conf, kong.ctx.plugin)
|
|
local ngx_ctx = ngx.ctx
|
|
local preread_start_mu =
|
|
ngx_ctx.KONG_PREREAD_START and ngx_ctx.KONG_PREREAD_START * 1000
|
|
or ngx_now_mu()
|
|
|
|
local proxy_span = get_or_add_proxy_span(zipkin, preread_start_mu)
|
|
|
|
if conf.phase_duration_flavor == "annotations" then
|
|
proxy_span:annotate("kps", preread_start_mu)
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
function ZipkinLogHandler:log(conf) -- luacheck: ignore 212
|
|
local now_mu = ngx_now_mu()
|
|
local zipkin = get_context(conf, kong.ctx.plugin)
|
|
local ngx_ctx = ngx.ctx
|
|
local request_span = zipkin.request_span
|
|
local proxy_span = get_or_add_proxy_span(zipkin, now_mu)
|
|
local reporter = get_reporter(conf)
|
|
|
|
local proxy_finish_mu =
|
|
ngx_ctx.KONG_BODY_FILTER_ENDED_AT and ngx_ctx.KONG_BODY_FILTER_ENDED_AT * 1000
|
|
or now_mu
|
|
|
|
if ngx_ctx.KONG_REWRITE_START and ngx_ctx.KONG_REWRITE_TIME then
|
|
-- note: rewrite is logged on the request span, not on the proxy span
|
|
if conf.phase_duration_flavor == "annotations" then
|
|
local rewrite_finish_mu = (ngx_ctx.KONG_REWRITE_START + ngx_ctx.KONG_REWRITE_TIME) * 1000
|
|
request_span:annotate("krf", rewrite_finish_mu)
|
|
end
|
|
end
|
|
|
|
if subsystem == "http" then
|
|
if conf.phase_duration_flavor == "annotations" then
|
|
-- note: rewrite is logged on the request_span, not on the proxy span
|
|
local rewrite_start_mu =
|
|
ngx_ctx.KONG_REWRITE_START and ngx_ctx.KONG_REWRITE_START * 1000
|
|
or request_span.timestamp
|
|
request_span:annotate("krs", rewrite_start_mu)
|
|
|
|
-- annotate access_start here instead of in the access phase
|
|
-- because the plugin access phase is skipped when dealing with
|
|
-- requests which are not matched by any route
|
|
-- but we still want to know when the access phase "started"
|
|
local access_start_mu =
|
|
ngx_ctx.KONG_ACCESS_START and ngx_ctx.KONG_ACCESS_START * 1000
|
|
or proxy_span.timestamp
|
|
proxy_span:annotate("kas", access_start_mu)
|
|
|
|
local access_finish_mu =
|
|
ngx_ctx.KONG_ACCESS_ENDED_AT and ngx_ctx.KONG_ACCESS_ENDED_AT * 1000
|
|
or proxy_finish_mu
|
|
proxy_span:annotate("kaf", access_finish_mu)
|
|
|
|
if not zipkin.header_filter_finished then
|
|
proxy_span:annotate("khf", now_mu)
|
|
zipkin.header_filter_finished = true
|
|
end
|
|
|
|
proxy_span:annotate("kbf", now_mu)
|
|
|
|
elseif conf.phase_duration_flavor == "tags" then
|
|
request_span:set_tag("kong.rewrite.duration_ms", ngx_ctx.KONG_REWRITE_TIME)
|
|
proxy_span:set_tag("kong.access.duration_ms", ngx_ctx.KONG_ACCESS_TIME)
|
|
proxy_span:set_tag("kong.header_filter.duration_ms", ngx_ctx.KONG_HEADER_FILTER_TIME)
|
|
proxy_span:set_tag("kong.body_filter.duration_ms", ngx_ctx.KONG_BODY_FILTER_TIME)
|
|
end
|
|
|
|
else
|
|
|
|
if conf.phase_duration_flavor == "annotations" then
|
|
local preread_finish_mu =
|
|
ngx_ctx.KONG_PREREAD_ENDED_AT and ngx_ctx.KONG_PREREAD_ENDED_AT * 1000
|
|
or proxy_finish_mu
|
|
proxy_span:annotate("kpf", preread_finish_mu)
|
|
|
|
elseif conf.phase_duration_flavor == "tags" then
|
|
proxy_span:set_tag("kong.preread.duration_ms", ngx_ctx.KONG_PREREAD_TIME)
|
|
end
|
|
end
|
|
|
|
local balancer_data = ngx_ctx.balancer_data
|
|
if balancer_data then
|
|
local balancer_tries = balancer_data.tries
|
|
local upstream_connect_time = splitn(ngx_var.upstream_connect_time, ", ")
|
|
for i = 1, balancer_data.try_count do
|
|
local try = balancer_tries[i]
|
|
local name = fmt("%s (balancer try %d)", request_span.name, i)
|
|
local span = request_span:new_child("CLIENT", name, try.balancer_start * 1000)
|
|
span.ip = try.ip
|
|
span.port = try.port
|
|
|
|
span:set_tag("kong.balancer.try", i)
|
|
if i < balancer_data.try_count then
|
|
span:set_tag("error", true)
|
|
span:set_tag("kong.balancer.state", try.state)
|
|
span:set_tag("http.status_code", try.code)
|
|
end
|
|
|
|
tag_with_service_and_route(span)
|
|
|
|
if try.balancer_latency ~= nil then
|
|
local try_connect_time = (tonumber(upstream_connect_time[i]) or 0) * 1000 -- ms
|
|
span:finish((try.balancer_start + try.balancer_latency + try_connect_time) * 1000)
|
|
else
|
|
span:finish(now_mu)
|
|
end
|
|
reporter:report(span)
|
|
end
|
|
proxy_span:set_tag("peer.hostname", balancer_data.hostname) -- could be nil
|
|
proxy_span.ip = balancer_data.ip
|
|
proxy_span.port = balancer_data.port
|
|
end
|
|
|
|
if subsystem == "http" then
|
|
request_span:set_tag("http.status_code", kong.response.get_status())
|
|
end
|
|
if ngx_ctx.authenticated_consumer then
|
|
request_span:set_tag("kong.consumer", ngx_ctx.authenticated_consumer.id)
|
|
end
|
|
if conf.include_credential and ngx_ctx.authenticated_credential then
|
|
request_span:set_tag("kong.credential", ngx_ctx.authenticated_credential.id)
|
|
end
|
|
request_span:set_tag("kong.node.id", kong.node.get_id())
|
|
|
|
tag_with_service_and_route(proxy_span)
|
|
|
|
proxy_span:finish(proxy_finish_mu)
|
|
reporter:report(proxy_span)
|
|
request_span:finish(now_mu)
|
|
reporter:report(request_span)
|
|
end
|
|
|
|
|
|
return ZipkinLogHandler
|