跳到主要内容

elasticsearch-logger

elasticsearch-logger 插件批量将请求和响应日志发送到 Elasticsearch。插件使用 Elasticsearch Bulk API 格式序列化每条日志,并支持自定义日志字段和索引名称。通过此集成,可以集中存储网关日志,并在 Kibana 中搜索、分析和可视化。

示例

以下示例演示如何为常见日志场景配置 elasticsearch-logger 插件。

要完成这些示例,请启动 Elasticsearch 和 Kibana。两者应使用相同的 Elastic Stack 版本。在 Docker 中,网关和 Elasticsearch 共享专用网络,使网关能够通过容器名称访问 Elasticsearch。

本地评估环境

Elastic Stack 默认启用身份认证和 TLS。为简化本地评估,以下设置保留身份认证,但禁用 Elasticsearch HTTP 和 Transport 接口的 TLS。Docker 端口仅绑定到环回接口,Kubernetes 服务也仅在集群内可用。

生产部署应使用由受信任证书颁发机构签发的 HTTPS 证书。如果证书使用私有 CA,请将 CA Bundle 添加到 apisix.ssl.ssl_trusted_certificate,并保持插件的 ssl_verify 选项启用。请将凭据存储在 Secret Manager 中,而不是配置文件中。

在 Linux 上,Elasticsearch 要求运行容器的主机或虚拟机将 vm.max_map_count 至少设置为 1048576。启动 Docker 或 Kubernetes 环境前,请检查当前值:

sysctl vm.max_map_count

如果值较低,请在 Docker 主机或每个 Kubernetes 节点上提高该值。此命令需要管理员权限:

sudo sysctl -w vm.max_map_count=1048576

对于 Docker Desktop 和托管 Kubernetes 环境,请按照 Elastic 的特定平台虚拟内存说明,将设置应用到其底层 Linux 环境。

GATEWAY_CONTAINER 设置为正在运行的 APISIX 或 API7 网关容器名称。创建专用 Docker 网络并将网关连接到该网络:

export GATEWAY_CONTAINER=replace-with-gateway-container-name

docker network create gateway-elasticsearch-net
docker network connect gateway-elasticsearch-net "$GATEWAY_CONTAINER"

启动启用了身份认证的 Elasticsearch:

docker run -d \
--name elasticsearch \
--network gateway-elasticsearch-net \
-v elasticsearch_logger_vol:/usr/share/elasticsearch/data/ \
-p 127.0.0.1:9200:9200 \
-e ELASTIC_PASSWORD=gateway-elastic-password \
-e ES_JAVA_OPTS="-Xms768m -Xmx768m" \
-e discovery.type=single-node \
-e xpack.security.enabled=true \
-e xpack.security.autoconfiguration.enabled=false \
-e xpack.security.http.ssl.enabled=false \
-e xpack.security.transport.ssl.enabled=false \
docker.elastic.co/elasticsearch/elasticsearch:9.5.3

等待 Elasticsearch 可用:

until curl -fsS -u "elastic:gateway-elastic-password" \
"http://127.0.0.1:9200/_cluster/health?wait_for_status=yellow" > /dev/null; do
sleep 2
done

为 Kibana 内部用户设置密码:

curl "http://127.0.0.1:9200/_security/user/kibana_system/_password" \
-u "elastic:gateway-elastic-password" \
-H "Content-Type: application/json" \
-X POST \
-d '{"password":"gateway-kibana-password"}'

创建一个可以监控集群、但只能写入这些示例所用索引的角色:

curl "http://127.0.0.1:9200/_security/role/gateway_logger" \
-u "elastic:gateway-elastic-password" \
-H "Content-Type: application/json" \
-X PUT \
-d '{
"cluster": ["monitor"],
"indices": [
{
"names": ["gateway", "gateway-*"],
"privileges": ["auto_configure", "create_index", "write"]
}
]
}'

创建用户并分配 Logger 角色:

curl "http://127.0.0.1:9200/_security/user/gateway_logger" \
-u "elastic:gateway-elastic-password" \
-H "Content-Type: application/json" \
-X PUT \
-d '{
"password": "gateway-logger-password",
"roles": ["gateway_logger"]
}'

启动 Kibana 以可视化索引数据:

docker run -d \
--name kibana \
--network gateway-elasticsearch-net \
-p 127.0.0.1:5601:5601 \
-e ELASTICSEARCH_HOSTS="http://elasticsearch:9200" \
-e ELASTICSEARCH_USERNAME=kibana_system \
-e ELASTICSEARCH_PASSWORD=gateway-kibana-password \
docker.elastic.co/kibana/kibana:9.5.3

Kibana 可用后,打开 localhost:5601,使用用户名 elastic 和密码 gateway-elastic-password 登录。

使用默认格式记录请求日志

以下示例在路由上启用插件,将请求和响应信息发送到 gateway 索引。

创建路由:

