Skip to content
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

Merged
merged 26 commits into from
Dec 16, 2022
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,9 @@ install: runtime
$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/include/apisix/model
$(ENV_INSTALL) apisix/include/apisix/model/*.proto $(ENV_INST_LUADIR)/apisix/include/apisix/model/

$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/inspect
$(ENV_INSTALL) apisix/inspect/*.lua $(ENV_INST_LUADIR)/apisix/inspect/

$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins
$(ENV_INSTALL) apisix/plugins/*.lua $(ENV_INST_LUADIR)/apisix/plugins/

Expand Down
151 changes: 151 additions & 0 deletions apisix/inspect/dbg.lua
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

evt unused in hook function?

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
127 changes: 127 additions & 0 deletions apisix/inspect/init.lua
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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What‘s dbg means?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do some err check?

Copy link
Member

Choose a reason for hiding this comment

The 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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can improve the error message here by adding the err returned from the f:read above.

https://github.com/LuaJIT/LuaJIT/blob/8625eee71f16a3a780ec92bc303c17456efc7fb3/src/lib_aux.c#L39
The second returned value is a string describing the err.

end
local func, err = loadstring(code)
if not func then
return false, err
end
func()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need use pcall(func()) here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
core.log.info("set hooks: err=", err, ", hooks=", core.json.encode(hooks))
core.log.info("set hooks: err: ", err, ", hooks: ", core.json.delay_encode(hooks))

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
61 changes: 61 additions & 0 deletions apisix/plugins/inspect.lua
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
4 changes: 4 additions & 0 deletions conf/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3s or 3 mins?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this file for? Has Lua code in it?

Copy link
Contributor Author

@kingluo kingluo Dec 15, 2022

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

@kingluo kingluo Dec 15, 2022

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are security risks.
This Lua code file must be in the APISIX directory, and users who do not have write permissions to the APISIX directory cannot write or modify this file.


deployment:
role: traditional
Expand Down
Binary file added docs/assets/images/plugin/inspect.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion docs/en/latest/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
"plugins/server-info",
"plugins/ext-plugin-pre-req",
"plugins/ext-plugin-post-req",
"plugins/ext-plugin-post-resp"
"plugins/ext-plugin-post-resp",
"plugins/inspect"
]
},
{
Expand Down
Loading