Envoy Proxy Access Log Grok Regex: Vector VRL Transform Cheatsheet & OpenTelemetry Exporter
Envoy’s default 14-field access log format is parsed using the Grok expression: \[%{TIMESTAMP_ISO8601:timestamp}\] "%{WORD:method} %{NOTSPACE:path} %{NOTSPACE:protocol}" %{NUMBER:status:int} %{NOTSPACE:flags} %{NUMBER:bytes_rx:int} %{NUMBER:bytes_tx:int} %{NUMBER:duration_ms:int}. For gigabit throughput, execute parsing inside Vector Remap Language (VRL) using native regex matches before exporting via gRPC to the OpenTelemetry OTLP Collector.
1. The Architecture of Envoy Access Logs: Standard Field Matrix
In modern cloud-native architectures (including Istio, Envoy Gateway, and AWS App Mesh), the data plane proxies millions of RPC and HTTP requests per second. By default, Envoy writes formatted text lines to standard output using a strictly ordered template.
Parsing these logs reliably requires understanding every token in the default format string:
| Field Index | Envoy Variable | Grok Semantic Matcher | VRL / Rust Target Type | Description |
|---|---|---|---|---|
| 1 | %START_TIME% | %{TIMESTAMP_ISO8601:timestamp} | timestamp | Request start time (UTC millisecond precision) |
| 2 | %REQ(:METHOD)% | %{WORD:http_method} | string | HTTP request verb (GET, POST, PUT, DELETE, etc.) |
| 3 | %REQ(PATH)% | %{NOTSPACE:request_path} | string | Target URI path including query parameters |
| 4 | %PROTOCOL% | %{NOTSPACE:protocol} | string | HTTP protocol version (HTTP/1.1, HTTP/2) |
| 5 | %RESPONSE_CODE% | %{NUMBER:response_code:int} | integer | Downstream HTTP status code sent by Envoy |
| 6 | %RESPONSE_FLAGS% | %{NOTSPACE:response_flags} | string | Failure or processing flag (e.g. UH, UF, UO, DC) |
| 7 | %DURATION% | %{NUMBER:duration_ms:int} | integer | Total round-trip time in milliseconds |
| 8 | %UPSTREAM_HOST% | %{NOTSPACE:upstream_host} | string | IP address and port of selected upstream backend |
2. High-Performance Vector VRL Transform Pipeline
At enterprise scale (100k+ RPS per cluster), parsing logs with Logstash or Python is cost-prohibitive due to memory bloat and regex backtracking. Vector (built by Datadog in Rust) provides Vector Remap Language (VRL), which executes structured parsing in sub-microsecond latency.
Here is the complete, production-hardened VRL configuration:
# vector.yaml - Envoy Access Log Ingestion Pipeline
sources:
envoy_logs:
type: kubernetes_logs
include_paths:
- "/var/log/pods/*_envoy*/*.log"
transforms:
parse_envoy:
type: remap
inputs:
- envoy_logs
source: |
# Fast-path regex matching for default Envoy format
parsed, err = parse_regex(.message, r'^\[(?P[^\]]+)\] "(?P\S+) (?P\S+) (?P\S+)" (?P\d+) (?P\S+) (?P\d+) (?P\d+) (?P\d+) (?P\S+) "(?P[^"]*)" "(?P[^"]*)" "(?P[^"]*)" "(?P[^"]*)" "(?P[^"]*)"$')
if err != null {
.parse_error = err
.raw_payload = .message
} else {
# Cast numeric fields
.timestamp = parse_timestamp(parsed.timestamp, "%Y-%m-%dT%H:%M:%S%.fZ") ?? now()
.http_method = parsed.method
.http_path = parsed.path
.http_protocol = parsed.protocol
.http_status = to_int!(parsed.status)
.response_flags = parsed.flags
.bytes_received = to_int!(parsed.bytes_rx)
.bytes_sent = to_int!(parsed.bytes_tx)
.duration_ms = to_int!(parsed.duration)
.upstream_duration_ms = to_int(parsed.upstream_duration) ?? null
.client_ip = parsed.x_forwarded_for
.user_agent = parsed.user_agent
.request_id = parsed.request_id
.authority = parsed.authority
.upstream_host = parsed.upstream_host
# Enrich with decoded flag semantics
.flag_description = match .response_flags {
"UH" => "No Healthy Upstream Host",
"UF" => "Upstream Connection Failure",
"UO" => "Upstream Overflow / Circuit Broken",
"UT" => "Upstream Request Timeout",
"DC" => "Downstream Connection Terminated",
"NR" => "No Route Configured",
"LH" => "Local Service Failed Health Check",
"-" => "Normal Execution",
_ => "Other Failure"
}
# Remove raw string to save egress bandwidth
del(.message)
}
sinks:
otel_exporter:
type: opentelemetry
inputs:
- parse_envoy
endpoint: "otel-collector.monitoring.svc.cluster.local:4317"
protocol: grpc
3. Comprehensive Envoy Response Flag Decoder Matrix
The %RESPONSE_FLAGS% field is Envoy’s most valuable troubleshooting asset. It identifies why a request failed without requiring packet captures or code tracing:
| Flag Code | Semantic Meaning | Typical HTTP Code | Root Cause Diagnostics |
|---|---|---|---|
| UH | No Healthy Host | 503 Service Unavailable | All pods in target service failed readiness probes. |
| UF | Upstream Connection Failure | 503 Service Unavailable | TCP reset (RST), connection refused, or TLS handshake failure. |
| UO | Upstream Overflow | 503 Service Unavailable | Envoy circuit breaker hit max pending requests / connections. |
| UT | Upstream Timeout | 504 Gateway Timeout | Backend exceeded route timeout before transmitting body. |
| DC | Downstream Connection Termination | 499 / 200 | Client browser or caller hung up before response completed. |
| NR | No Route | 404 Not Found | No matching virtual host or route prefix in Envoy RDS. |
4. OpenTelemetry (OTel) Semantic Convention Mapping
When funneling parsed Envoy logs into Grafana Loki, ClickHouse, or Datadog via the OpenTelemetry Collector, engineering teams should strictly conform to OpenTelemetry Semantic Conventions for HTTP Logs:
http.request.method→ Maps to parsedmethod(e.g. "POST").url.path→ Maps to parsedpath(e.g. "/checkout").http.response.status_code→ Maps to integerstatus(e.g. 503).network.protocol.name→ Extracted fromprotocol(e.g. "http").server.address→ Maps to parsedauthority(e.g. "api.internal.net").client.address→ First element ofx_forwarded_for.
5. Standalone Python Test Harness: Regex Performance & Parsing Guardrails
Execute this diagnostic Python validation harness to verify your Grok expressions against boundary cases (IPv6 addresses, absent upstream headers, truncated paths):
import re
from typing import Dict, Any, Optional
ENVOY_PATTERN = re.compile(
r'^\[(?P[^\]]+)\] '
r'"(?P\S+) (?P\S+) (?P\S+)" '
r'(?P\d{3}) (?P\S+) (?P\d+) (?P\d+) '
r'(?P\d+) (?P\S+) '
r'"(?P[^"]*)" "(?P[^"]*)" '
r'"(?P[^"]*)" "(?P[^"]*)" "(?P[^"]*)"$'
)
def parse_envoy_line(line: str) -> Optional[Dict[str, Any]]:
match = ENVOY_PATTERN.match(line.strip())
if not match:
return None
data = match.groupdict()
return {
"timestamp": data["timestamp"],
"method": data["method"],
"path": data["path"],
"protocol": data["protocol"],
"status": int(data["status"]),
"flags": data["flags"],
"duration_ms": int(data["duration"]),
"upstream_host": data["upstream_host"]
}
if __name__ == "__main__":
test_line = (
'[2026-10-05T08:14:22.412Z] "POST /api/v2/checkout HTTP/1.1" '
'503 UH 1420 284 18 12 "203.0.113.19" "Mozilla/5.0" '
'"a8f9c2d1" "api.internal.net" "10.244.3.18:8080"'
)
result = parse_envoy_line(test_line)
assert result is not None, "Failed to parse standard Envoy log line"
print("Parsed Record:", result)
6. Top 4 Production Log Ingestion Failures & Incident Runbook
- 1. Catastrophic Regex Backtracking on Truncated Logs: Naive regex using unanchored wildcard capture loops (
.*) causes CPU spikes when parsing broken log lines. Mitigation: Use bounded character classes (\S+or[^"]*) and anchor patterns strictly with^and$. - 2. High Cardinality URL Paths Exploding Storage: Directly indexing URL paths containing raw UUIDs (
/users/550e8400-e29b-41d4-a716-446655440000) destroys database indexing performance. Mitigation: Mask GUIDs and numeric IDs in VRL (e.g.replace(.http_path, r'/[0-9a-fA-F-]{36}', '/:id')). - 3. Silent Upstream Duration Cast Failures: When a request fails before connecting upstream, Envoy outputs
-for%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%. Attempting to parse-as an integer causes pipeline exceptions. Mitigation: Use VRL fallbacks:to_int(parsed.upstream_duration) ?? null. - 4. Clock Skew and Nano Timestamp Dropping: Ingesting logs across globally distributed clusters with non-synchronized host clocks creates out-of-order errors in ClickHouse/Loki. Mitigation: Enforce Chrony/NTP sync on Kubernetes worker nodes with < 5ms tolerance.
Frequently Asked Questions
Can Envoy output JSON directly instead of formatted text?
Yes. By configuring access_log.typed_config.json_format inside Envoy's bootstrap or Ingress YAML, Envoy can serialize logs as native JSON. However, structured text remains standard in many legacy and high-throughput environments due to JSON's 25-40% byte overhead over the wire.
How do I capture gRPC status codes in Envoy access logs?
You must add %RESP(GRPC-STATUS)% or %GRPC_STATUS% to your Envoy access log format string. This will output standard gRPC numerical codes (0 = OK, 1 = CANCELLED, 2 = UNKNOWN, 14 = UNAVAILABLE, etc.).
What is the performance difference between Fluentbit and Vector for Envoy logs?
In high-concurrency benchmarks, Vector routinely processes 150,000+ logs/sec per core compared to Fluentbit's ~90,000 logs/sec. Vector's VRL provides richer type-checking and native in-flight schema mutation with lower CPU variability under heavy load.