curl "http://127.0.0.1:9180/apisix/admin/routes/elasticsearch-logger-route" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"uri": "/anything",
"plugins": {
"elasticsearch-logger": {
"endpoint_addrs": ["http://elasticsearch:9200"],
"field": {
"index": "gateway"
},
"auth": {
"username": "gateway_logger",
"password": "gateway-logger-password"
}
}
},
"upstream": {
"nodes": {
"httpbin.org:80": 1
},
"type": "roundrobin"
}
}'

❶ 配置 Elasticsearch 端点。端点末尾不应包含斜杠。

❷ 将 index 字段配置为 gateway

这些示例中的凭据属于之前创建的本地评估用户。请将它们替换为按照组织安全策略管理的凭据。

发送一个请求到该路由以生成一条日志条目:

curl -i "http://127.0.0.1:9080/anything"

你应该收到 HTTP/1.1 200 OK 响应。批处理器可能需要数秒才能发送日志条目。

在 Kibana 中打开 Discover,创建索引模式为 gateway 的数据视图。新日志条目应包含与以下内容类似的字段:

{
"_index": "gateway",
"_id": "CE-JL5QBOkdYRG7kEjTJ",
"_version": 1,
"_score": 1,
"_source": {
"request": {
"headers": {
"host": "127.0.0.1:9080",
"accept": "*/*",
"user-agent": "curl/8.6.0"
},
"size": 85,
"querystring": {},
"method": "GET",
"url": "http://127.0.0.1:9080/anything",
"uri": "/anything"
},
"response": {
"headers": {
"content-type": "application/json",
"access-control-allow-credentials": "true",
"content-length": "390",
"access-control-allow-origin": "*",
"connection": "close",
"date": "Mon, 13 Jan 2025 10:18:14 GMT"
},
"status": 200,
"size": 618
},
"route_id": "elasticsearch-logger-route",
"latency": 585.00003814697,
"apisix_latency": 18.000038146973,
"upstream_latency": 567,
"upstream": "50.19.58.113:80",
"service_id": "",
"client_ip": "192.168.65.1"
},
"fields": {
...
}
}

使用插件元数据记录请求头和响应头

以下示例使用插件元数据内置变量记录指定的请求头和响应头。

插件元数据为同一插件的所有实例配置通用元数据字段。插件在多个资源上启用且需要统一更新元数据字段时,此功能十分有用。

首先,创建一个启用该插件的路由:

curl "http://127.0.0.1:9180/apisix/admin/routes/elasticsearch-logger-route" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"uri": "/anything",
"plugins": {
"elasticsearch-logger": {
"endpoint_addrs": ["http://elasticsearch:9200"],
"field": {
"index": "gateway"
},
"auth": {
"username": "gateway_logger",
"password": "gateway-logger-password"
}
}
},
"upstream": {
"nodes": {
"httpbin.org:80": 1
},
"type": "roundrobin"
}
}'

接下来,配置 elasticsearch-logger 的插件元数据:

curl "http://127.0.0.1:9180/apisix/admin/plugin_metadata/elasticsearch-logger" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"log_format": {
"host": "$host",
"@timestamp": "$time_iso8601",
"client_ip": "$remote_addr",
"env": "$http_env",
"resp_content_type": "$sent_http_Content_Type"
}
}'

❶ 记录自定义请求头 env

❷ 记录响应头 Content-Type

发送带有 env 头的请求到该路由:

curl -i "http://127.0.0.1:9080/anything" -H "env: dev"

你应该收到 HTTP/1.1 200 OK 响应。

在 Kibana Discover 中,日志条目应包含这些自定义字段:

{
"_index": "gateway",
"_id": "Ck-WL5QBOkdYRG7kODS0",
"_version": 1,
"_score": 1,
"_source": {
"client_ip": "192.168.65.1",
"route_id": "elasticsearch-logger-route",
"@timestamp": "2025-01-06T10:32:36+00:00",
"host": "127.0.0.1",
"env": "dev",
"resp_content_type": "application/json"
},
"fields": {
...
}
}

有条件地记录请求体

以下示例仅在请求满足 APISIX 表达式时记录请求体。

如果通过 Admin API 完成了上一个示例,请先删除自定义插件元数据再继续:

curl "http://127.0.0.1:9180/apisix/admin/plugin_metadata/elasticsearch-logger" -X DELETE \
-H "X-API-KEY: ${ADMIN_API_KEY}"

使用 ADC 时,请通过 --include-resource-type plugin_metadata 同步空的 plugin_metadata 映射。使用 Ingress Controller 时,请从 GatewayProxy 资源中删除插件元数据并重新应用。

创建路由:

curl "http://127.0.0.1:9180/apisix/admin/routes/elasticsearch-logger-route" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"plugins": {
"elasticsearch-logger": {
"endpoint_addrs": ["http://elasticsearch:9200"],
"field": {
"index": "gateway"
},
"auth": {
"username": "gateway_logger",
"password": "gateway-logger-password"
},
"include_req_body": true,
"include_req_body_expr": [["arg_log_body", "==", "yes"]]
}
},
"upstream": {
"nodes": {
"httpbin.org:80": 1
},
"type": "roundrobin"
},
"uri": "/anything"
}'

