Skip to content

Commit

Permalink
feat(emmy-lua-debugger): support busted & multi worker debugging + docs
Browse files Browse the repository at this point in the history
  • Loading branch information
hanshuebner committed May 27, 2024
1 parent b499955 commit ecf4314
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 31 deletions.
31 changes: 29 additions & 2 deletions DEVELOPER.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ The [EmmyLuaDebugger](https://github.com/EmmyLua/EmmyLuaDebugger) is a standalon
that runs on the same machine as Kong Gateway and that mediates between the IDE's
debugger and the Lua code running in Kong Gateway. It can be downloaded from
[GitHub](https://github.com/EmmyLua/EmmyLuaDebugger/releases). The release
ZIP file contains a single share library named emmy_core.so (Linux) or emmy_core.dylib (macOS).
ZIP file contains a single shared library named emmy_core.so (Linux) or emmy_core.dylib (macOS).
Place this file in a directory that is convenient for you and remember the path.

Depending on your Linux version, you may need to compile
Expand All @@ -486,7 +486,7 @@ recent version of GLIBC to be present.
To enable the EmmyLua debugger, the `KONG_EMMY_DEBUGGER` environment variable must be set to
the absolute path of the debugger shared library file when Kong Gateway is started. It is
also advisable to start Kong Gateway with only one worker process, as debugging multiple worker
processes is not supported. For example:
processes requires special care. For example:

```shell
KONG_EMMY_DEBUGGER=/path/to/emmy_core.so KONG_NGINX_WORKER_PROCESSES=1 kong start
Expand Down Expand Up @@ -515,6 +515,33 @@ a breakpoint in the global `access` function that is defined `runloop/handler.lu
a proxy request to the Gateway. The debugger should stop at the breakpoint and you can
inspect the variables in the request context.

### Debugging `busted` tests

To debug `busted` tests, you can set the `BUSTED_EMMY_DEBUGGER` environment variable to the path
to the EmmyLua debugger shared library. When debugging is enabled, `busted` will always wait for
the IDE to connect during startup.

### Debugging environment variables

The following environment variables can be set to control the behavior of the EmmyLua debugger
integration:

- `KONG_EMMY_DEBUGGER`: The path to the EmmyLua debugger shared library.
- `KONG_EMMY_DEBUGGER_HOST`: The IP address that the EmmyLua debugger will listen on. The default
is `localhost`.
- `KONG_EMMY_DEBUGGER_PORT`: The port that the EmmyLua debugger will listen on. The default is
`9966`.
- `KONG_EMMY_DEBUGGER_WAIT`: If set, Kong Gateway will wait for the debugger to connect
before starting continuing to start.
- `KONG_EMMY_DEBUGGER_SOURCE_PATH`: The path to the source code that the EmmyLua debugger will
use to resolve source code locations. The default is the current working directory.
- `KONG_EMMY_DEBUGGER_MULTI_WORKER`: If set, a debugger will be started for each worker process, using
incrementing port numbers starting at `KONG_EMMY_DEBUGGER_PORT`. The default is to start
only one debugger for worker zero.

To control debugger behavior while running `busted` tests, a similar set of environment variables
prefixed with `BUSTED_` instead of `KONG_` can be used.

## What's next

- Refer to the [Kong Gateway Docs](https://docs.konghq.com/gateway/) for more information.
Expand Down
13 changes: 13 additions & 0 deletions bin/busted
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ local pl_file = require("pl.file")

local tools_system = require("kong.tools.system")

local emmy_debugger = require("kong.tools.emmy_debugger")

local cert_path do
local busted_cert_file = pl_path.tmpname()
local busted_cert_content = pl_file.read("spec/fixtures/kong_spec.crt")
Expand Down Expand Up @@ -72,6 +74,16 @@ end

pcall(require, "luarocks.loader")

if os.getenv("BUSTED_EMMY_DEBUGGER") then
emmy_debugger.init({
debugger = os.getenv("BUSTED_EMMY_DEBUGGER"),
host = os.getenv("BUSTED_EMMY_DEBUGGER_HOST"),
port = os.getenv("BUSTED_EMMY_DEBUGGER_PORT"),
wait = true,
source_path = os.getenv("BUSTED_EMMY_DEBUGGER_SOURCE_PATH"),
})
end

require("kong.globalpatches")({
cli = true,
rbusted = true
Expand All @@ -88,4 +100,5 @@ _G.require = require "spec.require".require
-- Busted command-line runner
require 'busted.runner'({ standalone = false })


-- vim: set ft=lua ts=2 sw=2 sts=2 et :
85 changes: 56 additions & 29 deletions kong/tools/emmy_debugger.lua
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
local pl_path = require "pl.path"
local utils = require "kong.tools.utils"

local debugger = os.getenv("KONG_EMMY_DEBUGGER")
local emmy_debugger_host = os.getenv("KONG_EMMY_DEBUGGER_HOST") or "localhost"
local emmy_debugger_port = os.getenv("KONG_EMMY_DEBUGGER_PORT") or 9966
local emmy_debugger_wait = os.getenv("KONG_EMMY_DEBUGGER_WAIT")
local emmy_debugger_source_path = utils.split(os.getenv("KONG_EMMY_DEBUGGER_SOURCE_PATH") or "", ":")
local env_config = {
debugger = os.getenv("KONG_EMMY_DEBUGGER"),
host = os.getenv("KONG_EMMY_DEBUGGER_HOST"),
port = os.getenv("KONG_EMMY_DEBUGGER_PORT"),
wait = os.getenv("KONG_EMMY_DEBUGGER_WAIT"),
source_path = os.getenv("KONG_EMMY_DEBUGGER_SOURCE_PATH"),
multi_worker = os.getenv("KONG_EMMY_DEBUGGER_MULTI_WORKER"),
}

local source_path
local env_prefix

local function find_source(path)
if pl_path.exists(path) then
Expand All @@ -17,60 +23,80 @@ local function find_source(path)
return path
end

for _, source_path in ipairs(emmy_debugger_source_path) do
local full_path = pl_path.join(source_path, path)
if path:match("^jsonschema:") then
-- code is executing from jsonschema, don't attempt to map
return path
end

for _, p in ipairs(source_path) do
local full_path = pl_path.join(p, path)
if pl_path.exists(full_path) then
return full_path
end
end

ngx.log(ngx.ERR, "source file " .. path .. " not found in KONG_EMMY_DEBUGGER_SOURCE_PATH")
ngx.log(ngx.ERR, "source file " .. path .. " not found in " .. env_prefix .. "_EMMY_DEBUGGER_SOURCE_PATH")

return path
end

local function init()
local function load_debugger(path)
_G.emmy = {
fixPath = find_source
}

local ext = pl_path.extension(path)
local name = pl_path.basename(path):sub(1, -#ext - 1)

local save_cpath = package.cpath
package.cpath = pl_path.dirname(path) .. '/?' .. ext
local dbg = require(name)
package.cpath = save_cpath
return dbg
end

local function init(config_)
local config = config_ or {}
local debugger = config.debugger or env_config.debugger
local host = config.host or env_config.host or "localhost"
local port = config.port or env_config.port or 9966
local wait = config.wait or env_config.wait
local multi_worker = env_config.multi_worker or env_config.multi_worker

env_prefix = config.env_prefix or "KONG"
source_path = utils.split(config.source_path or env_config.source_path or "", ":")

if not debugger then
return
end

if not pl_path.isabs(debugger) then
ngx.log(ngx.ERR, "KONG_EMMY_DEBUGGER (" .. debugger .. ") must be an absolute path")
ngx.log(ngx.ERR, env_prefix .. "_EMMY_DEBUGGER (" .. debugger .. ") must be an absolute path")
return
end
if not pl_path.exists(debugger) then
ngx.log(ngx.ERR, "KONG_EMMY_DEBUGGER (" .. debugger .. ") file not found")
ngx.log(ngx.ERR, env_prefix .. "_EMMY_DEBUGGER (" .. debugger .. ") file not found")
return
end
local ext = pl_path.extension(debugger)
if ext ~= ".so" and ext ~= ".dylib" then
ngx.log(ngx.ERR, "KONG_EMMY_DEBUGGER (" .. debugger .. ") must be a .so (Linux) or .dylib (macOS) file")
ngx.log(ngx.ERR, env_prefix .. "_EMMY_DEBUGGER (" .. debugger .. ") must be a .so (Linux) or .dylib (macOS) file")
return
end
if ngx.worker.id() ~= 0 then
ngx.log(ngx.ERR, "KONG_EMMY_DEBUGGER is only supported in the first worker process, suggest setting KONG_NGINX_WORKER_PROCESSES to 1")
if ngx.worker.id() > 0 and not multi_worker then
ngx.log(ngx.ERR, env_prefix .. "_EMMY_DEBUGGER is only supported in the first worker process, suggest setting KONG_NGINX_WORKER_PROCESSES to 1")
return
end

ngx.log(ngx.NOTICE, "loading EmmyLua debugger " .. debugger)
ngx.log(ngx.WARN, "The EmmyLua integration for Kong is a feature solely for your convenience during development. Kong assumes no liability as a result of using the integration and does not endorse it’s usage. Issues related to usage of EmmyLua integration should be directed to the respective project instead.")

_G.emmy = {
fixPath = find_source
}

local name = pl_path.basename(debugger):sub(1, -#ext - 1)

local save_cpath = package.cpath
package.cpath = pl_path.dirname(debugger) .. '/?' .. ext
local dbg = require(name)
package.cpath = save_cpath

dbg.tcpListen(emmy_debugger_host, emmy_debugger_port)
local dbg = load_debugger(debugger)
dbg.tcpListen(host, port + (ngx.worker.id() or 0))

ngx.log(ngx.NOTICE, "EmmyLua debugger loaded, listening on port ", emmy_debugger_port)
ngx.log(ngx.NOTICE, "EmmyLua debugger loaded, listening on port ", port)

if emmy_debugger_wait then
if wait then
-- Wait for IDE connection
ngx.log(ngx.NOTICE, "waiting for IDE to connect")
dbg.waitIDE()
Expand All @@ -79,5 +105,6 @@ local function init()
end

return {
init = init
init = init,
load_debugger = load_debugger
}

1 comment on commit ecf4314

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

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

Bazel Build

Docker image available kong/kong:ecf4314ac394903eab119f82d86994c855a682b4
Artifacts available https://github.com/Kong/kong/actions/runs/9253136600

Please sign in to comment.