Custom traffic log format¶
Overview¶
By default, Traffic Logging writes every request in a fixed JSON shape. A traffic log template writes each line in your own JSON layout instead.
With a template, you can:
- Choose which fields each line has, what they are called and in what order they appear.
- Group fields into nested objects that match your log platform's schema.
- Add fixed values, such as a service or environment name.
- Convert latencies to the time unit you need.
You set the template inline in the gateway configuration.
Configuration¶
The template is set in the [traffic_logging] section, which must have enabled = true.
| Parameter | Type | Default | Description |
|---|---|---|---|
template |
string | "" |
The template, inline in the gateway configuration. |
When template is empty, the gateway writes the default line.
Template format¶
A template is a JSON object that describes one log line.
- A value that starts with
$ctx:is filled in from the request. The text after$ctx:is an expression that reads the available variables. - Any other value is written exactly as given. It can be a string, number,
true,false,null, an object or an array.
For example, this template:
{
"time": "$ctx:request.timestamp",
"service": "orders",
"api": {"name": "$ctx:api.name", "version": "$ctx:api.version"},
"http": {
"method": "$ctx:request.method",
"path": "$ctx:request.path",
"status": "$ctx:response.status"
},
"client": "$ctx:client.ip",
"app": "$ctx:application.name",
"durationMs": "$ctx:convertUnit(latencies.duration, 'ms')"
}
writes lines such as:
{"time":"2026-11-10T09:12:33.482Z","service":"orders","api":{"name":"Weather-API","version":"1.0.0"},"http":{"method":"GET","path":"/current","status":200},"client":"203.0.113.10","app":null,"durationMs":18.342}
Each line has the fields in the template's order. Numbers stay numbers. A field the request has no value for is written as null. In this example, app is null because no application was identified for the request.
Passing the template¶
The gateway reads the template when it starts. After you change the template, restart the gateway.
Set template to the template. A TOML multi-line string (''') keeps it readable.
[traffic_logging]
enabled = true
template = '''
{
"service": "orders",
"http": {"method": "$ctx:request.method", "status": "$ctx:response.status"},
"durationMs": "$ctx:convertUnit(latencies.duration, 'ms')"
}
'''
Helm: Set gateway.config.traffic_logging.template as a YAML string. Use a | block, not a YAML map, so that the field order is kept.
gateway:
config:
traffic_logging:
enabled: true
template: |
{
"service": "orders",
"http": {"method": "$ctx:request.method", "status": "$ctx:response.status"},
"durationMs": "$ctx:convertUnit(latencies.duration, 'ms')"
}
Template lines go to the same outputs (stdout, file or http) as default lines.
How template lines behave¶
- A template replaces the default line for every request.
masked_headers,max_payload_sizeandexclude_fieldsapply to template lines. Inexclude_fields, a name is a path in your template's output, for examplehttp.status.- The template decides which headers and bodies are logged. They have a value only when the matching
[collector]setting (request_headers,response_headers,request_bodyorresponse_body) is on.
Available variables¶
Use these variables in $ctx: values. A variable the request has no value for is written as null.
Request and response¶
These variables describe the request and the response sent to the client.
| Variable | Type | Description |
|---|---|---|
request.timestamp |
string | When the gateway received the request, in UTC. For example, 2026-11-10T09:12:33.482Z. |
request.id |
string | The request's correlation ID. |
request.method |
string | HTTP method. |
request.path |
string | Request path. |
request.pathTemplate |
string | The API resource path that matched the request, for example /orders/{id}. |
request.header |
map | Request headers, with lower-case names. Read one with request.header['<name>']. Requires collector.request_headers. |
request.body |
string | Request body, cut to max_payload_size. Requires collector.request_body. |
response.status |
number | HTTP status code sent to the client. |
response.header |
map | Response headers, with lower-case names. Read one with response.header['<name>']. Requires collector.response_headers. |
response.body |
string | Response body, cut to max_payload_size. Requires collector.response_body. |
Header values in masked_headers are written as ****.
API and backend¶
These variables describe the API that handled the request and the backend it called.
| Variable | Type | Description |
|---|---|---|
api.id |
string | API ID. |
api.name |
string | API name. |
api.version |
string | API version. |
api.context |
string | API context. |
api.kind |
string | API kind. |
project.id |
string | Project ID. |
target.statusCode |
number | Status code returned by the backend. |
target.destination |
string | Backend URL the request was sent to. |
Client and application¶
These variables describe the caller.
| Variable | Type | Description |
|---|---|---|
client.ip |
string | Client IP address. |
client.userAgent |
string | Client user agent. |
application.id |
string | ID of the application identified for the request. |
application.name |
string | Application name. |
application.owner |
string | Application owner. |
application.keyType |
string | Application key type. |
Authentication¶
These variables describe the authenticated caller and the token.
| Variable | Type | Description |
|---|---|---|
auth.authenticated |
true/false | Whether the request was authenticated. |
auth.authorized |
true/false | Whether the request was authorized. |
auth.subject |
string | Authenticated subject. |
auth.type |
string | Authentication type. |
auth.issuer |
string | Token issuer. |
auth.credential_id |
string | Credential ID. |
auth.token_id |
string | Token ID. |
auth.audience |
list | Token audiences. |
auth.scopes |
list | Granted scopes. |
auth.property |
map | Token claims. Read one with auth.property['<claim>']. |
Note
Claims are logged as they appear in the token. To log only the claims you need, read them one by one, for example auth.property['tenant']. To leave out a specific claim, use exclude_fields.
Errors¶
These variables have a value only when the request failed.
| Variable | Type | Description |
|---|---|---|
error.category |
string | Error category, for example AUTH. |
error.errorCode |
number | Error code. |
error.errorMessage |
string | Error classification, for example AUTHENTICATION_FAILURE. |
error.wso2ErrorCode |
number | WSO2 fault code. |
error.type |
string | Type of failure, for example authentication or guardrail. |
error.direction |
string | Whether the request or the response was rejected. |
error.summary |
string | Message sent to the client. |
error.policy |
string | Policy that caused the error. |
error.policyPhase |
string | Phase in which that policy ran. |
error.source |
string | What produced the response, for example gateway or backend. |
error.originalStatus |
number | Backend status code before a policy changed it. |
error.jsonRpcCode |
number | JSON-RPC error code, for MCP requests. |
error.guardrail.name |
string | Guardrail that intervened. |
error.guardrail.action |
string | Action the guardrail took. |
error.guardrail.reason |
string | Reason the guardrail gave. |
Latencies¶
Latencies are in microseconds.
| Variable | Type | Description |
|---|---|---|
latencies.duration |
number | Total time to handle the request. |
latencies.backendLatency |
number | Time spent waiting for the backend. |
latencies.requestMediationLatency |
number | Time spent processing the request in the gateway. |
latencies.responseMediationLatency |
number | Time spent processing the response in the gateway. |
To write a latency in another unit, use convertUnit(<latency>, '<unit>'). The unit is one of 'ns', 'us', 'ms' or 's'.
Metadata¶
This variable holds values that policies share for the request.
| Variable | Type | Description |
|---|---|---|
metadata |
map | Values that policies share for the request. Read one with metadata['<key>']. |
Note
metadata values are logged as the policies set them. Reference only keys whose values are safe to log.
Expression examples¶
The following expressions cover common needs.
| To write | Use |
|---|---|
| One header | "$ctx:request.header['x-request-id']" |
| One claim | "$ctx:auth.property['tenant']" |
| All claims, as an object | "$ctx:auth.property" |
| A default value when there is none | "$ctx:auth.authenticated ? auth.subject : 'anonymous'" |
| Text combined with a value | "$ctx:'v' + api.version" |
Troubleshooting¶
If the log lines aren't what you expect, check the following.
| Symptom | What to do |
|---|---|
The gateway does not start, and the error names template. |
Fix the problem the error describes, such as invalid JSON. Then restart the gateway. |
A field is always null, and a warning at startup names the field. |
Fix the $ctx: expression the warning describes, for example a misspelled variable. |
A header or body field is always null. |
Turn on the matching [collector] setting. |
A warning at startup says an exclude_fields entry matches no field. |
Change the entry to a path in your template's output. |
| Lines still look like the default line. | Check that traffic_logging.enabled is true and that template is spelled correctly. |