This is an automated email from the ASF dual-hosted git repository.
shreemaan-abhishek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new b79329d86 feat: add log_format_extra to enrich the default logger
format (#13568)
b79329d86 is described below
commit b79329d86fe0744ed885a4ac558c369a511bb9ad
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Sat Jun 20 11:42:13 2026 +0800
feat: add log_format_extra to enrich the default logger format (#13568)
---
apisix/balancer.lua | 10 +
apisix/cli/ngx_tpl.lua | 1 +
apisix/core/ctx.lua | 3 +
apisix/plugins/clickhouse-logger.lua | 4 +
apisix/plugins/elasticsearch-logger.lua | 4 +
apisix/plugins/file-logger.lua | 4 +
apisix/plugins/google-cloud-logging.lua | 15 +-
apisix/plugins/http-logger.lua | 4 +
apisix/plugins/kafka-logger.lua | 4 +
apisix/plugins/loggly.lua | 4 +
apisix/plugins/loki-logger.lua | 4 +
apisix/plugins/rocketmq-logger.lua | 4 +
apisix/plugins/skywalking-logger.lua | 4 +
apisix/plugins/sls-logger.lua | 4 +
apisix/plugins/splunk-hec-logging.lua | 15 +-
apisix/plugins/syslog.lua | 4 +
apisix/plugins/tcp-logger.lua | 4 +
apisix/plugins/tencent-cloud-cls.lua | 4 +
apisix/plugins/udp-logger.lua | 4 +
apisix/utils/log-util.lua | 29 +-
docs/en/latest/apisix-variable.md | 1 +
docs/en/latest/plugins/clickhouse-logger.md | 2 +
docs/en/latest/plugins/elasticsearch-logger.md | 2 +
docs/en/latest/plugins/google-cloud-logging.md | 2 +
docs/en/latest/plugins/http-logger.md | 2 +
docs/en/latest/plugins/kafka-logger.md | 2 +
docs/en/latest/plugins/loggly.md | 2 +
docs/en/latest/plugins/loki-logger.md | 6 +-
docs/en/latest/plugins/rocketmq-logger.md | 2 +
docs/en/latest/plugins/skywalking-logger.md | 2 +
docs/en/latest/plugins/sls-logger.md | 2 +
docs/en/latest/plugins/splunk-hec-logging.md | 2 +
docs/en/latest/plugins/syslog.md | 2 +
docs/en/latest/plugins/tcp-logger.md | 2 +
docs/en/latest/plugins/tencent-cloud-cls.md | 2 +
docs/en/latest/plugins/udp-logger.md | 2 +
t/APISIX.pm | 3 +-
t/core/ctx.t | 53 ++++
t/plugin/file-logger.t | 417 +++++++++++++++++++++++++
t/plugin/loki-logger.t | 94 ++++++
t/plugin/splunk-hec-logging.t | 68 ++++
41 files changed, 793 insertions(+), 6 deletions(-)
diff --git a/apisix/balancer.lua b/apisix/balancer.lua
index 7a358dadf..ce8277b4f 100644
--- a/apisix/balancer.lua
+++ b/apisix/balancer.lua
@@ -252,6 +252,11 @@ local function pick_server(route, ctx)
local node = up_conf.nodes[1]
ctx.balancer_ip = node.host
ctx.balancer_port = node.port
+ ctx.upstream_unresolved_host = node.domain or node.host
+ -- also expose it as an nginx var so it can be used in the access log
(http only)
+ if is_http and ctx.var then
+ ctx.var.upstream_unresolved_host = ctx.upstream_unresolved_host
+ end
node.upstream_host = parse_server_for_upstream_host(node,
ctx.upstream_scheme)
return node
end
@@ -337,6 +342,11 @@ local function pick_server(route, ctx)
res.domain = domain
ctx.balancer_ip = res.host
ctx.balancer_port = res.port
+ ctx.upstream_unresolved_host = res.domain or res.host
+ -- also expose it as an nginx var so it can be used in the access log
(http only)
+ if is_http and ctx.var then
+ ctx.var.upstream_unresolved_host = ctx.upstream_unresolved_host
+ end
ctx.server_picker = server_picker
res.upstream_host = parse_server_for_upstream_host(res,
ctx.upstream_scheme)
diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua
index d95046efe..52574e28d 100644
--- a/apisix/cli/ngx_tpl.lua
+++ b/apisix/cli/ngx_tpl.lua
@@ -810,6 +810,7 @@ http {
set $upstream_scheme 'http';
set $upstream_host $http_host;
+ set $upstream_unresolved_host '';
set $upstream_uri '';
set $request_line '';
set $ctx_ref '';
diff --git a/apisix/core/ctx.lua b/apisix/core/ctx.lua
index 58ee7c6b6..1827254b6 100644
--- a/apisix/core/ctx.lua
+++ b/apisix/core/ctx.lua
@@ -190,6 +190,7 @@ do
local ngx_var_names = {
upstream_scheme = true,
upstream_host = true,
+ upstream_unresolved_host = true,
upstream_upgrade = true,
upstream_connection = true,
upstream_uri = true,
@@ -244,6 +245,8 @@ do
route_name = true,
service_id = true,
service_name = true,
+ -- the upstream host before DNS resolution (configured domain/host)
+ upstream_unresolved_host = true,
}
local mt = {
diff --git a/apisix/plugins/clickhouse-logger.lua
b/apisix/plugins/clickhouse-logger.lua
index 99135f707..fd44e32fc 100644
--- a/apisix/plugins/clickhouse-logger.lua
+++ b/apisix/plugins/clickhouse-logger.lua
@@ -42,6 +42,7 @@ local schema = {
name = {type = "string", default = "clickhouse logger"},
ssl_verify = {type = "boolean", default = true},
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
include_req_body = {type = "boolean", default = false},
include_req_body_expr = {
type = "array",
@@ -72,6 +73,9 @@ local schema = {
local metadata_schema = {
type = "object",
properties = {
+ log_format_extra = {
+ type = "object"
+ },
log_format = {
type = "object"
},
diff --git a/apisix/plugins/elasticsearch-logger.lua
b/apisix/plugins/elasticsearch-logger.lua
index e03d75c72..736b03219 100644
--- a/apisix/plugins/elasticsearch-logger.lua
+++ b/apisix/plugins/elasticsearch-logger.lua
@@ -55,6 +55,7 @@ local schema = {
required = {"index"}
},
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
auth = {
type = "object",
properties = {
@@ -121,6 +122,9 @@ local schema = {
local metadata_schema = {
type = "object",
properties = {
+ log_format_extra = {
+ type = "object"
+ },
log_format = {
type = "object"
},
diff --git a/apisix/plugins/file-logger.lua b/apisix/plugins/file-logger.lua
index a8f809de7..afe73d662 100644
--- a/apisix/plugins/file-logger.lua
+++ b/apisix/plugins/file-logger.lua
@@ -34,6 +34,7 @@ local schema = {
type = "string"
},
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
include_req_body = {type = "boolean", default = false},
include_req_body_expr = {
type = "array",
@@ -71,6 +72,9 @@ local metadata_schema = {
},
log_format = {
type = "object"
+ },
+ log_format_extra = {
+ type = "object"
}
}
}
diff --git a/apisix/plugins/google-cloud-logging.lua
b/apisix/plugins/google-cloud-logging.lua
index 39202e521..6133293d2 100644
--- a/apisix/plugins/google-cloud-logging.lua
+++ b/apisix/plugins/google-cloud-logging.lua
@@ -18,6 +18,7 @@
local core = require("apisix.core")
local plugin = require("apisix.plugin")
local tostring = tostring
+local pairs = pairs
local http = require("resty.http")
local log_util = require("apisix.utils.log-util")
local bp_manager_mod = require("apisix.utils.batch-processor-manager")
@@ -98,6 +99,7 @@ local schema = {
default = "apisix.apache.org%2Flogs"
},
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
},
oneOf = {
{ required = { "auth_config" } },
@@ -109,6 +111,9 @@ local schema = {
local metadata_schema = {
type = "object",
properties = {
+ log_format_extra = {
+ type = "object"
+ },
log_format = {
type = "object"
},
@@ -190,7 +195,7 @@ end
local function get_logger_entry(conf, ctx, oauth)
- local entry, customized = log_util.get_log_entry(plugin_name, conf, ctx)
+ local entry, customized, extra = log_util.get_log_entry(plugin_name, conf,
ctx)
local google_entry
if not customized then
google_entry = {
@@ -210,6 +215,14 @@ local function get_logger_entry(conf, ctx, oauth)
service_id = entry.service_id,
},
}
+ -- the fixed payload above drops everything else, so add
log_format_extra
+ if extra then
+ for k, v in pairs(extra) do
+ if google_entry.jsonPayload[k] == nil then
+ google_entry.jsonPayload[k] = v
+ end
+ end
+ end
else
google_entry = {
jsonPayload = entry,
diff --git a/apisix/plugins/http-logger.lua b/apisix/plugins/http-logger.lua
index cc8481344..a57eb3564 100644
--- a/apisix/plugins/http-logger.lua
+++ b/apisix/plugins/http-logger.lua
@@ -34,6 +34,7 @@ local schema = {
auth_header = {type = "string"},
timeout = {type = "integer", minimum = 1, default = 3},
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
include_req_body = {type = "boolean", default = false},
include_req_body_expr = {
type = "array",
@@ -64,6 +65,9 @@ local schema = {
local metadata_schema = {
type = "object",
properties = {
+ log_format_extra = {
+ type = "object"
+ },
log_format = {
type = "object"
},
diff --git a/apisix/plugins/kafka-logger.lua b/apisix/plugins/kafka-logger.lua
index 014898c5e..845fa684b 100644
--- a/apisix/plugins/kafka-logger.lua
+++ b/apisix/plugins/kafka-logger.lua
@@ -40,6 +40,7 @@ local schema = {
enum = {"default", "origin"},
},
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
-- deprecated, use "brokers" instead
broker_list = {
type = "object",
@@ -146,6 +147,9 @@ local schema = {
local metadata_schema = {
type = "object",
properties = {
+ log_format_extra = {
+ type = "object"
+ },
log_format = {
type = "object"
},
diff --git a/apisix/plugins/loggly.lua b/apisix/plugins/loggly.lua
index d7d792764..112f72e12 100644
--- a/apisix/plugins/loggly.lua
+++ b/apisix/plugins/loggly.lua
@@ -95,6 +95,7 @@ local schema = {
default = true
},
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
severity_map = {
type = "object",
description = "upstream response code vs syslog severity mapping",
@@ -143,6 +144,9 @@ local metadata_schema = {
minimum = 1,
default= defaults.timeout
},
+ log_format_extra = {
+ type = "object"
+ },
log_format = {
type = "object",
}
diff --git a/apisix/plugins/loki-logger.lua b/apisix/plugins/loki-logger.lua
index b107ec10a..4e584a827 100644
--- a/apisix/plugins/loki-logger.lua
+++ b/apisix/plugins/loki-logger.lua
@@ -91,6 +91,7 @@ local schema = {
-- logger related configurations
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
include_req_body = {type = "boolean", default = false},
include_req_body_expr = {
type = "array",
@@ -120,6 +121,9 @@ local metadata_schema = {
log_format = {
type = "object"
},
+ log_format_extra = {
+ type = "object"
+ },
max_pending_entries = {
type = "integer",
description = "maximum number of pending entries in the batch
processor",
diff --git a/apisix/plugins/rocketmq-logger.lua
b/apisix/plugins/rocketmq-logger.lua
index 6610fb33e..9059e9ee2 100644
--- a/apisix/plugins/rocketmq-logger.lua
+++ b/apisix/plugins/rocketmq-logger.lua
@@ -48,6 +48,7 @@ local schema = {
key = {type = "string"},
tag = {type = "string"},
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
timeout = {type = "integer", minimum = 1, default = 3},
use_tls = {type = "boolean", default = false},
access_key = {type = "string", default = ""},
@@ -78,6 +79,9 @@ local schema = {
local metadata_schema = {
type = "object",
properties = {
+ log_format_extra = {
+ type = "object"
+ },
log_format = {
type = "object"
},
diff --git a/apisix/plugins/skywalking-logger.lua
b/apisix/plugins/skywalking-logger.lua
index 841a3e606..712dca945 100644
--- a/apisix/plugins/skywalking-logger.lua
+++ b/apisix/plugins/skywalking-logger.lua
@@ -37,6 +37,7 @@ local schema = {
service_name = {type = "string", default = "APISIX"},
service_instance_name = {type = "string", default = "APISIX Instance
Name"},
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
timeout = {type = "integer", minimum = 1, default = 3},
include_req_body = {type = "boolean", default = false},
include_req_body_expr = {
@@ -64,6 +65,9 @@ local schema = {
local metadata_schema = {
type = "object",
properties = {
+ log_format_extra = {
+ type = "object"
+ },
log_format = {
type = "object"
},
diff --git a/apisix/plugins/sls-logger.lua b/apisix/plugins/sls-logger.lua
index c482f51ae..1f0c6280f 100644
--- a/apisix/plugins/sls-logger.lua
+++ b/apisix/plugins/sls-logger.lua
@@ -52,6 +52,7 @@ local schema = {
max_resp_body_bytes = {type = "integer", minimum = 1, default =
524288},
timeout = {type = "integer", minimum = 1, default= 5000},
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
host = {type = "string"},
port = {type = "integer"},
project = {type = "string"},
@@ -66,6 +67,9 @@ local schema = {
local metadata_schema = {
type = "object",
properties = {
+ log_format_extra = {
+ type = "object"
+ },
log_format = {
type = "object"
}
diff --git a/apisix/plugins/splunk-hec-logging.lua
b/apisix/plugins/splunk-hec-logging.lua
index a7b9b2df0..ae2f73a15 100644
--- a/apisix/plugins/splunk-hec-logging.lua
+++ b/apisix/plugins/splunk-hec-logging.lua
@@ -25,6 +25,7 @@ local plugin = require("apisix.plugin")
local table_insert = core.table.insert
local table_concat = core.table.concat
local ipairs = ipairs
+local pairs = pairs
local DEFAULT_SPLUNK_HEC_ENTRY_SOURCE = "apache-apisix-splunk-hec-logging"
@@ -67,6 +68,7 @@ local schema = {
default = true
},
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
},
encrypt_fields = {"endpoint.token"},
required = { "endpoint" },
@@ -75,6 +77,9 @@ local schema = {
local metadata_schema = {
type = "object",
properties = {
+ log_format_extra = {
+ type = "object"
+ },
log_format = {
type = "object"
},
@@ -105,7 +110,7 @@ end
local function get_logger_entry(conf, ctx)
- local entry, customized = log_util.get_log_entry(plugin_name, conf, ctx)
+ local entry, customized, extra = log_util.get_log_entry(plugin_name, conf,
ctx)
local splunk_entry = {
time = ngx_now(),
source = DEFAULT_SPLUNK_HEC_ENTRY_SOURCE,
@@ -126,6 +131,14 @@ local function get_logger_entry(conf, ctx)
latency = entry.latency,
upstream = entry.upstream,
}
+ -- the fixed event above drops everything else, so add log_format_extra
+ if extra then
+ for k, v in pairs(extra) do
+ if splunk_entry.event[k] == nil then
+ splunk_entry.event[k] = v
+ end
+ end
+ end
else
splunk_entry.host = core.utils.gethostname()
splunk_entry.event = entry
diff --git a/apisix/plugins/syslog.lua b/apisix/plugins/syslog.lua
index f34542434..078d2264b 100644
--- a/apisix/plugins/syslog.lua
+++ b/apisix/plugins/syslog.lua
@@ -33,6 +33,7 @@ local schema = {
pool_size = {type = "integer", minimum = 5, default = 5},
tls = {type = "boolean", default = false},
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
include_req_body = {type = "boolean", default = false},
include_req_body_expr = {
type = "array",
@@ -61,6 +62,9 @@ local schema = batch_processor_manager:wrap_schema(schema)
local metadata_schema = {
type = "object",
properties = {
+ log_format_extra = {
+ type = "object"
+ },
log_format = {
type = "object"
}
diff --git a/apisix/plugins/tcp-logger.lua b/apisix/plugins/tcp-logger.lua
index d2cf97874..a52e35b6a 100644
--- a/apisix/plugins/tcp-logger.lua
+++ b/apisix/plugins/tcp-logger.lua
@@ -34,6 +34,7 @@ local schema = {
tls_options = {type = "string"},
timeout = {type = "integer", minimum = 1, default= 1000},
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
include_req_body = {type = "boolean", default = false},
include_req_body_expr = {
type = "array",
@@ -59,6 +60,9 @@ local schema = {
local metadata_schema = {
type = "object",
properties = {
+ log_format_extra = {
+ type = "object"
+ },
log_format = {
type = "object"
},
diff --git a/apisix/plugins/tencent-cloud-cls.lua
b/apisix/plugins/tencent-cloud-cls.lua
index 333742e8f..9e176b370 100644
--- a/apisix/plugins/tencent-cloud-cls.lua
+++ b/apisix/plugins/tencent-cloud-cls.lua
@@ -61,6 +61,7 @@ local schema = {
max_resp_body_bytes = { type = "integer", minimum = 1, default =
524288 },
global_tag = { type = "object" },
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
},
encrypt_fields = {"secret_key"},
required = { "cls_host", "cls_topic", "secret_id", "secret_key" }
@@ -70,6 +71,9 @@ local schema = {
local metadata_schema = {
type = "object",
properties = {
+ log_format_extra = {
+ type = "object"
+ },
log_format = {
type = "object"
},
diff --git a/apisix/plugins/udp-logger.lua b/apisix/plugins/udp-logger.lua
index 2ea049777..ae8b62694 100644
--- a/apisix/plugins/udp-logger.lua
+++ b/apisix/plugins/udp-logger.lua
@@ -32,6 +32,7 @@ local schema = {
port = {type = "integer", minimum = 0},
timeout = {type = "integer", minimum = 1, default = 3},
log_format = {type = "object"},
+ log_format_extra = {type = "object"},
include_req_body = {type = "boolean", default = false},
include_req_body_expr = {
type = "array",
@@ -57,6 +58,9 @@ local schema = {
local metadata_schema = {
type = "object",
properties = {
+ log_format_extra = {
+ type = "object"
+ },
log_format = {
type = "object"
},
diff --git a/apisix/utils/log-util.lua b/apisix/utils/log-util.lua
index 4551b8c68..241764166 100644
--- a/apisix/utils/log-util.lua
+++ b/apisix/utils/log-util.lua
@@ -298,10 +298,21 @@ function _M.get_log_entry(plugin_name, conf, ctx)
local entry
local customized = false
+ -- resolved log_format_extra fields, surfaced to callers that rebuild a
+ -- fixed payload (e.g. google-cloud-logging, splunk) so extras aren't
dropped
+ local extra_entry
local has_meta_log_format = metadata and metadata.value.log_format
and core.table.nkeys(metadata.value.log_format) > 0
+ -- conf value wins when present (even if empty), matching log_format;
+ -- only fall back to plugin metadata when conf has no log_format_extra
+ local log_format_extra = conf.log_format_extra
+ if log_format_extra == nil and metadata and
metadata.value.log_format_extra then
+ log_format_extra = metadata.value.log_format_extra
+ end
+ local has_extra = log_format_extra and core.table.nkeys(log_format_extra)
> 0
+
if conf.log_format or has_meta_log_format then
customized = true
entry = get_custom_format_log(ctx, conf.log_format or
metadata.value.log_format,
@@ -309,6 +320,22 @@ function _M.get_log_entry(plugin_name, conf, ctx)
else
if is_http then
entry = get_full_log(ngx, conf)
+ -- enrich the default rich log with extra user-defined fields
without
+ -- replacing it, so callers keep every default field and add their
own
+ if has_extra then
+ -- get_custom_format_log also appends route_id/service_id;
keep only
+ -- the user-declared keys so callers don't leak unrequested
fields
+ local tmp = get_custom_format_log(ctx, log_format_extra,
+ conf.max_req_body_bytes)
+ extra_entry = {}
+ for k in pairs(log_format_extra) do
+ extra_entry[k] = tmp[k]
+ -- never clobber a default field, only add new ones
+ if entry[k] == nil then
+ entry[k] = tmp[k]
+ end
+ end
+ end
else
-- get_full_log doesn't work in stream
core.log.error(plugin_name, "'s log_format is not set")
@@ -324,7 +351,7 @@ function _M.get_log_entry(plugin_name, conf, ctx)
if ctx.llm_response_text then
entry.llm_response_text = ctx.llm_response_text
end
- return entry, customized
+ return entry, customized, extra_entry
end
diff --git a/docs/en/latest/apisix-variable.md
b/docs/en/latest/apisix-variable.md
index 314d22dbf..adfb5aabd 100644
--- a/docs/en/latest/apisix-variable.md
+++ b/docs/en/latest/apisix-variable.md
@@ -50,5 +50,6 @@ additional variables.
| redis_cmd_line | Redis | The content of Redis command.
| |
| resp_body | core | In the logger plugin, if some of the
plugins support logging of response body, for example by configuring
`include_resp_body: true`, then this variable can be used in the log format. |
|
| rpc_time | xRPC | Time spent at the rpc request level.
| |
+| upstream_unresolved_host | core | The upstream host before DNS resolution
(the configured domain or IP of the picked node). | httpbin.org |
You can also register your own
[variable](./plugin-develop.md#register-custom-variable).
diff --git a/docs/en/latest/plugins/clickhouse-logger.md
b/docs/en/latest/plugins/clickhouse-logger.md
index 2b6074f96..5b530bc38 100644
--- a/docs/en/latest/plugins/clickhouse-logger.md
+++ b/docs/en/latest/plugins/clickhouse-logger.md
@@ -47,6 +47,7 @@ The `clickhouse-logger` Plugin pushes request and response
logs to [ClickHouse](
| timeout | integer | False | 3 |
greater than 0 | Time in seconds to keep the connection alive after sending
a request.
|
| ssl_verify | boolean | False | true |
| If `true`, verify SSL.
|
| log_format | object | False | |
| Custom log format using key-value pairs in JSON format. Values
can reference [APISIX variables](../apisix-variable.md) or [NGINX
variables](https://nginx.org/en/docs/http/ngx_http_core_module.html) by
prefixing with `$`. You can also configure log format on a global scale using
[Plugin Metadata](#plugin-metadata). |
+| log_format_extra | object | False |
| | Extra log fields **added on top of** the default log
entry, keeping every default field instead of replacing them (unlike
`log_format`). Same value syntax as `log_format`. Ignored when `log_format` is
set. |
| include_req_body | boolean | False | false |
| If `true`, include the request body in the log. Note that if the
request body is too big to be kept in the memory, it cannot be logged due to
NGINX's limitations.
|
| include_req_body_expr | array | False | |
| An array of one or more conditions in the form of [APISIX
expressions](https://github.com/api7/lua-resty-expr). Used when
`include_req_body` is `true`. Request body is only logged when the expressions
evaluate to `true`.
|
| include_resp_body | boolean | False | false |
| If `true`, include the response body in the log.
|
@@ -71,6 +72,7 @@ This Plugin supports using batch processors to aggregate and
process entries (lo
| Name | Type | Required | Default | Valid values |
Description
|
|--------------------|---------|----------|---------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| log_format | object | False | | | Custom
log format using key-value pairs in JSON format. Values can reference [APISIX
variables](../apisix-variable.md) or [NGINX
variables](https://nginx.org/en/docs/http/ngx_http_core_module.html) by
prefixing with `$`. This configuration is global and applies to all Routes and
Services that use the `clickhouse-logger` Plugin. |
+| log_format_extra | object | False | | |
Extra log fields **added on top of** the default log entry, keeping every
default field instead of replacing them (unlike `log_format`). Same value
syntax as `log_format`. Ignored when `log_format` is set. |
| max_pending_entries | integer | False | | >= 1 | Maximum
number of unprocessed entries allowed in the batch processor. When this limit
is reached, new entries will be dropped until the backlog is reduced.
|
## Examples
diff --git a/docs/en/latest/plugins/elasticsearch-logger.md
b/docs/en/latest/plugins/elasticsearch-logger.md
index 0e4a5dfd4..58fd33119 100644
--- a/docs/en/latest/plugins/elasticsearch-logger.md
+++ b/docs/en/latest/plugins/elasticsearch-logger.md
@@ -43,6 +43,7 @@ The `elasticsearch-logger` Plugin pushes request and response
logs in batches to
| field | object | True | |
| Elasticsearch field configuration. |
| field.index | string | True | |
| Elasticsearch [_index
field](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-index-field.html#mapping-index-field).
Supports [Lua time format](https://www.lua.org/pil/22.1.html) in curly
brackets for date-based indices (e.g., `service-{%Y-%m-%d}`) and [APISIX
variables](../apisix-variable.md) prefixed with `$` (e.g.,
`service-$host-{%Y.%m.%d}`). |
| log_format | object | False | |
| Custom log format as key-value pairs in JSON. Values support strings and
nested objects (up to five levels deep; deeper fields are truncated). Within
strings, [APISIX](../apisix-variable.md) or [NGINX
variables](http://nginx.org/en/docs/varindex.html) can be referenced by
prefixing with `$`. |
+| log_format_extra | object | False | |
| Extra log fields **added on top of** the default log entry, keeping
every default field instead of replacing them (unlike `log_format`). Same value
syntax as `log_format`. Ignored when `log_format` is set. |
| auth | object | False | |
| Elasticsearch
[authentication](https://www.elastic.co/guide/en/elasticsearch/reference/current/setting-up-authentication.html)
configuration. |
| auth.username | string | False | |
| Elasticsearch
[authentication](https://www.elastic.co/guide/en/elasticsearch/reference/current/setting-up-authentication.html)
username. Required if `auth` is configured. Must be provided together with
`auth.password`. |
| auth.password | string | False | |
| Elasticsearch
[authentication](https://www.elastic.co/guide/en/elasticsearch/reference/current/setting-up-authentication.html)
password. Required if `auth` is configured. Must be provided together with
`auth.username`. The secret is encrypted with AES before being stored in etcd. |
@@ -65,6 +66,7 @@ This Plugin supports using batch processors to aggregate and
process entries (lo
| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| log_format | object | False | | Log format declared as key-value pairs in
JSON. Values support strings and nested objects (up to five levels deep; deeper
fields are truncated). Within strings, [APISIX](../apisix-variable.md) or
[NGINX](http://nginx.org/en/docs/varindex.html) variables can be referenced by
prefixing with `$`. |
+| log_format_extra | object | False | | Extra log fields **added on top of**
the default log entry, keeping every default field instead of replacing them
(unlike `log_format`). Same value syntax as `log_format`. Ignored when
`log_format` is set. |
| max_pending_entries | integer | False | | Maximum number of pending entries
that can be buffered in batch processor before it starts dropping them. |
## Examples
diff --git a/docs/en/latest/plugins/google-cloud-logging.md
b/docs/en/latest/plugins/google-cloud-logging.md
index a383521c6..0e1b47364 100644
--- a/docs/en/latest/plugins/google-cloud-logging.md
+++ b/docs/en/latest/plugins/google-cloud-logging.md
@@ -50,6 +50,7 @@ The `google-cloud-logging` Plugin pushes request and response
logs in batches to
| resource | object | False | {"type": "global"}
| | Google monitored resource. See
[MonitoredResource](https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource)
for more details.
[...]
| log_id | string | False |
apisix.apache.org%2Flogs
| | Google Cloud logging ID. See
[LogEntry](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry)
for details.
[...]
| log_format | object | False |
| | Custom log format using key-value pairs in JSON format. Values
can reference [APISIX variables](../apisix-variable.md) or [NGINX
variables](https://nginx.org/en/docs/http/ngx_http_core_module.html) by
prefixing with `$`. You can also co [...]
+| log_format_extra | object | False |
| | Extra log fields **added on top of** the default log
entry, keeping every default field instead of replacing them (unlike
`log_format`). Same value syntax as `log_format`. Ignored when `log_format` is
set. |
| name | string | False | google-cloud-logging
| | Unique identifier of the Plugin for the batch processor. If
you use [Prometheus](./prometheus.md) to monitor APISIX metrics, the name is
exported in `apisix_batch_process_entries`.
[...]
| batch_max_size | integer | False | 1000
| | The number of log entries allowed in one batch. Once reached,
the batch will be sent to the logging service. Setting this parameter to `1`
means immediate processing.
[...]
| inactive_timeout | integer | False | 5
| | The maximum time in seconds to wait for new logs before
sending the batch to the logging service. The value should be smaller than
`buffer_duration`.
[...]
@@ -66,6 +67,7 @@ This Plugin supports using batch processors to aggregate and
process entries (lo
| Name | Type | Required | Default | Valid values |
Description
|
|--------------------|---------|----------|---------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| log_format | object | False | | | Custom
log format using key-value pairs in JSON format. Values can reference [APISIX
variables](../apisix-variable.md) or [NGINX
variables](https://nginx.org/en/docs/http/ngx_http_core_module.html) by
prefixing with `$`. This configuration is global and applies to all Routes and
Services that use the `google-cloud-logging` Plugin. |
+| log_format_extra | object | False | | |
Extra log fields **added on top of** the default log entry, keeping every
default field instead of replacing them (unlike `log_format`). Same value
syntax as `log_format`. Ignored when `log_format` is set. |
| max_pending_entries | integer | False | | >= 1 | Maximum
number of unprocessed entries allowed in the batch processor. When this limit
is reached, new entries will be dropped until the backlog is reduced.
|
## Examples
diff --git a/docs/en/latest/plugins/http-logger.md
b/docs/en/latest/plugins/http-logger.md
index bbbcd8093..c511f3f2f 100644
--- a/docs/en/latest/plugins/http-logger.md
+++ b/docs/en/latest/plugins/http-logger.md
@@ -43,6 +43,7 @@ The `http-logger` Plugin pushes request and response logs as
JSON objects to HTT
| auth_header | string | False | |
| Authorization headers, if required by the HTTP(S) server.
|
| timeout | integer | False | 3 | greater than 0
| Time to keep the connection alive after sending a request.
|
| log_format | object | False | |
| Custom log format using key-value pairs in JSON format. Values can reference
[NGINX variables](https://nginx.org/en/docs/http/ngx_http_core_module.html).
You can also configure log format on a global scale using the [plugin
metadata](../terminology/plugin-metadata.md), which configures the log format
for all `http-logger` Plugin instances. If the log format configured on the
individual Plugin instance differ [...]
+| log_format_extra | object | False | |
| Extra log fields **added on top of** the default log entry, keeping
every default field instead of replacing them (unlike `log_format`). Same value
syntax as `log_format`. Ignored when `log_format` is set. |
| include_req_body | boolean | False | false |
| If true, include the request body in the log. Note that if the request body
is too big to be kept in the memory, it cannot be logged due to NGINX's
limitations.
|
| include_req_body_expr | array | False | |
| An array of one or more conditions in the form of
[lua-resty-expr](https://github.com/api7/lua-resty-expr) expressions. Used when
`include_req_body` is true. Request body is only logged when the expressions
configured here evaluate to true. |
| include_resp_body | boolean | False | false |
| If true, include the response body in the log.
|
@@ -65,6 +66,7 @@ You can also set the format of the logs by configuring the
Plugin metadata. The
| Name | Type | Required | Description
|
|---------------------|---------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| log_format | object | False | Custom log format using key-value
pairs in JSON format. Values can reference [NGINX
variables](https://nginx.org/en/docs/http/ngx_http_core_module.html).
|
+| log_format_extra | object | False | Extra log fields **added on
top of** the default log entry, keeping every default field instead of
replacing them (unlike `log_format`). Same value syntax as `log_format`.
Ignored when `log_format` is set. |
| max_pending_entries | integer | False | Maximum number of unprocessed
entries allowed in the batch processor. When this limit is reached, new entries
will be dropped until the backlog is reduced. Available in APISIX from version
3.15.0. |
:::info IMPORTANT
diff --git a/docs/en/latest/plugins/kafka-logger.md
b/docs/en/latest/plugins/kafka-logger.md
index defd99397..f57081183 100644
--- a/docs/en/latest/plugins/kafka-logger.md
+++ b/docs/en/latest/plugins/kafka-logger.md
@@ -57,6 +57,7 @@ It might take some time to receive the log data. It will be
automatically sent a
| name | string | False | "kafka logger" |
| Unique identifier for the batch
processor. If you use Prometheus to monitor APISIX metrics, the name is
exported in `apisix_batch_process_entries`.
|
| meta_format | enum | False | "default" |
["default","origin"] | Format to collect the
request information. Setting to `default` collects the information in JSON
format and `origin` collects the information with the original HTTP request.
See [examples](#meta_format-example) below.
|
| log_format | object | False | |
| Log format declared as
key-value pairs in JSON. Values support strings and nested objects (up to five
levels deep; deeper fields are truncated). Within strings,
[APISIX](../apisix-variable.md) or
[NGINX](http://nginx.org/en/docs/varindex.html) variables can be referenced by
prefixing with `$`. |
+| log_format_extra | object | False |
| | Extra log fields **added
on top of** the default log entry, keeping every default field instead of
replacing them (unlike `log_format`). Same value syntax as `log_format`.
Ignored when `log_format` is set. |
| include_req_body | boolean | False | false |
[false, true] | When set to `true` includes
the request body in the log. If the request body is too big to be kept in the
memory, it can't be logged due to NGINX's limitations.
|
| include_req_body_expr | array | False | |
| Filter for when the
`include_req_body` attribute is set to `true`. Request body is only logged when
the expression set here evaluates to `true`. See
[lua-resty-expr](https://github.com/api7/lua-resty-expr) for more.
|
| max_req_body_bytes | integer | False | 524288 | >=1
| Maximum request body size in
bytes to push to Kafka. If the size exceeds the configured value, the body will
be truncated before being pushed.
|
@@ -144,6 +145,7 @@ You can also set the format of the logs by configuring the
Plugin metadata. The
| Name | Type | Required | Default | Description
|
| ------------------- | ------- | -------- | ------- |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
| log_format | object | False | | Log format declared as
key-value pairs in JSON. Values support strings and nested objects (up to five
levels deep; deeper fields are truncated). Within strings,
[APISIX](../apisix-variable.md) or
[NGINX](http://nginx.org/en/docs/varindex.html) variables can be referenced by
prefixing with `$`. |
+| log_format_extra | object | False | | Extra log fields
**added on top of** the default log entry, keeping every default field instead
of replacing them (unlike `log_format`). Same value syntax as `log_format`.
Ignored when `log_format` is set. |
| max_pending_entries | integer | False | | Maximum number of
pending entries that can be buffered in the batch processor before it starts
dropping them.
|
:::info IMPORTANT
diff --git a/docs/en/latest/plugins/loggly.md b/docs/en/latest/plugins/loggly.md
index 4d6ccb737..e672f9fcb 100644
--- a/docs/en/latest/plugins/loggly.md
+++ b/docs/en/latest/plugins/loggly.md
@@ -44,6 +44,7 @@ When the maximum batch size is exceeded, the data in the
queue is pushed to Logg
| severity_map | object | False | nil | A way to map
upstream HTTP response codes to Syslog severity. Key-value pairs where keys are
the HTTP response codes and the values are the Syslog severity levels. For
example `{"410": "CRIT"}`. |
| tags | array | False | | Metadata to be
included with any event log to aid in segmentation and filtering.
|
| log_format | object | False | {"host": "$host",
"@timestamp": "$time_iso8601", "client_ip": "$remote_addr"} | Log format
declared as key-value pairs in JSON. Values support strings and nested objects
(up to five levels deep; deeper fields are truncated). Within strings,
[APISIX](../apisix-variable.md) or
[NGINX](http://nginx.org/en/docs/varindex.html) variables can be referenced by
prefixing with `$`. |
+| log_format_extra | object | False |
| Extra log fields
**added on top of** the default log entry, keeping every default field instead
of replacing them (unlike `log_format`). Same value syntax as `log_format`.
Ignored when `log_format` is set. |
| include_req_body | boolean | False | false | When set to
`true` includes the request body in the log. If the request body is too big to
be kept in the memory, it can't be logged due to Nginx's limitations.
|
| include_req_body_expr | array | False | | Filter for
when the `include_req_body` attribute is set to `true`. Request body is only
logged when the expression set here evaluates to `true`. See
[lua-resty-expr](https://github.com/api7/lua-resty-expr) for more. |
| max_req_body_bytes | integer | False | 524288 | Request bodies within this
size will be logged, if the size exceeds the configured value it will be
truncated before logging. |
@@ -72,6 +73,7 @@ You can also configure the Plugin through Plugin metadata.
The following configu
| timeout | integer | False | 5000 |
| Loggly send data request timeout in milliseconds.
|
| protocol | string | False | "syslog" | [ "syslog" ,
"http", "https" ] | Protocol in which the logs are sent to Loggly.
|
| log_format | object | False | nil |
| Log format declared as key-value pairs in JSON. Values support
strings and nested objects (up to five levels deep; deeper fields are
truncated). Within strings, [APISIX](../apisix-variable.md) or
[NGINX](http://nginx.org/en/docs/varindex.html) variables can be referenced by
prefixing with `$`. |
+| log_format_extra | object | False | |
| Extra log fields **added on top of** the default log entry,
keeping every default field instead of replacing them (unlike `log_format`).
Same value syntax as `log_format`. Ignored when `log_format` is set. |
We support
[Syslog](https://documentation.solarwinds.com/en/success_center/loggly/content/admin/streaming-syslog-without-using-files.htm),
[HTTP/S](https://documentation.solarwinds.com/en/success_center/loggly/content/admin/http-bulk-endpoint.htm)
(bulk endpoint) protocols to send log events to Loggly. By default, in APISIX
side, the protocol is set to "syslog". It lets you send RFC5424 compliant
syslog events with some fine-grained control (log severity mapping based on
upstream HTTP re [...]
diff --git a/docs/en/latest/plugins/loki-logger.md
b/docs/en/latest/plugins/loki-logger.md
index 07d77f857..6b7bd6c93 100644
--- a/docs/en/latest/plugins/loki-logger.md
+++ b/docs/en/latest/plugins/loki-logger.md
@@ -52,7 +52,8 @@ When enabled, the Plugin will serialize the request context
information to [JSON
| keepalive | boolean | False | true | | If true, keep the
connection alive for multiple requests. |
| keepalive_timeout | integer | False | 60000 | >=1000 | Keepalive
timeout in milliseconds. |
| keepalive_pool | integer | False | 5 | >=1 | Maximum
number of connections in the connection pool. |
-| log_format | object | False | | | Custom log format as key-value
pairs in JSON. Values support strings and nested objects (up to five levels
deep; deeper fields are truncated). Within strings, [APISIX
variables](../apisix-variable.md) and [NGINX
variables](http://nginx.org/en/docs/varindex.html) can be referenced by
prefixing with `$`. |
+| log_format | object | False | | | Custom log format as key-value
pairs in JSON. Setting this **replaces** the default log entry with a flat
custom format. Values support strings and nested objects (up to five levels
deep; deeper fields are truncated). Within strings, [APISIX
variables](../apisix-variable.md) and [NGINX
variables](http://nginx.org/en/docs/varindex.html) can be referenced by
prefixing with `$`. |
+| log_format_extra | object | False | | | Extra log fields **added
on top of** the default log entry, keeping every default field instead of
replacing them (unlike `log_format`). Same value syntax as `log_format`.
Ignored when `log_format` is set. |
| name | string | False | loki-logger | | Unique identifier of the Plugin
for the batch processor. If you use [Prometheus](./prometheus.md) to monitor
APISIX metrics, the name is exported in `apisix_batch_process_entries`. |
| include_req_body | boolean | False | false | | If true, include the
request body in the log. Note that if the request body is too big to be kept in
the memory, it can not be logged due to NGINX's limitations. |
| include_req_body_expr | array[array] | False | | | An array of one or
more conditions in the form of
[lua-resty-expr](https://github.com/api7/lua-resty-expr). Used when the
`include_req_body` is true. Request body would only be logged when the
expressions configured here evaluate to true. |
@@ -69,7 +70,8 @@ You can also configure log format on a global scale using the
[Plugin Metadata](
| Name | Type | Required | Default | Valid values | Description |
|------|------|----------|---------|--------------|-------------|
-| log_format | object | False | | | Custom log format as key-value pairs in
JSON. Values support strings and nested objects (up to five levels deep; deeper
fields are truncated). Within strings, [APISIX
variables](../apisix-variable.md) and [NGINX
variables](http://nginx.org/en/docs/varindex.html) can be referenced by
prefixing with `$`. |
+| log_format | object | False | | | Custom log format as key-value pairs in
JSON. Setting this **replaces** the default log entry with a flat custom
format. Values support strings and nested objects (up to five levels deep;
deeper fields are truncated). Within strings, [APISIX
variables](../apisix-variable.md) and [NGINX
variables](http://nginx.org/en/docs/varindex.html) can be referenced by
prefixing with `$`. |
+| log_format_extra | object | False | | | Extra log fields **added on top
of** the default log entry, keeping every default field instead of replacing
them (unlike `log_format`). Same value syntax as `log_format`. Ignored when
`log_format` is set. |
| max_pending_entries | integer | False | | | Maximum number of pending
entries that can be buffered in batch processor before it starts dropping them.
|
## Examples
diff --git a/docs/en/latest/plugins/rocketmq-logger.md
b/docs/en/latest/plugins/rocketmq-logger.md
index 96a6db7b4..2b3d2a927 100644
--- a/docs/en/latest/plugins/rocketmq-logger.md
+++ b/docs/en/latest/plugins/rocketmq-logger.md
@@ -43,6 +43,7 @@ The `rocketmq-logger` Plugin pushes request and response logs
as JSON objects to
| key | string | False | |
| Key of the message.
|
| tag | string | False | |
| Tag of the message.
|
| log_format | object | False | |
| Custom log format using key-value pairs in JSON format. Values
can reference [NGINX
variables](https://nginx.org/en/docs/http/ngx_http_core_module.html). You can
also configure log format on a global scale using the [plugin
metadata](../terminology/plugin-metadata.md), which configures the log format
for all `rocketmq-logger` Plugin instances. If the log format configured on the
individual Plugin [...]
+| log_format_extra | object | False | |
| Extra log fields **added on top of** the default log entry,
keeping every default field instead of replacing them (unlike `log_format`).
Same value syntax as `log_format`. Ignored when `log_format` is set. |
| timeout | integer | False | 3 |
| Timeout for the upstream to send data.
|
| use_tls | boolean | False | false |
| If true, enable TLS encryption for the connection.
|
| access_key | string | False | |
| Access key for ACL. Setting to an empty string will disable the
ACL.
|
@@ -70,6 +71,7 @@ You can also set the format of the logs by configuring the
Plugin metadata. The
| Name | Type | Required | Description
|
|---------------------|---------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| log_format | object | False | Custom log format using key-value
pairs in JSON format. Values can reference [NGINX
variables](https://nginx.org/en/docs/http/ngx_http_core_module.html).
|
+| log_format_extra | object | False | Extra log fields **added on
top of** the default log entry, keeping every default field instead of
replacing them (unlike `log_format`). Same value syntax as `log_format`.
Ignored when `log_format` is set. |
| max_pending_entries | integer | False | Maximum number of unprocessed
entries allowed in the batch processor. When this limit is reached, new entries
will be dropped until the backlog is reduced. Available in APISIX from version
3.15.0. |
:::info IMPORTANT
diff --git a/docs/en/latest/plugins/skywalking-logger.md
b/docs/en/latest/plugins/skywalking-logger.md
index 5b036b662..d61589e7f 100644
--- a/docs/en/latest/plugins/skywalking-logger.md
+++ b/docs/en/latest/plugins/skywalking-logger.md
@@ -46,6 +46,7 @@ If there is an existing tracing context, it sets up the
trace-log correlation au
| service_name | string | False | "APISIX" |
| Service name for the SkyWalking reporter.
|
| service_instance_name | string | False | "APISIX Instance Name" |
| Service instance name for the SkyWalking reporter. Set it to
`$hostname` to directly get the local hostname. |
| log_format | object | False | | Custom log
format as key-value pairs in JSON. Values support strings and nested objects
(up to five levels deep; deeper fields are truncated). Within strings,
[APISIX](../apisix-variable.md) or [NGINX
variables](http://nginx.org/en/docs/varindex.html) can be referenced by
prefixing with `$`. |
+| log_format_extra | object | False | | Extra
log fields **added on top of** the default log entry, keeping every default
field instead of replacing them (unlike `log_format`). Same value syntax as
`log_format`. Ignored when `log_format` is set. |
| timeout | integer | False | 3 |
[1,...] | Time to keep the connection alive for after sending a request.
|
| name | string | False | "skywalking logger" |
| Unique identifier to identify the logger. If you use Prometheus to
monitor APISIX metrics, the name is exported in `apisix_batch_process_entries`.
|
| include_req_body | boolean | False | false | If true,
include the request body in the log. Note that if the request body is too big
to be kept in the memory, it can not be logged due to NGINX's limitations.
|
@@ -64,6 +65,7 @@ You can also set the format of the logs by configuring the
Plugin metadata. The
| Name | Type | Required | Default
| Description
|
| ---------- | ------ | -------- |
----------------------------------------------------------------------------- |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
| log_format | object | False | | Custom log format as key-value pairs in
JSON. Values support strings and nested objects (up to five levels deep; deeper
fields are truncated). Within strings, [APISIX](../apisix-variable.md) or
[NGINX variables](http://nginx.org/en/docs/varindex.html) can be referenced by
prefixing with `$`. |
+| log_format_extra | object | False | | Extra log fields **added on top
of** the default log entry, keeping every default field instead of replacing
them (unlike `log_format`). Same value syntax as `log_format`. Ignored when
`log_format` is set. |
| max_pending_entries | integer | False | | Maximum number of pending entries
that can be buffered in batch processor before it starts dropping them. |
## Examples
diff --git a/docs/en/latest/plugins/sls-logger.md
b/docs/en/latest/plugins/sls-logger.md
index c13fc2cb0..b5d8ab1d4 100644
--- a/docs/en/latest/plugins/sls-logger.md
+++ b/docs/en/latest/plugins/sls-logger.md
@@ -41,6 +41,7 @@ It might take some time to receive the log data. It will be
automatically sent a
| port | True | Target upstream port. Defaults to `10009`.
|
| timeout | False | Timeout for the upstream to send data.
|
| log_format | False | Log format declared as key-value pairs in
JSON. Values support strings and nested objects (up to five levels deep; deeper
fields are truncated). Within strings, [APISIX](../apisix-variable.md) or
[NGINX](http://nginx.org/en/docs/varindex.html) variables can be referenced by
prefixing with `$`. |
+| log_format_extra | False | Extra log fields **added on top of** the
default log entry, keeping every default field instead of replacing them
(unlike `log_format`). Same value syntax as `log_format`. Ignored when
`log_format` is set. |
| project | True | Project name in Alibaba Cloud log service.
Create SLS before using this Plugin.
|
| logstore | True | logstore name in Ali Cloud log service.
Create SLS before using this Plugin.
|
| access_key_id | True | AccessKey ID in Alibaba Cloud. See
[Authorization](https://www.alibabacloud.com/help/en/log-service/latest/create-a-ram-user-and-authorize-the-ram-user-to-access-log-service)
for more details.
|
@@ -91,6 +92,7 @@ You can also set the format of the logs by configuring the
Plugin metadata. The
| Name | Type | Required | Default
| Description
|
| ---------- | ------ | -------- |
----------------------------------------------------------------------------- |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
| log_format | object | False | | Log format declared as key-value pairs
in JSON. Values support strings and nested objects (up to five levels deep;
deeper fields are truncated). Within strings, [APISIX](../apisix-variable.md)
or [NGINX](http://nginx.org/en/docs/varindex.html) variables can be referenced
by prefixing with `$`. |
+| log_format_extra | object | False | | Extra log fields **added on top
of** the default log entry, keeping every default field instead of replacing
them (unlike `log_format`). Same value syntax as `log_format`. Ignored when
`log_format` is set. |
:::info IMPORTANT
diff --git a/docs/en/latest/plugins/splunk-hec-logging.md
b/docs/en/latest/plugins/splunk-hec-logging.md
index 44bf51be8..6ebab57ce 100644
--- a/docs/en/latest/plugins/splunk-hec-logging.md
+++ b/docs/en/latest/plugins/splunk-hec-logging.md
@@ -48,6 +48,7 @@ The `splunk-hec-logging` Plugin serializes request and
response context informat
| endpoint.keepalive_timeout | integer | False | 60000 | >= 1000
| Keepalive timeout in milliseconds.
|
| ssl_verify | boolean | False | true |
| If `true`, enables SSL verification as per [OpenResty
docs](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake).
|
| log_format | object | False | |
| Custom log format using key-value pairs in JSON format. Values can
reference [APISIX variables](../apisix-variable.md) or [NGINX
variables](https://nginx.org/en/docs/http/ngx_http_core_module.html) by
prefixing with `$`. You can also configure log format on a global scale using
[Plugin Metadata](#plugin-metadata). |
+| log_format_extra | object | False | |
| Extra log fields **added on top of** the default log entry,
keeping every default field instead of replacing them (unlike `log_format`).
Same value syntax as `log_format`. Ignored when `log_format` is set. |
| name | string | False | splunk-hec-logging |
| Unique identifier of the Plugin for the batch processor.
|
| batch_max_size | integer | False | 1000 | greater
than 0 | The number of log entries allowed in one batch. Once reached, the
batch will be sent to Splunk HEC. Setting this parameter to `1` means immediate
processing.
|
| inactive_timeout | integer | False | 5 | greater
than 0 | The maximum time in seconds to wait for new logs before sending the
batch to the logging service. The value should be smaller than
`buffer_duration`.
|
@@ -62,6 +63,7 @@ This Plugin supports using batch processors to aggregate and
process entries (lo
| Name | Type | Required | Default | Valid values |
Description
|
|--------------------|---------|----------|---------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| log_format | object | False | | | Custom
log format using key-value pairs in JSON format. Values can reference [APISIX
variables](../apisix-variable.md) or [NGINX
variables](https://nginx.org/en/docs/http/ngx_http_core_module.html) by
prefixing with `$`. This configuration is global and applies to all Routes and
Services that use the `splunk-hec-logging` Plugin. |
+| log_format_extra | object | False | | |
Extra log fields **added on top of** the default log entry, keeping every
default field instead of replacing them (unlike `log_format`). Same value
syntax as `log_format`. Ignored when `log_format` is set. |
| max_pending_entries | integer | False | | >= 1 | Maximum
number of unprocessed entries allowed in the batch processor. When this limit
is reached, new entries will be dropped until the backlog is reduced.
|
## Examples
diff --git a/docs/en/latest/plugins/syslog.md b/docs/en/latest/plugins/syslog.md
index 13eb01690..787c695d1 100644
--- a/docs/en/latest/plugins/syslog.md
+++ b/docs/en/latest/plugins/syslog.md
@@ -47,6 +47,7 @@ The `syslog` Plugin pushes request and response logs as JSON
objects to syslog s
| sock_type | string | False | `tcp` | `tcp` or `udp`
| Transport layer protocol to use.
|
| pool_size | integer | False | 5 | greater than or
equal to 5 | Keep-alive pool size used by `sock:keepalive`.
|
| log_format | object | False | |
| Custom log format using key-value pairs in JSON format. Values can
reference [NGINX
variables](https://nginx.org/en/docs/http/ngx_http_core_module.html). You can
also configure log format on a global scale using the [plugin
metadata](../terminology/plugin-metadata.md), which configures the log format
for all `syslog` Plugin instances. If the log format configured on the
individual Plugin instance diffe [...]
+| log_format_extra | object | False | |
| Extra log fields **added on top of** the default log entry,
keeping every default field instead of replacing them (unlike `log_format`).
Same value syntax as `log_format`. Ignored when `log_format` is set. |
| include_req_body | boolean | False | false |
| If true, include the request body in the log. Note that if the request
body is too big to be kept in the memory, it cannot be logged due to NGINX's
limitations. |
| include_req_body_expr | array | False | |
| An array of one or more conditions in the form of
[lua-resty-expr](https://github.com/api7/lua-resty-expr) expressions. Used when
`include_req_body` is true. Request body is only logged when the expressions
configured here evaluate to true. |
| include_resp_body | boolean | False | false |
| If true, include the response body in the log.
|
@@ -73,6 +74,7 @@ You can also set the format of the logs by configuring the
Plugin metadata. The
| Name | Type | Required | Description
|
|------------|--------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| log_format | object | False | Custom log format using key-value pairs in
JSON format. Values can reference [NGINX
variables](https://nginx.org/en/docs/http/ngx_http_core_module.html).
|
+| log_format_extra | object | False | Extra log fields **added on top of**
the default log entry, keeping every default field instead of replacing them
(unlike `log_format`). Same value syntax as `log_format`. Ignored when
`log_format` is set. |
:::info IMPORTANT
diff --git a/docs/en/latest/plugins/tcp-logger.md
b/docs/en/latest/plugins/tcp-logger.md
index 582610994..ac284d414 100644
--- a/docs/en/latest/plugins/tcp-logger.md
+++ b/docs/en/latest/plugins/tcp-logger.md
@@ -44,6 +44,7 @@ This plugin also allows to push logs as a batch to your
external TCP server. It
| port | integer | True | | [0,...] | Target
upstream port. |
| timeout | integer | False | 1000 | [1,...] | Timeout for
the upstream to send data. |
| log_format | object | False | | | Log format
declared as key-value pairs in JSON. Values support strings and nested objects
(up to five levels deep; deeper fields are truncated). Within strings,
[APISIX](../apisix-variable.md) or
[NGINX](http://nginx.org/en/docs/varindex.html) variables can be referenced by
prefixing with `$`. |
+| log_format_extra | object | False | | | Extra log
fields **added on top of** the default log entry, keeping every default field
instead of replacing them (unlike `log_format`). Same value syntax as
`log_format`. Ignored when `log_format` is set. |
| tls | boolean | False | false | | When set to
`true` performs SSL verification. |
| tls_options | string | False | | | TLS
options. |
| include_req_body | boolean | False | false | [false, true] | When set
to `true` includes the request body in the log. |
@@ -102,6 +103,7 @@ You can also set the format of the logs by configuring the
Plugin metadata. The
| Name | Type | Required | Default
| Description
|
| ---------- | ------ | -------- |
----------------------------------------------------------------------------- |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
| log_format | object | False | | Log format declared as key-value pairs
in JSON. Values support strings and nested objects (up to five levels deep;
deeper fields are truncated). Within strings, [APISIX](../apisix-variable.md)
or [NGINX](http://nginx.org/en/docs/varindex.html) variables can be referenced
by prefixing with `$`. |
+| log_format_extra | object | False | | Extra log fields **added on top
of** the default log entry, keeping every default field instead of replacing
them (unlike `log_format`). Same value syntax as `log_format`. Ignored when
`log_format` is set. |
| max_pending_entries | integer | False | | Maximum number of pending entries
that can be buffered in batch processor before it starts dropping them. |
:::info IMPORTANT
diff --git a/docs/en/latest/plugins/tencent-cloud-cls.md
b/docs/en/latest/plugins/tencent-cloud-cls.md
index 5d667efa1..980072d3a 100644
--- a/docs/en/latest/plugins/tencent-cloud-cls.md
+++ b/docs/en/latest/plugins/tencent-cloud-cls.md
@@ -50,6 +50,7 @@ The `tencent-cloud-cls` Plugin uses [TencentCloud
CLS](https://cloud.tencent.com
| max_resp_body_bytes | integer | False | 524288 | >=1 | Response bodies
within this size will be logged, if the size exceeds the configured value it
will be truncated before logging. |
| global_tag | object | No | | | kv pairs
in JSON,send with each log.
|
| log_format | object | No | | | Log format
declared as key-value pairs in JSON. Values support strings and nested objects
(up to five levels deep; deeper fields are truncated). Within strings,
[APISIX](../apisix-variable.md) or
[NGINX](http://nginx.org/en/docs/varindex.html) variables can be referenced by
prefixing with `$`. |
+| log_format_extra | object | No | | |
Extra log fields **added on top of** the default log entry, keeping every
default field instead of replacing them (unlike `log_format`). Same value
syntax as `log_format`. Ignored when `log_format` is set. |
NOTE: `encrypt_fields = {"secret_key"}` is also defined in the schema, which
means that the field will be stored encrypted in etcd. See [encrypted storage
fields](../plugin-develop.md#encrypted-storage-fields).
@@ -102,6 +103,7 @@ You can also set the format of the logs by configuring the
Plugin metadata. The
| Name | Type | Required | Default
| Description
|
| ---------- | ------ | -------- |
----------------------------------------------------------------------------- |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
| log_format | object | False | | Log format declared as key-value pairs
in JSON. Values support strings and nested objects (up to five levels deep;
deeper fields are truncated). Within strings, [APISIX](../apisix-variable.md)
or [NGINX](http://nginx.org/en/docs/varindex.html) variables can be referenced
by prefixing with `$`. |
+| log_format_extra | object | False | | Extra log fields **added on top
of** the default log entry, keeping every default field instead of replacing
them (unlike `log_format`). Same value syntax as `log_format`. Ignored when
`log_format` is set. |
| max_pending_entries | integer | False | | Maximum number of pending entries
that can be buffered in batch processor before it starts dropping them. |
:::info IMPORTANT
diff --git a/docs/en/latest/plugins/udp-logger.md
b/docs/en/latest/plugins/udp-logger.md
index 1d81c11f7..9eca52e93 100644
--- a/docs/en/latest/plugins/udp-logger.md
+++ b/docs/en/latest/plugins/udp-logger.md
@@ -43,6 +43,7 @@ This plugin also allows to push logs as a batch to your
external UDP server. It
| port | integer | True | | [0,...] | Target
upstream port. |
| timeout | integer | False | 3 | [1,...] |
Timeout for the upstream to send data. |
| log_format | object | False | | | Log format
declared as key-value pairs in JSON. Values support strings and nested objects
(up to five levels deep; deeper fields are truncated). Within strings,
[APISIX](../apisix-variable.md) or
[NGINX](http://nginx.org/en/docs/varindex.html) variables can be referenced by
prefixing with `$`. |
+| log_format_extra | object | False | | | Extra log
fields **added on top of** the default log entry, keeping every default field
instead of replacing them (unlike `log_format`). Same value syntax as
`log_format`. Ignored when `log_format` is set. |
| name | string | False | "udp logger" | | Unique
identifier for the batch processor. If you use Prometheus to monitor APISIX
metrics, the name is exported in `apisix_batch_process_entries`. processor.
|
| include_req_body | boolean | False | false | [false, true] | When
set to `true` includes the request body in the log. |
| include_req_body_expr | array | No | | |
Filter for when the `include_req_body` attribute is set to `true`. Request body
is only logged when the expression set here evaluates to `true`. See
[lua-resty-expr](https://github.com/api7/lua-resty-expr) for more.
|
@@ -100,6 +101,7 @@ You can also set the format of the logs by configuring the
Plugin metadata. The
| Name | Type | Required | Default
| Description
|
| ---------- | ------ | -------- |
----------------------------------------------------------------------------- |
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
| log_format | object | False | | Log format declared as key-value pairs
in JSON. Values support strings and nested objects (up to five levels deep;
deeper fields are truncated). Within strings, [APISIX](../apisix-variable.md)
or [NGINX](http://nginx.org/en/docs/varindex.html) variables can be referenced
by prefixing with `$`. |
+| log_format_extra | object | False | | Extra log fields **added on top
of** the default log entry, keeping every default field instead of replacing
them (unlike `log_format`). Same value syntax as `log_format`. Ignored when
`log_format` is set. |
| max_pending_entries | integer | False | | Maximum number of pending entries
that can be buffered in batch processor before it starts dropping them. |
:::info IMPORTANT
diff --git a/t/APISIX.pm b/t/APISIX.pm
index 9c04f47ff..e0b86560b 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -718,7 +718,7 @@ _EOC_
require("apisix").http_exit_worker()
}
- log_format main escape=default '\$remote_addr - \$remote_user
[\$time_local] \$http_host "\$request_line" \$status \$body_bytes_sent
\$request_time "\$http_referer" "\$http_user_agent" \$upstream_addr
\$upstream_status \$apisix_upstream_response_time
"\$upstream_scheme://\$upstream_host\$upstream_uri" \$request_llm_model
\$llm_model \$llm_time_to_first_token \$llm_prompt_tokens
\$llm_completion_tokens \$llm_total_tokens \$llm_stream \$llm_has_tool_calls
\$llm_tool_count \$llm_end_use [...]
+ log_format main escape=default '\$remote_addr - \$remote_user
[\$time_local] \$http_host "\$request_line" \$status \$body_bytes_sent
\$request_time "\$http_referer" "\$http_user_agent" \$upstream_addr
\$upstream_status \$apisix_upstream_response_time
"\$upstream_scheme://\$upstream_host\$upstream_uri" \$request_llm_model
\$llm_model \$llm_time_to_first_token \$llm_prompt_tokens
\$llm_completion_tokens \$llm_total_tokens \$llm_stream \$llm_has_tool_calls
\$llm_tool_count \$llm_end_use [...]
# fake server, only for test
server {
@@ -896,6 +896,7 @@ _EOC_
set \$upstream_scheme 'http';
set \$upstream_host \$http_host;
+ set \$upstream_unresolved_host '';
set \$upstream_uri '';
set \$request_line '';
set \$ctx_ref '';
diff --git a/t/core/ctx.t b/t/core/ctx.t
index d49d87699..af4ab2408 100644
--- a/t/core/ctx.t
+++ b/t/core/ctx.t
@@ -915,3 +915,56 @@ GET /hello
hello world
--- error_log eval
qr/request log:
\{"route_id":"1","route_name":"my_route","service_id":"1","service_name":"my_service"\}/
+
+
+
+=== TEST 36: upstream_unresolved_host is available in the nginx access log
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "upstream": {
+ "nodes": {
+ "localhost:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ -- a real proxied request so the access log line gets written
+ local http = require("resty.http")
+ local httpc = http.new()
+ local res, err = httpc:request_uri(
+ "http://127.0.0.1:" .. ngx.var.server_port .. "/hello")
+ if not res or res.status ~= 200 then
+ ngx.say("request failed: ", err or res.status)
+ return
+ end
+ -- let the log phase flush the access log line
+ ngx.sleep(0.1)
+
+ local fd = assert(io.open(ngx.config.prefix() ..
"logs/access.log", "r"))
+ local content = fd:read("*a")
+ fd:close()
+ -- the configured domain shows up before DNS resolution
+ if content:find("unresolved_host=localhost", 1, true) then
+ ngx.say("found")
+ else
+ ngx.say("not found")
+ end
+ }
+ }
+--- request
+GET /t
+--- response_body
+found
diff --git a/t/plugin/file-logger.t b/t/plugin/file-logger.t
index 127578e34..2213213ba 100644
--- a/t/plugin/file-logger.t
+++ b/t/plugin/file-logger.t
@@ -572,3 +572,420 @@ passed
}
--- response_body
write file log success
+
+
+
+=== TEST 15: log_format_extra enriches the default log without replacing it
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ -- additive log format via plugin metadata: keep the rich default
and
+ -- add the pre-DNS upstream host on top
+ local code, body = t('/apisix/admin/plugin_metadata/file-logger',
+ ngx.HTTP_PUT,
+ [[{
+ "path": "file-logger-extra.log",
+ "log_format_extra": {
+ "upstream_host": "$upstream_unresolved_host"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "file-logger": {}
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1982": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 16: default fields stay and the extra field is added
+--- config
+ location /t {
+ content_by_lua_block {
+ local core = require("apisix.core")
+ local t = require("lib.test_admin").test
+ local code = t("/hello", ngx.HTTP_GET)
+ local fd, err = io.open("file-logger-extra.log", 'r')
+ if not fd then
+ core.log.error("failed to open file: file-logger-extra.log,
error info: ", err)
+ return
+ end
+ local msg = fd:read()
+ fd:close()
+
+ local new_msg = core.json.decode(msg)
+ -- the extra field is present
+ if new_msg.upstream_host == '127.0.0.1' and
+ -- and the rich default fields are still there
+ type(new_msg.request) == "table" and
+ new_msg.request.method == 'GET' and
+ type(new_msg.response) == "table" and
+ new_msg.response.status == 200 and
+ type(new_msg.server) == "table" and
+ new_msg.server.version and
+ new_msg.route_id == '1'
+ then
+ ngx.status = code
+ ngx.say("enrich log format success")
+ else
+ ngx.say("enrich log format failed: " .. msg)
+ end
+ }
+ }
+--- response_body
+enrich log format success
+
+
+
+=== TEST 17: log_format_extra logs the pre-DNS host for a domain upstream
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/plugin_metadata/file-logger',
+ ngx.HTTP_PUT,
+ [[{
+ "path": "file-logger-domain.log",
+ "log_format_extra": {
+ "upstream_host": "$upstream_unresolved_host"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "file-logger": {}
+ },
+ "upstream": {
+ "nodes": {
+ "localhost:1982": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 18: extra field keeps the domain while the default upstream is
resolved
+--- config
+ location /t {
+ content_by_lua_block {
+ local core = require("apisix.core")
+ local t = require("lib.test_admin").test
+ local code = t("/hello", ngx.HTTP_GET)
+ local fd, err = io.open("file-logger-domain.log", 'r')
+ if not fd then
+ core.log.error("failed to open file: file-logger-domain.log,
error info: ", err)
+ return
+ end
+ local msg = fd:read()
+ fd:close()
+
+ local new_msg = core.json.decode(msg)
+ -- the extra field carries the configured hostname, before DNS
+ if new_msg.upstream_host == 'localhost' and
+ -- while the default upstream field is the resolved ip:port
+ new_msg.upstream == '127.0.0.1:1982' and
+ -- and the rich default fields are still there
+ type(new_msg.request) == "table" and
+ new_msg.request.method == 'GET' and
+ type(new_msg.response) == "table" and
+ new_msg.response.status == 200 and
+ type(new_msg.server) == "table" and
+ new_msg.server.version and
+ new_msg.route_id == '1'
+ then
+ ngx.status = code
+ ngx.say("enrich log format success")
+ else
+ ngx.say("enrich log format failed: " .. msg)
+ end
+ }
+ }
+--- response_body
+enrich log format success
+
+
+
+=== TEST 19: log_format wins and log_format_extra is ignored when both are set
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "file-logger": {
+ "path": "file-logger-precedence.log",
+ "log_format": {
+ "msg": "precedence test"
+ },
+ "log_format_extra": {
+ "upstream_host":
"$upstream_unresolved_host"
+ }
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1982": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 20: extra field absent and the default entry is replaced by log_format
+--- config
+ location /t {
+ content_by_lua_block {
+ local core = require("apisix.core")
+ local t = require("lib.test_admin").test
+ local code = t("/hello", ngx.HTTP_GET)
+ local fd, err = io.open("file-logger-precedence.log", 'r')
+ if not fd then
+ core.log.error("failed to open file:
file-logger-precedence.log, error info: ", err)
+ return
+ end
+ local msg = fd:read()
+ fd:close()
+
+ local new_msg = core.json.decode(msg)
+ -- log_format replaced the default entry, extra was ignored
+ if new_msg.msg == 'precedence test' and
+ new_msg.upstream_host == nil and
+ new_msg.request == nil and
+ new_msg.response == nil
+ then
+ ngx.status = code
+ ngx.say("log_format precedence success")
+ else
+ ngx.say("log_format precedence failed: " .. msg)
+ end
+ }
+ }
+--- response_body
+log_format precedence success
+
+
+
+=== TEST 21: route-level log_format_extra overrides the metadata one
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ -- metadata carries one extra field
+ local code, body = t('/apisix/admin/plugin_metadata/file-logger',
+ ngx.HTTP_PUT,
+ [[{
+ "log_format_extra": {
+ "meta_only": "from metadata"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ -- the route sets its own, which must fully replace the metadata
one
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "file-logger": {
+ "path": "file-logger-override.log",
+ "log_format_extra": {
+ "route_field": "from route"
+ }
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1982": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 22: only the route extra field is present, metadata one is dropped
+--- config
+ location /t {
+ content_by_lua_block {
+ local core = require("apisix.core")
+ local t = require("lib.test_admin").test
+ local code = t("/hello", ngx.HTTP_GET)
+ local fd, err = io.open("file-logger-override.log", 'r')
+ if not fd then
+ core.log.error("failed to open file: file-logger-override.log,
error info: ", err)
+ return
+ end
+ local msg = fd:read()
+ fd:close()
+
+ local new_msg = core.json.decode(msg)
+ -- route extra wins, metadata extra is gone, default fields stay
+ if new_msg.route_field == 'from route' and
+ new_msg.meta_only == nil and
+ type(new_msg.request) == "table" and
+ new_msg.request.method == 'GET' and
+ new_msg.route_id == '1'
+ then
+ ngx.status = code
+ ngx.say("route extra precedence success")
+ else
+ ngx.say("route extra precedence failed: " .. msg)
+ end
+ }
+ }
+--- response_body
+route extra precedence success
+
+
+
+=== TEST 23: log_format_extra logs the pre-DNS host for a multi-node domain
upstream
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/plugin_metadata/file-logger',
+ ngx.HTTP_PUT,
+ [[{
+ "log_format_extra": {
+ "upstream_host": "$upstream_unresolved_host"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ -- two domain nodes exercise the server_picker path in balancer.lua
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "file-logger": {
+ "path": "file-logger-multinode.log"
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "localhost:1980": 1,
+ "localhost:1982": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 24: the picked node's pre-DNS host is logged regardless of which one
is chosen
+--- config
+ location /t {
+ content_by_lua_block {
+ local core = require("apisix.core")
+ local t = require("lib.test_admin").test
+ local code = t("/hello", ngx.HTTP_GET)
+ local fd, err = io.open("file-logger-multinode.log", 'r')
+ if not fd then
+ core.log.error("failed to open file:
file-logger-multinode.log, error info: ", err)
+ return
+ end
+ local msg = fd:read()
+ fd:close()
+
+ local new_msg = core.json.decode(msg)
+ -- both nodes share the host "localhost", so whichever is picked
logs it
+ if new_msg.upstream_host == 'localhost' and
+ type(new_msg.request) == "table" and
+ new_msg.request.method == 'GET' and
+ new_msg.route_id == '1'
+ then
+ ngx.status = code
+ ngx.say("enrich log format success")
+ else
+ ngx.say("enrich log format failed: " .. msg)
+ end
+ }
+ }
+--- response_body
+enrich log format success
diff --git a/t/plugin/loki-logger.t b/t/plugin/loki-logger.t
index a72c6f589..89d9b4c8f 100644
--- a/t/plugin/loki-logger.t
+++ b/t/plugin/loki-logger.t
@@ -522,3 +522,97 @@ passed
}
--- response_body
passed
+
+
+
+=== TEST 19: setup route (log_format_extra enriches default via plugin
metadata)
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ -- additive log format: keep the rich default and add the pre-DNS
+ -- upstream host on top
+ local code, body = t('/apisix/admin/plugin_metadata/loki-logger',
+ ngx.HTTP_PUT,
+ [[{
+ "log_format_extra": {
+ "upstream_host": "$upstream_unresolved_host"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "loki-logger": {
+ "endpoint_addrs": ["http://127.0.0.1:3100"],
+ "tenant_id": "tenant_1",
+ "log_labels": {
+ "enrich_label": "enrich_value"
+ },
+ "batch_max_size": 1
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 20: hit route
+--- request
+GET /hello
+--- response_body
+hello world
+
+
+
+=== TEST 21: check loki log (default fields kept + extra field added)
+--- config
+ location /t {
+ content_by_lua_block {
+ local cjson = require("cjson")
+ local now = ngx.now() * 1000
+ local data, err = require("lib.grafana_loki").fetch_logs_from_loki(
+ tostring(now - 3000) .. "000000", -- from
+ tostring(now) .. "000000", -- to
+ { query = [[{enrich_label="enrich_value"} | json]] }
+ )
+
+ assert(err == nil, "fetch logs error: " .. (err or ""))
+ assert(data.status == "success", "loki response error: " ..
cjson.encode(data))
+ assert(#data.data.result > 0, "loki log empty: " ..
cjson.encode(data))
+
+ local entry = data.data.result[1]
+ -- the extra field is added
+ assert(entry.stream.upstream_host == "127.0.0.1",
+ "expected extra field upstream_host: " ..
cjson.encode(entry))
+ -- the rich default fields are still present
+ assert(entry.stream.route_id == "1",
+ "expected default field route_id: " .. cjson.encode(entry))
+ assert(entry.stream.request_method == "GET",
+ "expected default field request_method: " ..
cjson.encode(entry))
+ }
+ }
+--- error_code: 200
diff --git a/t/plugin/splunk-hec-logging.t b/t/plugin/splunk-hec-logging.t
index 4610784af..18d606200 100644
--- a/t/plugin/splunk-hec-logging.t
+++ b/t/plugin/splunk-hec-logging.t
@@ -533,3 +533,71 @@ true
true
--- no_error_log
[alert]
+
+
+
+=== TEST 15: log_format_extra surfaces on the default (non-customized) event
path
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ -- metadata carries only log_format_extra, so the default event
path runs
+ local code, body =
t('/apisix/admin/plugin_metadata/splunk-hec-logging',
+ ngx.HTTP_PUT,
+ [[{
+ "log_format_extra": {
+ "upstream_host": "$upstream_unresolved_host"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ local code, body = t('/apisix/admin/routes/1', ngx.HTTP_PUT, {
+ uri = "/hello",
+ upstream = {
+ type = "roundrobin",
+ nodes = {
+ ["127.0.0.1:1982"] = 1
+ }
+ },
+ plugins = {
+ ["splunk-hec-logging"] = {
+ endpoint = {
+ uri = "http://127.0.0.1:18088/services/collector",
+ token = "BD274822-96AA-4DA6-90EC-18940FB2414C"
+ },
+ batch_max_size = 1,
+ inactive_timeout = 1
+ }
+ }
+ })
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ local code = t("/hello", "GET")
+ if code >= 300 then
+ ngx.status = code
+ ngx.say("fail")
+ return
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+--- wait: 5
+
+
+
+=== TEST 16: check splunk log carries the extra field
+--- exec
+tail -n 1 ci/pod/vector/splunk.log
+--- response_body eval
+qr/.*"upstream_host":"127.0.0.1".*/