skywalking-logger
The skywalking-logger plugin sends request and response logs as JSON objects to a SkyWalking OAP server in batches. You can customize the fields included in each log entry.
If there is an existing tracing context, it sets up the trace-log correlation automatically and relies on SkyWalking Cross Process Propagation Headers Protocol.
Examples
The examples show how to send request logs to SkyWalking, customize their contents, and correlate them with distributed traces.
- Docker
- Kubernetes
Create the network used by the SkyWalking containers:
docker network create gateway-skywalking-net
If the gateway also runs in Docker, set GATEWAY_CONTAINER and connect it to the network. Skip this step for a host-installed gateway:
export GATEWAY_CONTAINER=replace-with-gateway-container-name
docker network connect gateway-skywalking-net "$GATEWAY_CONTAINER"
Create the following Docker Compose file:
services:
banyandb:
image: apache/skywalking-banyandb:0.11.0
command: standalone
networks:
- skywalking
oap:
image: apache/skywalking-oap-server:11.0.0
environment:
SW_STORAGE: banyandb
SW_STORAGE_BANYANDB_TARGETS: banyandb:17912
ports:
- "127.0.0.1:12800:12800"
depends_on:
- banyandb
networks:
skywalking:
aliases:
- skywalking-oap
horizon:
image: apache/skywalking-ui:horizon-1.0.0
environment:
HORIZON_OAP_QUERY_URL: http://skywalking-oap:12800
HORIZON_OAP_ADMIN_URL: http://skywalking-oap:17128
HORIZON_AUTH_LOCAL_USERS: '[{"username":"admin","passwordHash":"$$argon2id$$v=19$$m=65536,t=3,p=4$$eemqy1r72oSXR58y8VpRqw$$Bn/dULrmJTHEi3263KfgWDEwQmUsqNLi3xwyv/DekHM","roles":["admin"]}]'
ports:
- "127.0.0.1:8081:8081"
depends_on:
- oap
networks:
- skywalking
networks:
skywalking:
name: gateway-skywalking-net
external: true
Start the services:
docker compose -f skywalking-compose.yaml up -d
Horizon is available at http://localhost:8081. Sign in with username admin and password admin.
The local user in this example uses public demonstration credentials. Use it only for a trusted local evaluation. Configure an identity provider or generate a unique password hash before exposing Horizon outside the local environment.
Create a namespace and a Secret containing the Horizon local-user configuration:
kubectl create namespace skywalking
kubectl create secret generic horizon-auth -n skywalking \
--from-literal='HORIZON_AUTH_LOCAL_USERS=[{"username":"admin","passwordHash":"$argon2id$v=19$m=65536,t=3,p=4$eemqy1r72oSXR58y8VpRqw$Bn/dULrmJTHEi3263KfgWDEwQmUsqNLi3xwyv/DekHM","roles":["admin"]}]'
Create a values file that pins compatible SkyWalking components and uses BanyanDB for storage:
fullnameOverride: skywalking
oap:
image:
tag: 11.0.0
storageType: banyandb
ui:
image:
tag: horizon-1.0.0
envFromSecret: horizon-auth
elasticsearch:
enabled: false
banyandb:
enabled: true
image:
tag: 0.11.0
Install the official SkyWalking Helm chart:
helm upgrade --install skywalking oci://docker.io/apache/skywalking-helm \
--version 5.0.0 \
--namespace skywalking \
-f skywalking-values.yaml
The OAP server is available to the gateway at skywalking-oap.skywalking.svc.cluster.local:12800. To access Horizon locally, forward its service port and open http://localhost:8081:
kubectl port-forward -n skywalking service/skywalking-ui 8081:80
Sign in with username admin and password admin. Replace the public demonstration credentials with a unique password hash before making Horizon accessible outside the local environment.
The Admin API and ADC examples below use http://skywalking-oap:12800, the address on the Docker network. For a host-installed gateway, use http://127.0.0.1:12800 instead. If the gateway reaches OAP through Kubernetes, use http://skywalking-oap.skywalking.svc.cluster.local:12800. The Ingress Controller examples already use the Kubernetes Service address.
Log Requests in Default Log Format
The following example sends request logs from one route to SkyWalking.
Create a route with the skywalking-logger plugin and configure the plugin with your OAP server URI:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "skywalking-logger-route",
"uri": "/anything",
"plugins": {
"skywalking-logger": {
"endpoint_addr": "http://skywalking-oap:12800"
}
},
"upstream": {
"nodes": {
"httpbin.org:80": 1
},
"type": "roundrobin"
}
}'
services:
- name: httpbin
labels:
docs-example: skywalking-logging
routes:
- uris:
- /anything
name: skywalking-logger-route
plugins:
skywalking-logger:
endpoint_addr: "http://skywalking-oap:12800"
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
Preview the changes to services with the example label:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=skywalking-logging
Synchronize the reviewed changes:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=skywalking-logging
- Gateway API
- APISIX CRD
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: skywalking-logger-plugin-config
spec:
plugins:
- name: skywalking-logger
config:
endpoint_addr: "http://skywalking-oap.skywalking.svc.cluster.local:12800"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: skywalking-logger-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /anything
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: skywalking-logger-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: skywalking-logger-route
spec:
ingressClassName: apisix
http:
- name: skywalking-logger-route
match:
paths:
- /anything
upstreams:
- name: httpbin-external-domain
plugins:
- name: skywalking-logger
enable: true
config:
endpoint_addr: "http://skywalking-oap.skywalking.svc.cluster.local:12800"
Apply the configuration to your cluster:
kubectl apply -f skywalking-logger-ic.yaml
Send a request to the route:
curl -i "http://127.0.0.1:9080/anything"
You should receive an HTTP/1.1 200 OK response.
In Horizon, navigate to General Service → Logs, select the APISIX service, and run a query. You should see a log entry corresponding to the request. Addresses, timing values, and version details vary by environment:
{
"upstream_latency": 3407,
"request": {
"method": "GET",
"headers": {
"user-agent": "curl/8.7.1",
"host": "127.0.0.1:9080",
"accept": "*/*"
},
"url": "http://127.0.0.1:9080/anything",
"size": 107,
"querystring": {},
"uri": "/anything"
},
"client_ip": "192.168.155.1",
"route_id": "skywalking-logger-route",
"start_time": 1789693705560,
"upstream": "34.195.250.0:80",
"server": {
"version": "3.18.0",
"hostname": "dd2886d0b7bf"
},
"service_id": "",
"response": {
"size": 881,
"status": 200,
"headers": {
"content-type": "application/json",
"date": "Fri, 18 Sep 2026 01:08:29 GMT",
"server": "APISIX/3.18.0",
"access-control-allow-origin": "*",
"connection": "close",
"access-control-allow-credentials": "true",
"content-length": "653"
}
},
"latency": 6791.0001277924,
"apisix_latency": 3384.0001277924
}
Log Request and Response Headers With Plugin Metadata
The following example uses plugin metadata to add selected request and response headers to every skywalking-logger instance. The metadata values reference built-in variables, so one configuration applies the same log fields across multiple routes and services.
First, create a route with the skywalking-logger plugin and configure the plugin with your OAP server URI (same as Log Requests in Default Log Format).
Next, configure the plugin metadata for skywalking-logger:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/plugin_metadata/skywalking-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"
}
}'
Plugin metadata is a global collection and cannot be isolated with a label selector. Export the complete collection before changing this entry:
adc dump -o adc.yaml --with-id \
--include-resource-type plugin_metadata
Add or update the skywalking-logger entry while preserving every other entry in adc.yaml:
plugin_metadata:
# Keep all other plugin metadata entries from the exported file.
skywalking-logger:
log_format:
host: "$host"
"@timestamp": "$time_iso8601"
client_ip: "$remote_addr"
env: "$http_env"
resp_content_type: "$sent_http_Content_Type"
Preview the complete metadata change and confirm that it contains no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type plugin_metadata
Synchronize the reviewed plugin metadata collection:
adc sync -f adc.yaml \
--include-resource-type plugin_metadata
Add the following entry under spec.pluginMetadata in the complete GatewayProxy manifest used by the deployment:
skywalking-logger:
log_format:
host: "$host"
"@timestamp": "$time_iso8601"
client_ip: "$remote_addr"
env: "$http_env"
resp_content_type: "$sent_http_Content_Type"
Apply the updated complete manifest through the deployment's normal Kubernetes or GitOps workflow.
❶ Log the custom request header env.
❷ Log the response header Content-Type.
Send a request to the route with the env header:
curl -i "http://127.0.0.1:9080/anything" -H "env: dev"
You should receive an HTTP/1.1 200 OK response. In Horizon, navigate to General Service → Logs, select the APISIX service, and run a query. The log entry should contain the configured fields:
[
{
"route_id": "skywalking-logger-route",
"client_ip": "192.168.65.1",
"@timestamp": "2026-09-18T01:08:29+00:00",
"host": "127.0.0.1",
"env": "dev",
"resp_content_type": "application/json"
}
]
Log Request Bodies Conditionally
The following example logs the request body only when the request matches the configured condition.
Create a route with the skywalking-logger plugin:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "skywalking-logger-route",
"uri": "/anything",
"plugins": {
"skywalking-logger": {
"endpoint_addr": "http://skywalking-oap:12800",
"include_req_body": true,
"include_req_body_expr": [["arg_log_body", "==", "yes"]]
}
},
"upstream": {
"nodes": {
"httpbin.org:80": 1
},
"type": "roundrobin"
}
}'
services:
- name: httpbin
labels:
docs-example: skywalking-logging
routes:
- uris:
- /anything
name: skywalking-logger-route
plugins:
skywalking-logger:
endpoint_addr: "http://skywalking-oap:12800"
include_req_body: true
include_req_body_expr:
- ["arg_log_body", "==", "yes"]
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
Preview the changes to services with the example label:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=skywalking-logging
Synchronize the reviewed changes:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=skywalking-logging
- Gateway API
- APISIX CRD
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: skywalking-logger-body-config
spec:
plugins:
- name: skywalking-logger
config:
endpoint_addr: "http://skywalking-oap.skywalking.svc.cluster.local:12800"
include_req_body: true
include_req_body_expr:
- ["arg_log_body", "==", "yes"]
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: skywalking-logger-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /anything
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: skywalking-logger-body-config
backendRefs:
- name: httpbin-external-domain
port: 80
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: skywalking-logger-route
spec:
ingressClassName: apisix
http:
- name: skywalking-logger-route
match:
paths:
- /anything
upstreams:
- name: httpbin-external-domain
plugins:
- name: skywalking-logger
enable: true
config:
endpoint_addr: "http://skywalking-oap.skywalking.svc.cluster.local:12800"
include_req_body: true
include_req_body_expr:
- ["arg_log_body", "==", "yes"]
Apply the configuration to your cluster:
kubectl apply -f skywalking-logger-body-ic.yaml
❶ include_req_body: Set to true to include request body.
❷ include_req_body_expr: Only include request body if the URL query string log_body is yes.
Send a request to the route with a URL query string satisfying the condition:
curl -i "http://127.0.0.1:9080/anything?log_body=yes" -X POST -d '{"env": "dev"}'
You should receive an HTTP/1.1 200 OK response. In Horizon, navigate to General Service → Logs, select the APISIX service, and run a query. The log entry should include the request body:
[
{
"request": {
"url": "http://127.0.0.1:9080/anything?log_body=yes",
"querystring": {
"log_body": "yes"
},
"uri": "/anything?log_body=yes",
...,
"body": "{\"env\": \"dev\"}",
},
...
}
]
Send a request to the route without any URL query string:
curl -i "http://127.0.0.1:9080/anything" -X POST -d '{"env": "dev"}'
You should not observe a log entry without the request body.
Custom log formats do not add collected request or response bodies automatically. Include the corresponding variables in the format:
{
"include_req_body": true,
"include_resp_body": true,
"log_format": {
"request_body": "$request_body",
"response_body": "$resp_body"
}
}
Body size limits still apply. Use log_format_extra to add custom fields without replacing the default log entry.
Associate Traces with Logs
The following example enables tracing and request logging on one route so Horizon can link each log entry to its trace.
This example also requires the skywalking plugin to be enabled globally and configured with a reachable OAP endpoint address. For Helm deployments, see the SkyWalking plugin setup.
Create a route with the skywalking-logger plugin and configure the plugin with your OAP server URI:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "skywalking-logger-route",
"uri": "/anything",
"plugins": {
"skywalking": {
"sample_ratio": 1
},
"skywalking-logger": {
"endpoint_addr": "http://skywalking-oap:12800"
}
},
"upstream": {
"nodes": {
"httpbin.org:80": 1
},
"type": "roundrobin"
}
}'
services:
- name: httpbin
labels:
docs-example: skywalking-logging
routes:
- uris:
- /anything
name: skywalking-logger-route
plugins:
skywalking:
sample_ratio: 1
skywalking-logger:
endpoint_addr: "http://skywalking-oap:12800"
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
Preview the changes to services with the example label:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=skywalking-logging
Synchronize the reviewed changes:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=skywalking-logging
- Gateway API
- APISIX CRD
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: skywalking-logger-trace-config
spec:
plugins:
- name: skywalking
config:
sample_ratio: 1
- name: skywalking-logger
config:
endpoint_addr: "http://skywalking-oap.skywalking.svc.cluster.local:12800"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: skywalking-logger-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /anything
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: skywalking-logger-trace-config
backendRefs:
- name: httpbin-external-domain
port: 80
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: skywalking-logger-route
spec:
ingressClassName: apisix
http:
- name: skywalking-logger-route
match:
paths:
- /anything
upstreams:
- name: httpbin-external-domain
plugins:
- name: skywalking
enable: true
config:
sample_ratio: 1
- name: skywalking-logger
enable: true
config:
endpoint_addr: "http://skywalking-oap.skywalking.svc.cluster.local:12800"
Apply the configuration to your cluster:
kubectl apply -f skywalking-logger-trace-ic.yaml
Generate a few requests to the route:
curl -i "http://127.0.0.1:9080/anything"
You should receive HTTP/1.1 200 OK responses.
In Horizon, navigate to General Service → Logs and run a query. The correlated request log includes a trace link that opens its trace:
