mirror of
https://github.com/mailscope/kumomta.git
synced 2026-09-07 19:18:57 +00:00
This is useful if your hooks have interior logic to decide to filter out a given record. For example, you might have multiple hook endpoints but only messages with certain headers/metadata should be routed to any one of them for a specific event. Without the ability to pre-filter, we need to pay the cost of spooling the event speculatively, and then skipping it when processing logging for the (hopefully!) batch. Using pre-filter you can cut out that overhead. This is not yet documented; we're getting this in to get some feedback before we finalize this interface.
286 lines
7.8 KiB
Lua
286 lines
7.8 KiB
Lua
local mod = {
|
|
CONFIGURED = {},
|
|
}
|
|
local kumo = require 'kumo'
|
|
local utils = require 'policy-extras.policy_utils'
|
|
local typing = require 'policy-extras.typing'
|
|
local queue_module = require 'policy-extras.queue'
|
|
|
|
local Any, Map, Option, Record, String =
|
|
typing.any, typing.map, typing.option, typing.record, typing.string
|
|
|
|
local QueueConfig = Record('QueueConfig', {
|
|
_dynamic = queue_module.is_queue_config_option,
|
|
})
|
|
|
|
local DispHookOptions = Record('DispHookOptions', {
|
|
name = String,
|
|
hook = typing.Function,
|
|
})
|
|
|
|
function mod:new_disposition_hook(options)
|
|
local options = DispHookOptions(options)
|
|
|
|
if mod.CONFIGURED[options.name] then
|
|
error(
|
|
string.format(
|
|
"log_hook with name '%s' has already been configured",
|
|
options.name
|
|
)
|
|
)
|
|
end
|
|
mod.CONFIGURED[options.name] = options
|
|
|
|
kumo.on('pre_init', function()
|
|
local log_parameters = {
|
|
name = options.name,
|
|
}
|
|
-- utils.merge_into(options.log_parameters, log_parameters)
|
|
kumo.configure_log_disposition_hook(log_parameters)
|
|
end)
|
|
|
|
local hook_name = 'log_disposition_' .. options.name
|
|
kumo.on(hook_name, options.hook)
|
|
end
|
|
|
|
local LogHookOptions = Record('LogHookOptions', {
|
|
name = String,
|
|
log_parameters = Option(Map(String, Any)),
|
|
queue_config = Option(QueueConfig),
|
|
constructor = typing.Function,
|
|
batch_size = Option(typing.number),
|
|
min_batch_size = Option(typing.number),
|
|
max_batch_latency = Option(String),
|
|
filter = Option(typing.Function),
|
|
})
|
|
|
|
--[[
|
|
local log_hooks = require 'policy-extras.log_hooks'
|
|
|
|
-- Call this at the top level, outside of an event handler
|
|
log_hooks:new {
|
|
name = "webhook",
|
|
-- log_parameters are combined with the name and
|
|
-- passed through to kumo.configure_log_hook
|
|
log_parameters = {
|
|
headers = { 'Subject', 'X-Customer-ID' },
|
|
},
|
|
-- queue config are passed to kumo.make_queue_config.
|
|
-- You can use these to override the retry parameters
|
|
-- if you wish.
|
|
-- The defaults are shown below.
|
|
queue_config = {
|
|
retry_interval = "1m",
|
|
max_retry_interval = "20m",
|
|
},
|
|
|
|
-- Optional pre-filter function.
|
|
-- This is called as part of processing the should_enqueue_log_record
|
|
-- event callback after we have applied the default filter; the msg
|
|
-- is considered to be eligible to enqueue unless this function
|
|
-- returns true to indicate that "yes, it should be filtered out".
|
|
filter = function(msg, hook_name)
|
|
if should_filter_out(msg) then
|
|
-- We do not want this record
|
|
return true
|
|
end
|
|
return false
|
|
end),
|
|
|
|
constructor = function(domain, tenant, campaign)
|
|
local connection = {}
|
|
local client = kumo.http.build_client {}
|
|
function connection:send(message)
|
|
local response = client
|
|
:post('http://10.0.0.1:4242/log')
|
|
:header('Content-Type', 'application/json')
|
|
:body(message:get_data())
|
|
:send()
|
|
|
|
local disposition = string.format(
|
|
'%d %s: %s',
|
|
response:status_code(),
|
|
response:status_reason(),
|
|
response:text()
|
|
)
|
|
|
|
if response:status_is_success() then
|
|
return disposition
|
|
end
|
|
|
|
-- Signal that the webhook request failed.
|
|
-- In this case the 500 status prevents us from retrying
|
|
-- the webhook call again, but you could be more sophisticated
|
|
-- and analyze the disposition to determine if retrying it
|
|
-- would be useful and generate a 400 status instead.
|
|
-- In that case, the message will be retried later, until
|
|
-- it reached its expiration.
|
|
kumo.reject(500, disposition)
|
|
end
|
|
return connection
|
|
end,
|
|
}
|
|
|
|
]]
|
|
function mod:new(options)
|
|
local options = LogHookOptions(options)
|
|
|
|
if mod.CONFIGURED[options.name] then
|
|
error(
|
|
string.format(
|
|
"log_hook with name '%s' has already been configured",
|
|
options.name
|
|
)
|
|
)
|
|
end
|
|
mod.CONFIGURED[options.name] = options
|
|
|
|
kumo.on('pre_init', function()
|
|
local log_parameters = {
|
|
name = options.name,
|
|
}
|
|
utils.merge_into(options.log_parameters, log_parameters)
|
|
kumo.configure_log_hook(log_parameters)
|
|
end)
|
|
|
|
-- Choose a domain name with a "TLD" that will never match a
|
|
-- legitimate TLD. This helps to avoid collision with real
|
|
-- functioning SMTP domains
|
|
local domain_name = string.format('%s.log_hook', options.name)
|
|
-- Now derive a constructor event name from that
|
|
local constructor_name = string.format('make.%s', domain_name)
|
|
|
|
kumo.on('should_enqueue_log_record', function(msg, hook_name)
|
|
if hook_name ~= options.name then
|
|
-- It's not our hook
|
|
return
|
|
end
|
|
|
|
local log_record = msg:get_meta 'log_record'
|
|
|
|
-- avoid an infinite loop caused by logging that we logged that we logged...
|
|
if log_record.reception_protocol == 'LogRecord' then
|
|
return false
|
|
end
|
|
|
|
if options.filter then
|
|
if options.filter(msg, hook_name) then
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- was some other event that we want to log via the webhook
|
|
msg:set_meta('queue', domain_name)
|
|
return true
|
|
end)
|
|
|
|
local queue_config = QueueConfig {
|
|
retry_interval = '1m',
|
|
max_retry_interval = '20m',
|
|
}
|
|
utils.merge_into(options.queue_config, queue_config)
|
|
queue_config.protocol = {
|
|
custom_lua = {
|
|
constructor = constructor_name,
|
|
batch_size = options.batch_size or 1,
|
|
min_batch_size = options.min_batch_size,
|
|
max_batch_latency = options.max_batch_latency,
|
|
},
|
|
}
|
|
|
|
kumo.on(
|
|
'get_queue_config',
|
|
function(domain, tenant, campaign, routing_domain)
|
|
if domain ~= domain_name then
|
|
-- It's not the domain associated with our hook
|
|
return
|
|
end
|
|
|
|
-- Use the `make.NAME.log_hook` event to handle delivery
|
|
-- of webhook log records
|
|
return kumo.make_queue_config(queue_config)
|
|
end
|
|
)
|
|
|
|
-- And connect up the constructor event to the user-provided constructor
|
|
kumo.on(constructor_name, options.constructor)
|
|
end
|
|
|
|
local JsonLogHookOptions = Record('JsonLogHookOptions', {
|
|
name = String,
|
|
log_parameters = Option(Map(String, Any)),
|
|
queue_config = Option(QueueConfig),
|
|
url = String,
|
|
})
|
|
|
|
--[[
|
|
local log_hooks = require 'policy-extras.log_hooks'
|
|
|
|
-- Call this at the top level, outside of an event handler
|
|
log_hooks:new_json {
|
|
name = "webhook",
|
|
-- log_parameters are combined with the name and
|
|
-- passed through to kumo.configure_log_hook
|
|
log_parameters = {
|
|
headers = { 'Subject', 'X-Customer-ID' },
|
|
},
|
|
-- queue config are passed to kumo.make_queue_config.
|
|
-- You can use these to override the retry parameters
|
|
-- if you wish.
|
|
-- The defaults are shown below.
|
|
queue_config = {
|
|
retry_interval = "1m",
|
|
max_retry_interval = "20m",
|
|
},
|
|
-- The URL to POST the JSON to
|
|
url = "http://10.0.0.1:4242/log",
|
|
}
|
|
]]
|
|
function mod:new_json(options)
|
|
local json_options = JsonLogHookOptions(options)
|
|
local url = json_options.url
|
|
options.url = nil
|
|
|
|
options.constructor = function(domain, tenant, campaign)
|
|
local connection = {}
|
|
local client = kumo.http.build_client {}
|
|
function connection:send(message)
|
|
local response = client
|
|
:post(url)
|
|
:header('Content-Type', 'application/json')
|
|
:body(message:get_data())
|
|
:send()
|
|
|
|
local disposition = string.format(
|
|
'%d %s: %s',
|
|
response:status_code(),
|
|
response:status_reason(),
|
|
response:text()
|
|
)
|
|
|
|
if response:status_is_success() then
|
|
return disposition
|
|
end
|
|
|
|
-- Signal that the webhook request failed.
|
|
-- In this case the 500 status prevents us from retrying
|
|
-- the webhook call again, but you could be more sophisticated
|
|
-- and analyze the disposition to determine if retrying it
|
|
-- would be useful and generate a 400 status instead.
|
|
-- In that case, the message will be retried later, until
|
|
-- it reached its expiration.
|
|
kumo.reject(500, disposition)
|
|
end
|
|
|
|
function connection:close()
|
|
client:close()
|
|
end
|
|
|
|
return connection
|
|
end
|
|
|
|
return self:new(options)
|
|
end
|
|
|
|
return mod
|