❶ 将 include_req_body 设为 true 以包含请求体。

❷ 设置 include_req_body_expr,仅当 log_body 查询参数为 yes 时包含请求体。

发送包含满足条件的查询参数的请求:

curl -i "http://127.0.0.1:9080/anything?log_body=yes" -X POST -d '{"env": "dev"}'

你应该收到 HTTP/1.1 200 OK 响应。

在 Kibana Discover 中,日志条目应包含请求体:

{
"_index": "gateway",
"_id": "Dk-cL5QBOkdYRG7k7DSW",
"_version": 1,
"_score": 1,
"_source": {
"request": {
"headers": {
"user-agent": "curl/8.6.0",
"accept": "*/*",
"content-length": "14",
"host": "127.0.0.1:9080",
"content-type": "application/x-www-form-urlencoded"
},
"size": 182,
"querystring": {
"log_body": "yes"
},
"body": "{\"env\": \"dev\"}",
"method": "POST",
"url": "http://127.0.0.1:9080/anything?log_body=yes",
"uri": "/anything?log_body=yes"
},
"start_time": 1735965595203,
"response": {
"headers": {
"content-type": "application/json",
"access-control-allow-credentials": "true",
"content-length": "548",
"access-control-allow-origin": "*",
"connection": "close",
"date": "Mon, 13 Jan 2025 11:02:32 GMT"
},
"status": 200,
"size": 776
},
"route_id": "elasticsearch-logger-route",
"latency": 703.9999961853,
"apisix_latency": 34.999996185303,
"upstream_latency": 669,
"upstream": "34.197.122.172:80",
"service_id": "",
"client_ip": "192.168.65.1"
},
"fields": {
...
}
}

发送一个不带 URL 查询字符串的请求到该路由:

curl -i "http://127.0.0.1:9080/anything" -X POST -d '{"env": "dev"}'

在 Kibana Discover 中,新日志条目不应包含请求体:

{
"_index": "gateway",
"_id": "EU-eL5QBOkdYRG7kUDST",
"_version": 1,
"_score": 1,
"_source": {
"request": {
"headers": {
"content-type": "application/x-www-form-urlencoded",
"accept": "*/*",
"content-length": "14",
"host": "127.0.0.1:9080",
"user-agent": "curl/8.6.0"
},
"size": 169,
"querystring": {},
"method": "POST",
"url": "http://127.0.0.1:9080/anything",
"uri": "/anything"
},
"start_time": 1735965686363,
"response": {
"headers": {
"content-type": "application/json",
"access-control-allow-credentials": "true",
"content-length": "510",
"access-control-allow-origin": "*",
"connection": "close",
"date": "Mon, 13 Jan 2025 11:15:54 GMT"
},
"status": 200,
"size": 738
},
"route_id": "elasticsearch-logger-route",
"latency": 680.99999427795,
"apisix_latency": 4.9999942779541,
"upstream_latency": 676,
"upstream": "34.197.122.172:80",
"service_id": "",
"client_ip": "192.168.65.1"
},
"fields": {
...
}
}
信息

自定义日志格式不会自动添加已收集的请求体或响应体。请在格式中包含对应变量:

{
"include_req_body": true,
"include_resp_body": true,
"log_format": {
"request_body": "$request_body",
"response_body": "$resp_body"
}
}

请求体和响应体大小限制仍然适用。如需在不替换默认日志条目的情况下添加自定义字段,请使用 log_format_extra

在 Elasticsearch 索引中包含请求日期

以下示例在索引名称中使用 Lua 时间格式,按请求日期组织日志。

创建路由:

curl "http://127.0.0.1:9180/apisix/admin/routes/elasticsearch-logger-route" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"uri": "/anything",
"plugins": {
"elasticsearch-logger": {
"endpoint_addrs": ["http://elasticsearch:9200"],
"field": {
"index": "gateway-{%Y.%m.%d}"
},
"auth": {
"username": "gateway_logger",
"password": "gateway-logger-password"
}
}
},
"upstream": {
"nodes": {
"httpbin.org:80": 1
},
"type": "roundrobin"
}
}'

❶ 配置 Elasticsearch 的端点地址。

❷ 配置 index 字段以使用当前年、月和日。

发送一个请求到该路由以生成一条日志条目:

curl -i "http://127.0.0.1:9080/anything"

你应该收到 HTTP/1.1 200 OK 响应。

在 Kibana 中创建索引模式为 gateway-* 的数据视图。日志条目应使用包含请求日期的索引名称:

{
"_index": "gateway-2026.09.14",
"_id": "CE-KL5QB0kdYRG7dEiTJ",
"_version": 1,
"_score": 1,
"_source": {
"request": {
...
},
"response": {
"status": 200,
"size": 618,
...
}
},
...
}