Logging

JSONL access log format, every field explained, and jq recipes for analysis.

Logging

The gateway produces structured JSONL (JSON Lines) access logs — one JSON object per request.

Configuration

{
  "plugins": {
    "logging": {
      "enabled": true,
      "mode": "file",
      "file": {
        "path": "/var/log/quayel/access.jsonl",
        "buffer_size": 8192,
        "flush_interval_ms": 1000
      }
    }
  }
}
FieldTypeDefaultDescription
enabledbooltrueEnable/disable logging
modestring"file"Log mode
file.pathstring—Path to JSONL log file
file.buffer_sizeint8192Write buffer size
file.flush_interval_msint1000Flush interval

Log format

Each line is a complete JSON object:

{
  "timestamp": "2026-01-15T10:30:45.123456789+00:00",
  "gateway_id": "gw-prod-01",
  "gateway_name": "production",
  "server_name": "api.example.com",
  "config_version": "2026-01-15-001",
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "client_ip": "203.0.113.50",
  "peer_ip": "203.0.113.50",
  "client_ip_source": "X-Forwarded-For",
  "method": "GET",
  "scheme": "https",
  "host": "api.example.com",
  "path": "/api/v1/users",
  "response_status": 200,
  "bandwidth": { },
  "ip_intel": { },
  "firewall": { },
  "rate_limit": { },
  "cache": { },
  "load_balancer": { },
  "termination_phase": null,
  "timings_us": { }
}

Fields reference

Identity

FieldTypeDescription
timestampstringISO 8601 timestamp with nanoseconds
gateway_idstringGateway instance ID
gateway_namestringGateway name
server_namestringServer name
config_versionstringConfig version string
request_idstringUUID v4 unique request ID

Client

FieldTypeDescription
client_ipstringResolved real client IP
peer_ipstringDirect TCP connection IP
client_ip_sourcestringHow client_ip was determined

HTTP

FieldTypeDescription
methodstringHTTP method
schemestringRequest scheme (http/https)
hoststringRequest host
pathstringRequest path (including query string)

Response

FieldTypeDescription
response_statusnumberHTTP response status code

Bandwidth

{
  "bandwidth": {
    "client_bytes_in": 1234,
    "client_bytes_out": 5678,
    "total_client_bytes": 6912,
    "upstream_bytes_out": 1234,
    "upstream_bytes_in": 5678,
    "total_origin_bytes": 6912
  }
}
FieldDescription
client_bytes_inBytes received from client
client_bytes_outBytes sent to client
total_client_bytesclient_bytes_in + client_bytes_out
upstream_bytes_outBytes sent to upstream
upstream_bytes_inBytes received from upstream
total_origin_bytesupstream_bytes_out + upstream_bytes_in

IP intelligence

{
  "ip_intel": {
    "country": "US",
    "region": "CA",
    "asn": 16509,
    "provider": "Amazon AWS",
    "network_type": "datacenter",
    "is_vpn": false,
    "is_proxy": false,
    "is_datacenter": true,
    "is_mobile": null,
    "is_tor": false,
    "risk_score": 10
  }
}

See IP Intelligence for field details.

Firewall

{
  "firewall": {
    "matched": true,
    "rule_id": "block-tor",
    "action": "Block"
  }
}
FieldDescription
matchedWhether any firewall rule matched
rule_idID of the matched rule
actionAction taken (Allow, Block, Redirect, etc.)

Rate limiting

{
  "rate_limit": {
    "matched": true,
    "rule_id": "api-limit",
    "blocked": false,
    "remaining": 87
  }
}
FieldDescription
matchedWhether any rate limit rule matched
rule_idID of the matched rule
blockedWhether the request was blocked
remainingRemaining requests in current window

Cache

{
  "cache": {
    "rule_id": "cache-static",
    "status": "HIT",
    "key_hash": "a1b2c3d4..."
  }
}
FieldDescription
rule_idID of the matched cache rule
statusCache status (see below)
key_hashHash of the cache key

