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

docs(dynamic-hooks): add dynamic hooks documentation #13068

Merged
merged 1 commit into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
96 changes: 96 additions & 0 deletions kong/dynamic_hook/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
## Dynamic hooks

Dynamic hooks can be used to extend Kong's behavior and run code at specific stages in the request/response lifecycle.


### Principles of operation

This module provides a way to define, enable, and execute dynamic hooks in Kong. It also allows hooking "before" and "after" handlers to functions, that are patched to execute them when called.
Dynamic Hooks can be organized into groups, allowing to enable or disable sets of hooks collectively.

Dynamic Hooks are intended solely for internal use. Usage of this feature is at your own risk.


#### Example usage

```lua
local dynamic_hook = require "kong.dynamic_hook"

----------------------------------------
-- Define a hook handler
local function before_hook(...)
io.write("hello, ")
end

-- Hook a function
dynamic_hook.hook_function("my_group", _G, "print", "varargs", {
befores = { before_hook },
})

-- Enable the hook group
dynamic_hook.always_enable("my_group")

-- Call the function
print("world!") -- prints "hello, world!"

----------------------------------------
-- Define another hook handler
local function log_event_hook(arg1, arg2)
ngx.log(ngx.INFO, "event triggered with args: ", arg1, ", ", arg2)
end

-- Register a new hook
dynamic_hook.hook("event_group", "log_event", log_event_hook)

-- Enable the hook group for this request
dynamic_hook.enable_on_this_request("event_group")

-- Run the hook
dynamic_hook.run_hook("event_group", "log_event", 10, "test")
```


### Application in Kong Gateway

Kong Gateway defines, registers and runs the following hooks:


| Hook | Description | Run Location |
| ----------- | ----------- | ----------- |
| timing:auth - auth | (Timing module) enables request debugging<br>for requests that match the requirements | Kong.rewrite (beginning) |
| timing - before:rewrite | (Timing module) enters the "rewrite" context, to begin<br>measuring the rewrite phase's duration | Kong.rewrite (beginning) |
| timing - after:rewrite | (Timing module) exits the "rewrite" context, to end<br>measuring the rewrite phase's duration | Kong.rewrite (end) |
| timing - dns:cache_lookup | (Timing module) sets the cache_hit context property | During each in-memory DNS cache lookup |
| timing - before:balancer | (Timing module) enters the "balancer" context, to begin<br>measuring the balancer phase's duration | Kong.balancer (beginning) |
| timing - after:balancer | (Timing module) exits the "balancer" context, to end<br>measuring the balancer phase's duration | Kong.balancer (end) |
| timing - before:access | (Timing module) enters the "access" context, to begin<br>measuring the access phase's duration | Kong.access (beginning) |
| timing - before:router | (Timing module) enters the router's context, to begin<br>measuring the router's execution | Before router initialization |
| timing - after:router | (Timing module) exits the router's context, to end<br>measuring the router's execution | After router execution |
| timing - workspace_id:got | (Timing module) sets the workspace_id context property | Kong.access, after workspace ID assignment |
| timing - after:access | (Timing module) exits the "access" context, to end<br>measuring the access phase's duration | Kong.access (end) |
| timing - before:response | (Timing module) enters the "response" context, to begin<br>measuring the response phase's duration | Kong.response (beginning) |
| timing - after:response | (Timing module) exits the "response" context, to end<br>measuring the response phase's duration | Kong.response (end) |
| timing - before:header_filter | (Timing module) enters the "header_filter" context, to begin<br>measuring the header_filter phase's duration | Kong.header_filter (beginning) |
| timing - after:header_filter | (Timing module) exits the "header_filter" context, to end<br>measuring the header_filter phase's duration | Kong.header_filter (end) |
| timing - before:body_filter | (Timing module) enters the "body_filter" context, to begin<br>measuring the body_filter phase's duration | Kong.body_filter (beginning) |
| timing - after:body_filter | (Timing module) exits the "body_filter" context, to end<br>measuring the body_filter phase's duration | Kong.body_filter (end) |
| timing - before:log | (Timing module) enters the "log" context, to begin<br>measuring the log phase's duration | Kong.log (beginning) |
| timing - after:log | (Timing module) exits the "log" context, to end<br>measuring the log phase's duration | Kong.log (end) |
| timing - before:plugin_iterator | (Timing module) enters the "plugins" context, to begin<br>measuring the plugins iterator's execution | Before plugin iteration starts |
| timing - after:plugin_iterator | (Timing module) exits the "plugins" context, to end<br>measuring the plugins iterator's execution | After plugin iteration ends |
| timing - before:plugin | (Timing module) enters each plugin's context, to begin<br>measuring the plugin's execution | Before each plugin handler |
| timing - after:plugin | (Timing module) exits each plugin's context, to end<br>measuring the plugin's execution | After each plugin handler |


