{"id":21970812,"url":"https://github.com/kingluo/lua-resty-inspect","last_synced_at":"2025-04-28T11:41:08.082Z","repository":{"id":70272657,"uuid":"529875607","full_name":"kingluo/lua-resty-inspect","owner":"kingluo","description":"make dynamic and arbitrary breakpoint in your nginx lua code and inspect the info","archived":false,"fork":false,"pushed_at":"2023-12-18T18:21:43.000Z","size":78,"stargazers_count":16,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-30T09:11:35.583Z","etag":null,"topics":["debug","inspect","lua","luajit","nginx","openresty"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kingluo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-28T13:48:55.000Z","updated_at":"2024-05-29T00:11:34.000Z","dependencies_parsed_at":"2024-11-29T22:15:19.739Z","dependency_job_id":null,"html_url":"https://github.com/kingluo/lua-resty-inspect","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kingluo%2Flua-resty-inspect","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kingluo%2Flua-resty-inspect/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kingluo%2Flua-resty-inspect/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kingluo%2Flua-resty-inspect/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kingluo","download_url":"https://codeload.github.com/kingluo/lua-resty-inspect/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251306617,"owners_count":21568304,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["debug","inspect","lua","luajit","nginx","openresty"],"created_at":"2024-11-29T14:43:12.404Z","updated_at":"2025-04-28T11:41:08.061Z","avatar_url":"https://github.com/kingluo.png","language":"Lua","readme":"# lua-resty-inspect\n\nIt's useful to set arbitrary breakpoint in any lua file to inspect the context information,\ne.g. print local variables if some condition satisfied.\n\nIn this way, you don't need to modify the source code of your project, and just get diagnose information\non demand, i.e. dynamic logging.\n\n**Why not modify the source code to debug?**\n\n* The production environment doesn't allow to modify the code, and you should not to.\n* bad code changes would pollute the business source code.\n* You need to reload nginx to make code changes take effect, which ruins the online business.\n* It's difficult to change code in container.\n* It's easy to forget rolling back the changes after debug. And it's hard to roll back completely.\n\nThis library supports setting breakpoints within both interpretd function and jit compiled function.\nThe breakpoint could be at any position within the function. The function could be global/local/module/ananymous.\n\nIt works for luajit2.1 or lua5.1.\n\nThis library has been used to implement inspect plugin of APISIX:\n\nhttps://apisix.apache.org/zh/docs/apisix/plugins/inspect/\n\n## Use cases\n\n* locate the issue source\n* print some masked logs\n* learn from huge source code via debugging\n\n## Features\n\n* non-intrusive, bad code in the breakpint doesn't affect the business source code\n* set breakpoint at any position\n* dynamic breakpoint\n* Customized breakpoint handler\n* you could define one-shot breakpoint\n* work for jit compiled function\n* if function reference specified, then performance impact is only bound to that function (JIT compiled code will not trigger debug hook, so they would run fast even if hook is enabled)\n* if all breakpoints deleted, jit could recover\n\n## Operation Graph\n\n![1666251376616](https://user-images.githubusercontent.com/4401042/196885851-d883a7fb-0ab5-45b1-987f-7b6d810748f5.png)\n\n## API\n\n### require(\"resty.inspect.dbg\").set_hook(file, line, func, filter_func)\n\nThe breakpoint is specified by `file` (full qualified or short file name) and the `line` number.\n\nThe `func` specified the scope (which function or global) of jit cache to flush:\n* If the breakpoint is related to a module function or\nglobal function, you should set it that function reference, then only the jit cache of that function would\nbe flushed, and it would not affect other caches to avoid slowing down other parts of the program.\n* If the breakpointis related to local function or anonymous function,\nthen you have to set it to `nil` (because no way to get function reference), which would flush the whole jit cache of lua vm.\n\nYou attach a `filter_func` function of the breakpoint, the function takes the `info` as argument and returns\ntrue of false to determine whether the breakpoint would be removed. You could setup one-shot breakpoint\nat ease.\n\nThe `info` is a hash table which contains below keys:\n\n* `finfo`: return value of `debug.getinfo(level, \"nSlf\")`\n* `uv`: upvalues hash table\n* `vals`: local variables hash table\n\n### require(\"resty.inspect.dbg\").unset_hook(file, line)\n\nremove the specific breakpoint.\n\n### require(\"resty.inspect\").init(delay, file)\n\nSetup a timer (in `delay` interval, in seconds) to monitor specific `file` to setup the needed breakpoints. You could modify that file\nto configure breakpoints on-the-fly. Delete that file would unset all breakpoints. It's recommanded to use soft link file trick.\n\n### require(\"resty.inspect\").destroy()\n\nDestroy the monitor timer.\n\n## Synopsis\n\n```lua\n-- example_hooks.lua\nlocal dbg = require \"resty.inspect.dbg\"\n\n-- print stack backtrace and variable `conf_key`\ndbg.set_hook(\"limit-req.lua\", 88, require(\"apisix.plugins.limit-req\").access,\nfunction(info)\n    ngx.log(ngx.INFO, debug.traceback(\"foo traceback\", 3))\n    ngx.log(ngx.INFO, dbg.getname(info.finfo))\n    ngx.log(ngx.INFO, \"conf_key=\", info.vals.conf_key)\n    return true\nend)\n\n-- send info to remote server if var `i` is 222\ndbg.set_hook(\"t/lib/demo.lua\", 31, require(\"t.lib.demo\").hot2, function(info)\n    if info.vals.i == 222 then\n        ngx.timer.at(0, function(_, body)\n            local httpc = require(\"resty.http\").new()\n            httpc:request_uri(\"http://127.0.0.1:9080/upstream1\", {\n                method = \"POST\",\n                body = body,\n            })\n        end, ngx.var.request_uri .. \",\" .. info.vals.i)\n        return true\n    end\n    return false\nend)\n\n-- get worker id only if `sync_data` is one of the function callers\ndbg.set_hook(\"apisix/upstream.lua\", 506, nil, function(info)\n    if debug.traceback(\"demo traceback\", 3):find(\"sync_data\") then\n        ngx.log(ngx.WARN, \"ngx.worker.id=\", ngx.worker.id())\n        return true\n    end\n    return false\nend)\n\n-- check when new route configuration get updated by data plane\nlocal cjson = require(\"cjson\")\ndbg.set_hook(\"apisix/core/config_etcd.lua\", 393, nil, function(info)\n    local filter_res = \"/routes\"\n    if info.vals.self.key:sub(-#filter_res) == filter_res and not info.vals.err then\n        ngx.log(ngx.WARN, \"etcd watch /routes response: \", cjson.encode(info.vals.dir_res))\n        return true\n    end\n    return false\nend)\n```\n\n**The breakpoint handler could be arbitrary lua code, so you could do anything there, not\njust printing logs.**\n\n**But, note that async code, e.g. cosocket, should not run in the breakpoint directly,\nbecause the execution of the breakpint handler function is synchronous and blocking,\nand you have no chance to return to the nginx event loop until completion.**\n\n**Instead, you could use a timer to postpone the async code, just like what I did above.**\n\n## Caveats\n\nTo setup breakpoint within jit compiled function, it needs to flush the jit cache first.\n\nDepending on the scope of jit cache flush, if you only flush the specific function cache, then it only\nslows down that function execution, otherwise, it would slow down the whole jit cache of lua vm.\n\nWhen the breakpoints are enabled, the lua vm could not trace new hot paths and compile them.\n\nBut when the breakpoints disappear, the lua vm would recover jit tracing.\n\nConservatively, this library is mainly useful for functional debug without stress.\n\n## Example\n\n### 1. example/nginx.conf\n\n```nginx\nerror_log /dev/stderr info;\nworker_processes auto;\n\nevents {}\n\nhttp {\n    lua_package_path '/opt/lua-resty-inspect/example/?.lua;/opt/lua-resty-inspect/lib/?.lua;/opt/lua-resty-inspect/lib/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/share/lua/5.1/?.lua;;';\n    lua_package_cpath '/usr/local/lib/lua/5.1/?.so;;';\n\n    init_worker_by_lua_block {\n        local inspect = require \"resty.inspect\"\n        inspect.init(1, \"/opt/lua-resty-inspect/example/resty_inspect_hooks.lua\")\n    }\n\n    server {\n        listen 10000;\n\n        location / {\n            content_by_lua_block {\n                local hot = require(\"hot\")\n\n                -- hook enabled, jit flush, slow\n                hot.timeit(hot.foo, \"foo\")\n                hot.run_bar()\n\n                -- hook removed, tracing\n                hot.timeit(hot.foo, \"foo\")\n                hot.run_bar()\n\n                -- jit, fast\n                hot.timeit(hot.foo, \"foo\")\n                hot.run_bar()\n\n                ngx.say(\"ok\")\n            }\n        }\n    }\n}\n\n```\n\n### 2. example/resty_inspect_hooks1.lua\n\n```lua\nlocal dbg = require \"resty.inspect.dbg\"\nlocal cjson = require \"cjson\"\nlocal hot = require \"hot\"\n\n-- short file name\n-- foo is module function, so here we just flush jit cache of foo function\ndbg.set_hook(\"hot.lua\", 9, hot.foo, function(info)\n    if info.vals.i == 100 then\n        ngx.log(ngx.INFO, debug.traceback(\"foo traceback\", 3))\n        ngx.log(ngx.INFO, dbg.getname(info.finfo))\n        ngx.log(ngx.INFO, cjson.encode(info.vals))\n        ngx.log(ngx.INFO, cjson.encode(info.uv))\n        return true\n    end\n    return false\nend)\n\n```\n\n### 3. example/resty_inspect_hooks2.lua\n\n```lua\nlocal dbg = require \"resty.inspect.dbg\"\nlocal cjson = require \"cjson\"\nlocal hot = require \"hot\"\n\n-- short file name\n-- foo is module function, so here we just flush jit cache of foo function\ndbg.set_hook(\"hot.lua\", 9, hot.foo, function(info)\n    if info.vals.i == 100 then\n        ngx.log(ngx.INFO, debug.traceback(\"foo traceback\", 3))\n        ngx.log(ngx.INFO, dbg.getname(info.finfo))\n        ngx.log(ngx.INFO, cjson.encode(info.vals))\n        ngx.log(ngx.INFO, cjson.encode(info.uv))\n        return true\n    end\n    return false\nend)\n\n-- more specific file name\n-- bar is local function, so it have to flush the whole jit cache\ndbg.set_hook(\"example/hot.lua\", 17, nil, function(info)\n    if info.vals.i == 99 then\n        ngx.log(ngx.INFO, debug.traceback(\"bar traceback\", 3))\n        ngx.log(ngx.INFO, dbg.getname(info.finfo))\n        return true\n    end\n    return false\nend)\n\n```\n\n### 4. test\n\n**Setup the test env:**\n\n```bash\nluarocks install cjson socket lfs\ncd /opt\ngit clone https://github.com/kingluo/lua-resty-inspect\ncd /opt/lua-resty-inspect\n/usr/local/openresty-debug/nginx/sbin/nginx -p /opt/lua-resty-inspect/example/ -c nginx.conf -g \"daemon off;\"\n```\n\n**Nginx log output explanation:**\n\n```bash\n### no breakpoints initially, because the breakpoints file resty_inspect_hooks.lua not exist\n\n### setup foo breakpoint\n\nln -sf /opt/lua-resty-inspect/example/resty_inspect_hooks1.lua /opt/lua-resty-inspect/example/resty_inspect_hooks.lua\n\n2022/08/28 21:44:12 [info] 2688226#2688226: *31 [lua] init.lua:29: setup_hooks(): set hooks: err=nil, hooks=[\"hot.lua#9\"], context: ngx.timer\n\n### trigger the hot.lua execution\n\ncurl localhost:10000/get\n\n### breakpoint on foo() function triggered\n### print related information around the breakpoint\n\n2022/08/28 21:44:28 [info] 2688226#2688226: *47 [lua] resty_inspect_hooks.lua:9: foo traceback\nstack traceback:\n         /opt/lua-resty-inspect/lib/resty/inspect/dbg.lua:50: in function '__index'\n         /opt/lua-resty-inspect/example/hot.lua:9: in function 'func'\n         /opt/lua-resty-inspect/example/hot.lua:24: in function 'timeit'\n         content_by_lua(nginx.conf:35):5: in main chunk, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"localhost:10000\"\n2022/08/28 21:44:28 [info] 2688226#2688226: *47 [lua] resty_inspect_hooks.lua:10: /opt/lua-resty-inspect/example/hot.lua:9 (func), client: 127.0.0.1, server: , request:\n  \"GET /get HTTP/1.1\", host: \"localhost:10000\"\n2022/08/28 21:44:28 [info] 2688226#2688226: *47 [lua] resty_inspect_hooks.lua:11: {\"i\":100,\"j\":1}, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"localhost:10000\"\n2022/08/28 21:44:28 [info] 2688226#2688226: *47 [lua] resty_inspect_hooks.lua:12: {\"s\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaall\"}, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"localhost:10000\"\n\n\n### the breakpoint only affect the jit cache of foo, so you could see that the foo execution is slow,\n### but the bar function is still fast\n\n2022/08/28 21:44:28 [info] 2688226#2688226: *47 [lua] hot.lua:26: timeit(): timeit foo: 81.11 msecs, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"l\nocalhost:10000\"\n2022/08/28 21:44:28 [info] 2688226#2688226: *47 [lua] hot.lua:26: timeit(): timeit bar: 0.12 msecs, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"lo\ncalhost:10000\"\n\n\n### the breakpoint is setup to be one-shot, so you could see that the latter foo would redo the jit tracing, and becomes fast again\n\n\n2022/08/28 21:44:28 [info] 2688226#2688226: *47 [lua] hot.lua:26: timeit(): timeit foo: 0.56 msecs, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"localhost:10000\"\n2022/08/28 21:44:28 [info] 2688226#2688226: *47 [lua] hot.lua:26: timeit(): timeit bar: 0.04 msecs, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"localhost:10000\"\n2022/08/28 21:44:28 [info] 2688226#2688226: *47 [lua] hot.lua:26: timeit(): timeit foo: 0.05 msecs, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"localhost:10000\"\n2022/08/28 21:44:28 [info] 2688226#2688226: *47 [lua] hot.lua:26: timeit(): timeit bar: 0.04 msecs, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"localhost:10000\"\n2022/08/28 21:44:28 [info] 2688226#2688226: *48 [lua] init.lua:57: alive hooks: {}, context: ngx.timer\n2022/08/28 21:44:33 [info] 2688226#2688226: *53 [lua] init.lua:57: alive hooks: {}, context: ngx.timer\n2022/08/28 21:44:38 [info] 2688226#2688226: *58 [lua] init.lua:57: alive hooks: {}, context: ngx.timer\n2022/08/28 21:44:43 [info] 2688226#2688226: *63 [lua] init.lua:57: alive hooks: {}, context: ngx.timer\n2022/08/28 21:44:48 [info] 2688226#2688226: *68 [lua] init.lua:57: alive hooks: {}, context: ngx.timer\n\n\n##\n## setup two breakpoints again\n##\n\nln -sf /opt/lua-resty-inspect/example/resty_inspect_hooks2.lua /opt/lua-resty-inspect/example/resty_inspect_hooks.lua\n\n##\n## breakpoints enabled\n##\n\n2022/08/28 21:44:49 [info] 2688226#2688226: *69 [lua] init.lua:29: setup_hooks(): set hooks: err=nil, hooks=[\"hot.lua#9\",\"example\\/hot.lua#17\"], context: ngx.timer\n\n##\n## The bar breakpoint clears the whole jit cache, so you could see that it slows down everything\n##\n\n2022/08/28 21:45:03 [info] 2688226#2688226: *84 [lua] resty_inspect_hooks.lua:9: foo traceback\nstack traceback:\n        /opt/lua-resty-inspect/lib/resty/inspect/dbg.lua:50: in function '__index'\n        /opt/lua-resty-inspect/example/hot.lua:9: in function 'func'\n        /opt/lua-resty-inspect/example/hot.lua:24: in function 'timeit'\n        content_by_lua(nginx.conf:35):5: in main chunk, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"localhost:10000\"\n2022/08/28 21:45:03 [info] 2688226#2688226: *84 [lua] resty_inspect_hooks.lua:10: /opt/lua-resty-inspect/example/hot.lua:9 (func), client: 127.0.0.1, server: , request:\n \"GET /get HTTP/1.1\", host: \"localhost:10000\"\n2022/08/28 21:45:03 [info] 2688226#2688226: *84 [lua] resty_inspect_hooks.lua:11: {\"i\":100,\"j\":1}, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"loc\nalhost:10000\"\n2022/08/28 21:45:03 [info] 2688226#2688226: *84 [lua] resty_inspect_hooks.lua:12: {\"s\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaall\"}, client: 12\n7.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"localhost:10000\"\n2022/08/28 21:45:03 [info] 2688226#2688226: *84 [lua] hot.lua:26: timeit(): timeit foo: 68.49 msecs, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"l\nocalhost:10000\"\n2022/08/28 21:45:03 [info] 2688226#2688226: *84 [lua] resty_inspect_hooks.lua:22: bar traceback\nstack traceback:\n        /opt/lua-resty-inspect/lib/resty/inspect/dbg.lua:50: in function '__index'\n        /opt/lua-resty-inspect/example/hot.lua:17: in function 'func'\n        /opt/lua-resty-inspect/example/hot.lua:24: in function 'timeit'\n        /opt/lua-resty-inspect/example/hot.lua:30: in function 'run_bar'\n        content_by_lua(nginx.conf:35):6: in main chunk, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"localhost:10000\"\n2022/08/28 21:45:03 [info] 2688226#2688226: *84 [lua] resty_inspect_hooks.lua:23: /opt/lua-resty-inspect/example/hot.lua:17 (func), client: 127.0.0.1, server: , request\n: \"GET /get HTTP/1.1\", host: \"localhost:10000\"\n2022/08/28 21:45:03 [info] 2688226#2688226: *84 [lua] hot.lua:26: timeit(): timeit bar: 63.60 msecs, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"l\nocalhost:10000\"\n2022/08/28 21:45:03 [info] 2688226#2688226: *84 [lua] hot.lua:26: timeit(): timeit foo: 0.64 msecs, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"lo\ncalhost:10000\"\n2022/08/28 21:45:03 [info] 2688226#2688226: *84 [lua] hot.lua:26: timeit(): timeit bar: 0.11 msecs, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"lo\ncalhost:10000\"\n2022/08/28 21:45:03 [info] 2688226#2688226: *84 [lua] hot.lua:26: timeit(): timeit foo: 0.07 msecs, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"lo\ncalhost:10000\"\n2022/08/28 21:45:03 [info] 2688226#2688226: *84 [lua] hot.lua:26: timeit(): timeit bar: 0.03 msecs, client: 127.0.0.1, server: , request: \"GET /get HTTP/1.1\", host: \"lo\ncalhost:10000\"\n2022/08/28 21:45:08 [info] 2688226#2688226: *89 [lua] init.lua:57: alive hooks: {}, context: ngx.timer\n\n##\n## remove hooks\n##\n\nrm -f /opt/lua-resty-inspect/example/resty_inspect_hooks.lua\n\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkingluo%2Flua-resty-inspect","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkingluo%2Flua-resty-inspect","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkingluo%2Flua-resty-inspect/lists"}