You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
formatter: add REQ_ALL_HEADERS / RESP_ALL_HEADERS operators to serialize all HTTP headers
Description
Problem
Envoy's access log formatting currently requires each HTTP header to be individually named via %REQ(header-name)% / %RESP(header-name)%. This means operators must know in advance which headers are relevant and update their access log configuration every time a new header becomes interesting.
This creates two real-world pain points:
Security observability gaps. Unexpected or malicious headers (e.g. spring.cloud.function.routing-expression exploited during Log4Shell, or novel exfiltration headers) are silently dropped from logs because they were never enumerated in configuration. Threat detection pipelines cannot flag what they cannot see.
Operational overhead. Adding a single header to access logs requires a config change, validation, and a rolling redeploy across the fleet. For large deployments this is a meaningful operational cost just to start logging a header that was already on the wire.
Proposed solution
Add two new access log command operators:
%REQ_ALL_HEADERS% -- serializes all HTTP request headers as a JSON object
%RESP_ALL_HEADERS% -- same for response headers
When used with json_format, the output is a nested JSON object (not a flat string), allowing downstream log consumers (SIEM, Elasticsearch, etc.) to query individual headers without additional deserialization.
Design choices
Decision
Rationale
Ship as an extension formatter (envoy.formatter.all_headers), not a core operator
Opt-in, requires explicit configuration. Directly addresses security concern about accidentally logging sensitive data. Follows the req_without_query precedent.
exclude_headers config field (repeated string, case-insensitive)
Operators control what is redacted. Documentation recommends excluding authorization, cookie, set-cookie.
max_value_bytes config field (uint32, 0 = unlimited)
Bounds per-value log size, preventing multi-MB log lines from large header values.
Comma-join for repeated headers
RFC 7230 compliant. Simpler than emitting arrays. set-cookie caveat documented.
Include pseudo-headers (:method, :path, :authority, :status)
They are naturally present in the HeaderMap and useful for observability.
Return Protobuf::Struct from formatValue()
Produces a nested JSON object in json_format logs, same pattern as DYNAMIC_METADATA.
HeaderMap::iterate() is O(n headers) and involves string copies into a protobuf struct. This is non-trivial on high-throughput paths. Recommended mitigations:
Pair this operator with an access log filter (e.g. sample-based, or only when %RESPONSE_FLAGS% != -) rather than using it on every request.
Use max_value_bytes to bound per-value size.
The extension-formatter approach makes this fully opt-in.
Title
formatter: add
REQ_ALL_HEADERS/RESP_ALL_HEADERSoperators to serialize all HTTP headersDescription
Problem
Envoy's access log formatting currently requires each HTTP header to be individually named via
%REQ(header-name)%/%RESP(header-name)%. This means operators must know in advance which headers are relevant and update their access log configuration every time a new header becomes interesting.This creates two real-world pain points:
Security observability gaps. Unexpected or malicious headers (e.g.
spring.cloud.function.routing-expressionexploited during Log4Shell, or novel exfiltration headers) are silently dropped from logs because they were never enumerated in configuration. Threat detection pipelines cannot flag what they cannot see.Operational overhead. Adding a single header to access logs requires a config change, validation, and a rolling redeploy across the fleet. For large deployments this is a meaningful operational cost just to start logging a header that was already on the wire.
Proposed solution
Add two new access log command operators:
%REQ_ALL_HEADERS%-- serializes all HTTP request headers as a JSON object%RESP_ALL_HEADERS%-- same for response headersWhen used with
json_format, the output is a nested JSON object (not a flat string), allowing downstream log consumers (SIEM, Elasticsearch, etc.) to query individual headers without additional deserialization.Design choices
envoy.formatter.all_headers), not a core operatorreq_without_queryprecedent.exclude_headersconfig field (repeated string, case-insensitive)authorization,cookie,set-cookie.max_value_bytesconfig field (uint32, 0 = unlimited)set-cookiecaveat documented.:method,:path,:authority,:status)HeaderMapand useful for observability.Protobuf::StructfromformatValue()json_formatlogs, same pattern asDYNAMIC_METADATA.Example configuration
Example output (JSON format)
{ "timestamp": "2026-05-25T12:00:00.000Z", "status": 200, "request_headers": { ":method": "GET", ":path": "/api/v1/resource", ":authority": "example.com", "user-agent": "curl/8.0", "x-request-id": "abc-123", "accept": "text/html,application/json" }, "response_headers": { ":status": "200", "content-type": "application/json", "x-envoy-upstream-service-time": "12" } }Related issues
Directly related:
help wanted). Tracks multi-value header support; comment from @ramaraochavali proposesREQUEST_HEADER(*)notation. There is aTODOinreq_without_query.cc:62referencing this.Header redaction / obfuscation (key maintainer concern):
authorization,cookiefrom debug logs.Performance considerations
HeaderMap::iterate()is O(n headers) and involves string copies into a protobuf struct. This is non-trivial on high-throughput paths. Recommended mitigations:%RESPONSE_FLAGS%!=-) rather than using it on every request.max_value_bytesto bound per-value size.