"timing" hooks are used by the timing module when the request debugging feature is enabled.

The following functions are patched using `hook_function`:

| Function | Description |
| ----------- | ----------- |
| resty.dns.client.toip | (Timing module) measure dns query execution time |
| resty.http.connect | (Timing module) measure http connect execution time |
| resty.http.request | (Timing module) measure http request execution time |
| resty.redis.{method} | (Timing module) measure each Redis {method}'s<br> execution time |
| ngx.socket.tcp | (Timing module) measure each tcp connection<br>and ssl handshake execution times |
| ngx.socket.udp | (Timing module) measure each udp "setpeername"<br>execution time |
57 changes: 57 additions & 0 deletions kong/dynamic_hook/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,27 @@ local function wrap_function(max_args, group_name, original_func, handlers)
end


--- Hooks (patches) a function
-- Hooks "before" and "after" handlers to a function. The function is patched
-- to execute the handlers when it is called. The `parent` and `function_key`
-- parameters are used to identify and patch the function to be hooked.
--
-- @function dynamic_hook:hook_function
-- @tparam string group_name The name of the hook group
-- @tparam table parent The table containing the function to be hooked
-- @tparam string function_key The key (in the `parent` table) of the function
-- to be hooked
-- @tparam number max_args The maximum number of arguments the function accepts
-- @tparam table handlers A table containing the `before` and `after` handlers.
-- The table may contain the keys listed below:
-- * table `befores` array of handlers to execute before the function
-- * table `afters` array of handlers to execute before the function
--
-- @usage
-- -- Define a "before" handler to be executed before the _G.print function
-- dynamic_hook.hook_function("my_group", _G, "print", "varargs", {
-- befores = { before_handler },
-- })
function _M.hook_function(group_name, parent, function_key, max_args, handlers)
assert(type(group_name) == "string", "group_name must be a string")
assert(type(parent) == "table", "parent must be a table")
Expand All @@ -175,6 +196,14 @@ function _M.hook_function(group_name, parent, function_key, max_args, handlers)
end


--- Registers a new hook
-- The hook handler function is executed when `run_hook` is called with the
-- same `group_name` and `hook_name`.
--
-- @function dynamic_hook:hook
-- @tparam string group_name The name of the hook group
-- @tparam string hook_name The name of the hook
-- @tparam table handler The hook function
function _M.hook(group_name, hook_name, handler)
assert(type(group_name) == "string", "group_name must be a string")
assert(type(hook_name) == "string", "hook_name must be a string")
Expand All @@ -190,6 +219,13 @@ function _M.hook(group_name, hook_name, handler)
end


--- Checks if a hook group is enabled.
-- If a group is enabled, its hooks can be executed when `run_hook` is called
-- with the corresponding `group_name` and `hook_name` parameters.
--
-- @function dynamic_hook:is_group_enabled
-- @tparam string group_name The name of the hook group
-- @treturn boolean `true` if the group is enabled, `false` otherwise
function _M.is_group_enabled(group_name)
assert(type(group_name) == "string", "group_name must be a string")

Expand All @@ -211,6 +247,18 @@ function _M.is_group_enabled(group_name)
end


--- Runs a hook
-- Runs the hook registered for the given `group_name` and `hook_name` (if the
-- group is enabled).
--
-- @function dynamic_hook:run_hook
-- @tparam string group_name The name of the hook group
-- @tparam string hook_name The name of the hook
-- @tparam any `a1, a2, ..., a8` Arguments passed to the hook function
-- @tparam any ... Additional arguments passed to the hook function
-- @usage
-- -- Run the "my_hook" hook of the "my_group" group
-- dynamic_hook.run_hook("my_group", "my_hook", arg1, arg2)
function _M.run_hook(group_name, hook_name, a1, a2, a3, a4, a5, a6, a7, a8, ...)
assert(type(group_name) == "string", "group_name must be a string")
assert(type(hook_name) == "string", "hook_name must be a string")
Expand Down Expand Up @@ -243,6 +291,11 @@ function _M.run_hook(group_name, hook_name, a1, a2, a3, a4, a5, a6, a7, a8, ...)
end


--- Enables a hook group for the current request
--
-- @function dynamic_hook:enable_on_this_request
-- @tparam string group_name The name of the hook group to enable
-- @tparam table (optional) ngx_ctx The Nginx context object
function _M.enable_on_this_request(group_name, ngx_ctx)
assert(type(group_name) == "string", "group_name must be a string")

Expand All @@ -259,6 +312,10 @@ function _M.enable_on_this_request(group_name, ngx_ctx)
end


--- Enables a hook group for all requests
--
-- @function dynamic_hook:always_enable
-- @tparam string group_name The name of the hook group to enable
function _M.always_enable(group_name)
assert(type(group_name) == "string", "group_name must be a string")

Expand Down
Loading