-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add inspect plugin #8400
feat: add inspect plugin #8400
Changes from 23 commits
0086938
485956f
25a8147
e7407a7
bcbfb26
0d8204e
a444068
f113090
2d16a2c
1767979
c9dca47
d1099c0
72dd417
cfd6e72
4156700
4d21630
0f3dcb1
a19b580
2a4aac6
cf0038c
9c4c58c
9792e4c
f4fe75f
483ff23
1916a2e
6245336
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
-- | ||
-- Licensed to the Apache Software Foundation (ASF) under one or more | ||
-- contributor license agreements. See the NOTICE file distributed with | ||
-- this work for additional information regarding copyright ownership. | ||
-- The ASF licenses this file to You under the Apache License, Version 2.0 | ||
-- (the "License"); you may not use this file except in compliance with | ||
-- the License. You may obtain a copy of the License at | ||
-- | ||
-- http://www.apache.org/licenses/LICENSE-2.0 | ||
-- | ||
-- Unless required by applicable law or agreed to in writing, software | ||
-- distributed under the License is distributed on an "AS IS" BASIS, | ||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
-- See the License for the specific language governing permissions and | ||
-- limitations under the License. | ||
-- | ||
local core = require("apisix.core") | ||
local string_format = string.format | ||
local debug = debug | ||
local ipairs = ipairs | ||
local pcall = pcall | ||
local table_insert = table.insert | ||
local jit = jit | ||
|
||
local _M = {} | ||
|
||
local hooks = {} | ||
|
||
function _M.getname(n) | ||
if n.what == "C" then | ||
return n.name | ||
end | ||
local lc = string_format("%s:%d", n.short_src, n.currentline) | ||
if n.what ~= "main" and n.namewhat ~= "" then | ||
return string_format("%s (%s)", lc, n.name) | ||
else | ||
return lc | ||
end | ||
end | ||
|
||
local function hook(evt, arg) | ||
local level = 2 | ||
local finfo = debug.getinfo(level, "nSlf") | ||
local key = finfo.source .. "#" .. arg | ||
|
||
local hooks2 = {} | ||
for _, hook in ipairs(hooks) do | ||
if key:sub(-#hook.key) == hook.key then | ||
local filter_func = hook.filter_func | ||
local info = {finfo = finfo, uv = {}, vals = {}} | ||
|
||
-- upvalues | ||
local i = 1 | ||
while true do | ||
local name, value = debug.getupvalue(finfo.func, i) | ||
if name == nil then break end | ||
if name:sub(1, 1) ~= "(" then | ||
info.uv[name] = value | ||
end | ||
i = i + 1 | ||
end | ||
|
||
-- local values | ||
local i = 1 | ||
while true do | ||
local name, value = debug.getlocal(level, i) | ||
if not name then break end | ||
if name:sub(1, 1) ~= "(" then | ||
info.vals[name] = value | ||
end | ||
i = i + 1 | ||
end | ||
|
||
local r1, r2_or_err = pcall(filter_func, info) | ||
if not r1 then | ||
core.log.error("inspect: pcall filter_func:", r2_or_err) | ||
spacewander marked this conversation as resolved.
Show resolved
Hide resolved
|
||
elseif r2_or_err == false then | ||
-- if filter_func returns false, keep the hook | ||
table_insert(hooks2, hook) | ||
end | ||
else | ||
-- key not match, keep the hook | ||
table_insert(hooks2, hook) | ||
end | ||
end | ||
|
||
-- disable debug mode if all hooks done | ||
if #hooks2 ~= #hooks then | ||
hooks = hooks2 | ||
if #hooks == 0 then | ||
debug.sethook() | ||
end | ||
end | ||
end | ||
|
||
function _M.set_hook(file, line, func, filter_func) | ||
if file == nil then | ||
file = "=stdin" | ||
end | ||
|
||
local key = file .. "#" .. line | ||
table_insert(hooks, {key = key, filter_func = filter_func}) | ||
|
||
if jit then | ||
jit.flush(func) | ||
jit.off() | ||
end | ||
|
||
debug.sethook(hook, "l") | ||
end | ||
|
||
function _M.unset_hook(file, line) | ||
if file == nil then | ||
file = "=stdin" | ||
end | ||
|
||
local hooks2 = {} | ||
|
||
local key = file .. "#" .. line | ||
for i, hook in ipairs(hooks) do | ||
if hook.key ~= key then | ||
table_insert(hooks2, hook) | ||
end | ||
end | ||
|
||
if #hooks2 ~= #hooks then | ||
hooks = hooks2 | ||
if #hooks == 0 then | ||
debug.sethook() | ||
if jit then | ||
jit.on() | ||
end | ||
end | ||
end | ||
end | ||
|
||
function _M.unset_all() | ||
if #hooks > 0 then | ||
hooks = {} | ||
debug.sethook() | ||
if jit then | ||
jit.on() | ||
end | ||
end | ||
end | ||
|
||
function _M.hooks() | ||
return hooks | ||
end | ||
|
||
return _M |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,127 @@ | ||||||
-- | ||||||
-- Licensed to the Apache Software Foundation (ASF) under one or more | ||||||
-- contributor license agreements. See the NOTICE file distributed with | ||||||
-- this work for additional information regarding copyright ownership. | ||||||
-- The ASF licenses this file to You under the Apache License, Version 2.0 | ||||||
-- (the "License"); you may not use this file except in compliance with | ||||||
-- the License. You may obtain a copy of the License at | ||||||
-- | ||||||
-- http://www.apache.org/licenses/LICENSE-2.0 | ||||||
-- | ||||||
-- Unless required by applicable law or agreed to in writing, software | ||||||
-- distributed under the License is distributed on an "AS IS" BASIS, | ||||||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
-- See the License for the specific language governing permissions and | ||||||
-- limitations under the License. | ||||||
-- | ||||||
local core = require("apisix.core") | ||||||
local dbg = require("apisix.inspect.dbg") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What‘s There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Abbreviation for "debug". Also to avoid conflict with the built-in Lua debug module. |
||||||
local lfs = require("lfs") | ||||||
local pl_path = require("pl.path") | ||||||
local io = io | ||||||
local table_insert = table.insert | ||||||
local pcall = pcall | ||||||
local ipairs = ipairs | ||||||
local os = os | ||||||
local ngx = ngx | ||||||
local loadstring = loadstring | ||||||
|
||||||
local _M = {} | ||||||
|
||||||
local last_modified = 0 | ||||||
|
||||||
local stop = false | ||||||
|
||||||
local running = false | ||||||
|
||||||
local last_report_time = 0 | ||||||
|
||||||
local REPORT_INTERVAL = 30 -- secs | ||||||
|
||||||
local function run_lua_file(file) | ||||||
local f, err = io.open(file, "rb") | ||||||
if not f then | ||||||
return false, err | ||||||
end | ||||||
local code = f:read("*all") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's do some err check? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about your opinion on this? |
||||||
f:close() | ||||||
if code == nil then | ||||||
return false, "cannot read hooks file" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can improve the error message here by adding the https://github.com/LuaJIT/LuaJIT/blob/8625eee71f16a3a780ec92bc303c17456efc7fb3/src/lib_aux.c#L39 |
||||||
end | ||||||
local func, err = loadstring(code) | ||||||
if not func then | ||||||
return false, err | ||||||
end | ||||||
func() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Already did so in the outer function. |
||||||
return true | ||||||
end | ||||||
|
||||||
local function setup_hooks(file) | ||||||
if pl_path.exists(file) then | ||||||
dbg.unset_all() | ||||||
local _, err = pcall(run_lua_file, file) | ||||||
local hooks = {} | ||||||
for _, hook in ipairs(dbg.hooks()) do | ||||||
table_insert(hooks, hook.key) | ||||||
end | ||||||
core.log.info("set hooks: err=", err, ", hooks=", core.json.encode(hooks)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
is better? |
||||||
end | ||||||
end | ||||||
|
||||||
local function reload_hooks(premature, delay, file) | ||||||
if premature or stop then | ||||||
stop = false | ||||||
running = false | ||||||
return | ||||||
end | ||||||
|
||||||
local time, err = lfs.attributes(file, 'modification') | ||||||
if err then | ||||||
if last_modified ~= 0 then | ||||||
core.log.info(err, ", disable all hooks") | ||||||
dbg.unset_all() | ||||||
last_modified = 0 | ||||||
end | ||||||
elseif time ~= last_modified then | ||||||
setup_hooks(file) | ||||||
last_modified = time | ||||||
else | ||||||
local ts = os.time() | ||||||
if ts - last_report_time >= REPORT_INTERVAL then | ||||||
local hooks = {} | ||||||
for _, hook in ipairs(dbg.hooks()) do | ||||||
table_insert(hooks, hook.key) | ||||||
end | ||||||
core.log.info("alive hooks: ", core.json.encode(hooks)) | ||||||
last_report_time = ts | ||||||
end | ||||||
end | ||||||
|
||||||
local ok, err = ngx.timer.at(delay, reload_hooks, delay, file) | ||||||
if not ok then | ||||||
core.log.error("failed to create the timer: ", err) | ||||||
running = false | ||||||
end | ||||||
end | ||||||
|
||||||
function _M.init(delay, file) | ||||||
if not running then | ||||||
file = file or "/var/run/apisix_inspect_hooks.lua" | ||||||
delay = delay or 3 | ||||||
|
||||||
setup_hooks(file) | ||||||
|
||||||
local ok, err = ngx.timer.at(delay, reload_hooks, delay, file) | ||||||
if not ok then | ||||||
core.log.error("failed to create the timer: ", err) | ||||||
return | ||||||
end | ||||||
running = true | ||||||
end | ||||||
end | ||||||
|
||||||
function _M.destroy() | ||||||
stop = true | ||||||
end | ||||||
|
||||||
return _M |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
-- | ||
-- Licensed to the Apache Software Foundation (ASF) under one or more | ||
-- contributor license agreements. See the NOTICE file distributed with | ||
-- this work for additional information regarding copyright ownership. | ||
-- The ASF licenses this file to You under the Apache License, Version 2.0 | ||
-- (the "License"); you may not use this file except in compliance with | ||
-- the License. You may obtain a copy of the License at | ||
-- | ||
-- http://www.apache.org/licenses/LICENSE-2.0 | ||
-- | ||
-- Unless required by applicable law or agreed to in writing, software | ||
-- distributed under the License is distributed on an "AS IS" BASIS, | ||
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
-- See the License for the specific language governing permissions and | ||
-- limitations under the License. | ||
-- | ||
local core = require("apisix.core") | ||
local plugin = require("apisix.plugin") | ||
local inspect = require("apisix.inspect") | ||
|
||
|
||
local plugin_name = "inspect" | ||
|
||
|
||
local schema = { | ||
type = "object", | ||
properties = {}, | ||
} | ||
|
||
|
||
local _M = { | ||
version = 0.1, | ||
priority = 200, | ||
name = plugin_name, | ||
schema = schema, | ||
} | ||
|
||
|
||
function _M.check_schema(conf, schema_type) | ||
return core.schema.check(schema, conf) | ||
end | ||
|
||
|
||
function _M.init() | ||
local attr = plugin.plugin_attr(plugin_name) | ||
spacewander marked this conversation as resolved.
Show resolved
Hide resolved
|
||
local delay | ||
local hooks_file | ||
if attr then | ||
delay = attr.delay | ||
hooks_file = attr.hooks_file | ||
end | ||
core.log.info("delay=", delay, ", hooks_file=", hooks_file) | ||
return inspect.init(delay, hooks_file) | ||
end | ||
|
||
|
||
function _M.destroy() | ||
return inspect.destroy() | ||
end | ||
|
||
return _M |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -461,6 +461,7 @@ plugins: # plugin list (sorted by priority) | |
- file-logger # priority: 399 | ||
- clickhouse-logger # priority: 398 | ||
- tencent-cloud-cls # priority: 397 | ||
- inspect # priority: 200 | ||
#- log-rotate # priority: 100 | ||
# <- recommend to use priority (0, 100) for your custom plugins | ||
- example-plugin # priority: 0 | ||
|
@@ -555,6 +556,9 @@ plugin_attr: | |
send: 60s | ||
# redirect: | ||
# https_port: 8443 # the default port for use by HTTP redirects to HTTPS | ||
inspect: | ||
delay: 3 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3s or 3 mins? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3 seconds, I would add a comment for this line. |
||
hooks_file: "/var/run/apisix_inspect_hooks.lua" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this file for? Has Lua code in it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @moonming yes, this hooks file is used to define breakpoints, which itself is lua file. When the administrator needs to set breakpoints to inspect something, he would edit this file, or link another file to this path. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Normally this file is non-exist. It's only available when you need to set breakpoints. Please refer to the plugin doc. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are security risks. |
||
|
||
deployment: | ||
role: traditional | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
evt
unused inhook
function?