Cache status values

StatusDescription
NONENo cache rule matched
HITFresh cache hit — served from cache
MISSCache miss — fetched from upstream
EXPIRED(Reserved for future use)
BYPASSCache bypassed by rule
DYNAMIC(Reserved for future use)
STALEServed stale data (lock race fallback)
REVALIDATINGServed stale data while another request refreshes cache

Load balancer

{
  "load_balancer": {
    "lb_id": "default",
    "algorithm": "RoundRobin",
    "target_id": "backend-1",
    "target": "10.0.0.1"
  }
}
FieldDescription
lb_idLoad balancer pool ID
algorithmAlgorithm used
target_idSelected target ID
targetSelected target host

Termination phase

{
  "termination_phase": "Firewall"
}
ValueDescription
nullRequest completed normally
IpIntelTerminated by IP Intel plugin
FirewallTerminated by Firewall (blocked/redirected)
RateLimitTerminated by Rate Limiting (429)
CacheTerminated by Cache (HIT served)
LoadBalancerTerminated by Load Balancer (no upstream)

Timings

{
  "timings_us": {
    "ip_intel_us": 42,
    "firewall_us": 3,
    "rate_limit_us": 0,
    "cache_lookup_us": null,
    "load_balancer_us": 7,
    "upstream_connect_us": null,
    "upstream_ttfb_us": null,
    "total_us": 1091
  }
}

All timings are in microseconds (μs).

FieldDescription
ip_intel_usTime spent in IP intelligence lookup
firewall_usTime spent evaluating firewall rules
rate_limit_usTime spent in rate limit checking
cache_lookup_usTime spent in cache lookup
load_balancer_usTime spent selecting upstream target
upstream_connect_usTime to connect to upstream
upstream_ttfb_usUpstream time to first byte
total_usTotal request processing time

Log analysis examples

Count requests by country

cat access.jsonl | jq -r '.ip_intel.country' | sort | uniq -c | sort -rn | head -20

Find blocked requests

cat access.jsonl | jq 'select(.firewall.matched == true)' | wc -l

Find rate-limited requests

cat access.jsonl | jq 'select(.rate_limit.blocked == true)'

Average response time

cat access.jsonl | jq '.timings_us.total_us' | awk '{sum+=$1; n++} END {print sum/n " μs"}'

Requests per second

cat access.jsonl | jq -r '.timestamp' | cut -c1-19 | uniq -c | sort -rn | head

Top IPs by request count

cat access.jsonl | jq -r '.client_ip' | sort | uniq -c | sort -rn | head -20

VPN/Tor traffic

cat access.jsonl | jq 'select(.ip_intel.is_vpn == true or .ip_intel.is_tor == true)'

High-risk requests

cat access.jsonl | jq 'select(.ip_intel.risk_score > 30)'

Cache hit rate

total=$(cat access.jsonl | jq 'select(.cache.status != "NONE")' | wc -l)
hits=$(cat access.jsonl | jq 'select(.cache.status == "HIT")' | wc -l)
echo "Cache hit rate: $(echo "scale=2; $hits * 100 / $total" | bc)%"

Cache revalidation events

# Count concurrent stale serving events
cat access.jsonl | jq 'select(.cache.status == "REVALIDATING")' | wc -l

# Count stale fallbacks (lock race condition)
cat access.jsonl | jq 'select(.cache.status == "STALE")' | wc -l

Slow requests (>100ms)

cat access.jsonl | jq 'select(.timings_us.total_us > 100000)'

Log rotation

Use logrotate or similar:

/var/log/quayel/access.jsonl {
    daily
    rotate 30
    compress
    missingok
    notifempty
    copytruncate
}

Notes

  • Logs are written synchronously (one write() per line)
  • The log file is created automatically if it doesn't exist
  • Parent directories are created automatically
  • Logs are appended (no rotation built-in — use external tools)
  • Each gateway instance writes to its own log file
Copyright © 2026