213 lines
5.1 KiB
Lua
213 lines
5.1 KiB
Lua
local _M = {
|
|
maybe_push = function() end,
|
|
get_request_logs = function() return {} end,
|
|
get_worker_logs = function() return {} end,
|
|
}
|
|
|
|
if ngx.config.subsystem ~= "http" then
|
|
return _M
|
|
end
|
|
|
|
|
|
local request_id_get = require "kong.observability.tracing.request_id".get
|
|
local time_ns = require "kong.tools.time".time_ns
|
|
local table_merge = require "kong.tools.table".table_merge
|
|
local deep_copy = require "kong.tools.table".deep_copy
|
|
|
|
local get_log_level = require "resty.kong.log".get_log_level
|
|
local constants_log_levels = require "kong.constants".LOG_LEVELS
|
|
|
|
local table_new = require "table.new"
|
|
local string_buffer = require "string.buffer"
|
|
|
|
local ngx = ngx
|
|
local kong = kong
|
|
local table = table
|
|
local tostring = tostring
|
|
local native_ngx_log = _G.native_ngx_log or ngx.log
|
|
|
|
local ngx_null = ngx.null
|
|
local table_pack = table.pack -- luacheck: ignore
|
|
|
|
local MAX_WORKER_LOGS = 1000
|
|
local MAX_REQUEST_LOGS = 1000
|
|
local INITIAL_SIZE_WORKER_LOGS = 100
|
|
local NGX_CTX_REQUEST_LOGS_KEY = "o11y_logs_request_scoped"
|
|
|
|
local worker_logs = table_new(INITIAL_SIZE_WORKER_LOGS, 0)
|
|
local logline_buf = string_buffer.new()
|
|
|
|
local notified_buffer_full = false
|
|
|
|
|
|
-- WARNING: avoid using `ngx.log` in this function to prevent recursive loops
|
|
local function configured_log_level()
|
|
local ok, level = pcall(get_log_level)
|
|
if not ok then
|
|
-- This is unexpected outside of the context of unit tests
|
|
local level_str = kong.configuration.log_level
|
|
native_ngx_log(ngx.WARN,
|
|
"[observability] OpenTelemetry logs failed reading dynamic log level. " ..
|
|
"Using log level: " .. level_str .. " from configuration."
|
|
)
|
|
level = constants_log_levels[level_str]
|
|
end
|
|
|
|
return level
|
|
end
|
|
|
|
|
|
-- needed because table.concat doesn't like booleans
|
|
local function concat_tostring(tab)
|
|
local tab_len = #tab
|
|
if tab_len == 0 then
|
|
return ""
|
|
end
|
|
|
|
for i = 1, tab_len do
|
|
local value = tab[i]
|
|
|
|
if value == ngx_null then
|
|
value = "nil"
|
|
else
|
|
value = tostring(value)
|
|
end
|
|
|
|
logline_buf:put(value)
|
|
end
|
|
|
|
return logline_buf:get()
|
|
end
|
|
|
|
|
|
local function generate_log_entry(request_scoped, inj_attributes, log_level, log_str, request_id, debug_info)
|
|
|
|
local span_id
|
|
|
|
if request_scoped then
|
|
-- add tracing information if tracing is enabled
|
|
local active_span = kong and kong.tracing and kong.tracing.active_span()
|
|
if active_span then
|
|
span_id = active_span.span_id
|
|
end
|
|
end
|
|
|
|
local attributes = {
|
|
["request.id"] = request_id,
|
|
["introspection.current.line"] = debug_info.currentline,
|
|
["introspection.name"] = debug_info.name,
|
|
["introspection.namewhat"] = debug_info.namewhat,
|
|
["introspection.source"] = debug_info.source,
|
|
["introspection.what"] = debug_info.what,
|
|
}
|
|
if inj_attributes then
|
|
attributes = table_merge(attributes, inj_attributes)
|
|
end
|
|
|
|
local now_ns = time_ns()
|
|
return {
|
|
time_unix_nano = now_ns,
|
|
observed_time_unix_nano = now_ns,
|
|
log_level = log_level,
|
|
body = log_str,
|
|
attributes = attributes,
|
|
span_id = span_id,
|
|
}
|
|
end
|
|
|
|
|
|
local function get_request_log_buffer()
|
|
local log_buffer = ngx.ctx[NGX_CTX_REQUEST_LOGS_KEY]
|
|
if not log_buffer then
|
|
log_buffer = table_new(10, 0)
|
|
ngx.ctx[NGX_CTX_REQUEST_LOGS_KEY] = log_buffer
|
|
end
|
|
return log_buffer
|
|
end
|
|
|
|
|
|
-- notifies the user that the log buffer is full, once (per worker)
|
|
local function notify_buffer_full_once()
|
|
if not notified_buffer_full then
|
|
notified_buffer_full = true
|
|
native_ngx_log(ngx.NOTICE,
|
|
"[observability] OpenTelemetry logs buffer is full: dropping new log entries."
|
|
)
|
|
end
|
|
end
|
|
|
|
|
|
local function notify_if_resumed()
|
|
-- if we are in a "resumed" state
|
|
if notified_buffer_full then
|
|
notified_buffer_full = false
|
|
native_ngx_log(ngx.NOTICE,
|
|
"[observability] OpenTelemetry logs buffer resumed accepting log entries."
|
|
)
|
|
end
|
|
end
|
|
|
|
|
|
function _M.maybe_push(stack_level, attributes, log_level, ...)
|
|
-- WARNING: do not yield in this function, as it is called from ngx.log
|
|
|
|
-- Early return cases:
|
|
|
|
-- log level too low
|
|
if log_level and configured_log_level() < log_level then
|
|
return
|
|
end
|
|
|
|
local log_buffer, max_logs
|
|
local request_id = request_id_get()
|
|
local request_scoped = request_id ~= nil
|
|
|
|
-- get the appropriate log buffer depending on the current context
|
|
if request_scoped then
|
|
log_buffer = get_request_log_buffer()
|
|
max_logs = MAX_REQUEST_LOGS
|
|
|
|
else
|
|
log_buffer = worker_logs
|
|
max_logs = MAX_WORKER_LOGS
|
|
end
|
|
|
|
-- return if log buffer is full
|
|
if #log_buffer >= max_logs then
|
|
notify_buffer_full_once()
|
|
return
|
|
end
|
|
notify_if_resumed()
|
|
|
|
local args = table_pack(...)
|
|
local log_str = concat_tostring(args)
|
|
|
|
-- generate & push log entry
|
|
local debug_info = debug.getinfo(stack_level, "nSl")
|
|
local log_entry = generate_log_entry(
|
|
request_scoped,
|
|
attributes,
|
|
log_level,
|
|
log_str,
|
|
request_id,
|
|
debug_info
|
|
)
|
|
table.insert(log_buffer, log_entry)
|
|
end
|
|
|
|
|
|
function _M.get_worker_logs()
|
|
local wl = worker_logs
|
|
worker_logs = table_new(INITIAL_SIZE_WORKER_LOGS, 0)
|
|
return wl
|
|
end
|
|
|
|
|
|
function _M.get_request_logs()
|
|
local request_logs = get_request_log_buffer()
|
|
return deep_copy(request_logs)
|
|
end
|
|
|
|
|
|
return _M
|