Last active
September 7, 2023 03:15
-
-
Save chobits/33219d345710eb2d585fcb5295851dbc to your computer and use it in GitHub Desktop.
trace function call chains while reading the lua source
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Trace request context exclusively within Lua projects | |
-- executed by the Lua-nginx-module | |
-- | |
-- The timer context is not available within functions triggered by | |
-- ngx.timer.at(). However, you can easily make adjustments to this file to | |
-- enable it. | |
-- | |
-- In your lua source file: | |
-- require("bt").init_hook() | |
-- true : it displays the level number of the calling function. | |
-- false: it displays space-based indentation determined by the level number. | |
local DISPLAY_LEVEL_NUMBER = false | |
local _M = {} | |
function _M.init_hook() | |
if not ngx.ctx then | |
return nil | |
end | |
local _t = { | |
level = 0, | |
call_chains = {}, -- array[] | |
id = string.sub(ngx.var.request_id, 1, 8), | |
} | |
ngx.ctx._trace_info = _t | |
setmetatable(_M, {}) | |
return _t | |
end | |
local function trace_skip(info) | |
if not info or not info.name then | |
return true | |
end | |
-- skip inline function, such as print() | |
if info.source == "[C]" or info.currentline == -1 then | |
return true | |
end | |
-- skip myself | |
if string.find(info.source, "/bt.lua") or info.name == "init_hook" then | |
return true | |
end | |
-- not kong source (maybe required library) | |
if string.find(info.source, "/openresty/") | |
or string.find(info.source, "/lualib/") | |
or string.find(info.source, "/lua/5%.[123]/") | |
then | |
return true | |
end | |
return false | |
end | |
-- This function will be called whenever a function is called or returns | |
local function trace_hook(event) | |
-- skip timer | |
-- request context | |
if not ngx.ctx then | |
return | |
end | |
local _t = ngx.ctx._trace_info | |
if not _t then | |
return | |
end | |
local info = debug.getinfo(2, "nSl") | |
if trace_skip(info) then | |
return | |
end | |
-- evaluate | |
local func_name = info.name | |
local line_info = info.short_src .. ":" .. (info.currentline or "unknown") | |
local prefix, s | |
local level = _t.level | |
local event_prefix = ">" | |
if event == "call" then | |
_t.level = level + 1 | |
_t.call_chains[_t.level] = func_name | |
elseif event == "return" then | |
event_prefix = "< (no caller)" | |
-- fix that some function has no return event (e.g. __index [C]:-1) | |
local i = level | |
while i > 0 do | |
local orig_func_name = _t.call_chains[i] | |
i = i - 1 | |
-- print("[DD] call chain pop:", orig_func_name, " return func:", func_name) | |
if orig_func_name == func_name then | |
_t.level = i | |
level = i | |
event_prefix = "<" | |
break | |
end | |
end | |
else | |
event_prefix = "?" | |
end | |
if DISPLAY_LEVEL_NUMBER then | |
prefix = tostring(level) | |
else | |
prefix = string.rep(" ", level) | |
end | |
s = string.format("%s: %s %s %s() %s", | |
_t.id, prefix, event_prefix, func_name, line_info) | |
-- show | |
ngx.log(ngx.ERR, "\nHook: [" .. s .. "]\n") | |
-- print(s) | |
end | |
local function init() | |
debug.sethook(trace_hook, "cr") | |
--[[ | |
if jit then | |
jit.off() | |
end | |
]] | |
end | |
init() | |
return _M |
- TODO: Some inline C functions (or other situation) may not trigger a return event. We are actively working to address this issue within the call chain to ensure a balanced call/return depth level. However, especially when there are consecutive calls to inline functions, which makes call depth level increases.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
For kong project, how to use
Description of Log Fields
result from nginx error.log