How it works
- When a request arrives, the gateway fixes its record id and returns it in
X-ManyLayers-Trace-Id. - When the request finishes, the record is handed to a background writer, so logging adds no latency.
- The writer appends records in order. Each record’s SHA-256 hash covers its content and the previous record’s hash, so changing or deleting any stored record breaks the chain from that point on.
What each record holds
| Group | Fields |
|---|---|
| Identity | Organization, workspace, team, API key, user; client IP (the connection’s peer address, not X-Forwarded-For) and User-Agent |
| Request | Endpoint, requested model, served model and provider account, gateway config, request metadata from X-ManyLayers-Metadata |
| Outcome | HTTP status, latency, input and output tokens, retries, whether a provider was called at all |
| Decisions | Applied policies, guardrail verdicts, PII and firewall findings (type, offsets and score — never the matched text), the Auto Routing decision |
| Correlation | OpenTelemetry trace id and root span id |
| Bodies | Request and response bodies, only when captured (see below), always after PII redaction |
| Chain | Previous hash and this record’s hash |
Configuration
gateway.yaml when the gateway starts. The shipped container config reads log_bodies from MANYLAYERS_AUDIT_LOG_BODIES. A guardrail execution record is kept for the same retention_days.
Deciding whether bodies are stored
For each request, in order:- No logging config matches →
audit.log_bodiesdecides. - Logging configs match → the innermost matching rules decide. Bodies are stored only if every one of them has
log_requests: true, and every one’s redaction is applied to the stored copy. A matching rule can turn capture on even whenlog_bodiesisfalse. X-ManyLayers-Store-Logs: falseon the request → bodies are not stored, whatever the rules say.truechanges nothing; it can never turn capture back on.
Logging configs
A logging config is a policy that sets body capture and redaction for a scope: the organization, a workspace, or within it one team, service account, user or API key.Key fields
At most 128 characters.
workspace or organization. Name at most one of team_id, service_account_id, user_id, api_key_id to narrow it.false keeps bodies out of the log for this scope. Toggle it alone with PATCH /admin/gateway/policies/logging-configs/{id} and {"log_requests": false}.Applied to the stored copy only — the model still receives the request.
secrets runs first, then pii (narrowed by pii_categories, empty = all; categories as in the pii guardrail), then each patterns regex (every pattern needs a name and a pattern). Defaults: ***REDACTED*** for secrets, [REDACTED:{category}] for PII and [REDACTED] for patterns; a non-empty replacement replaces all three. A rule that cannot be compiled is refused with 400 when written.GET/POST /admin/gateway/policies/logging-configs, PUT/PATCH/DELETE .../{id}. Reading needs gateway.policies.read; writing needs gateway.policies.manage.
Per-request opt-out
true or false returns 400. The record itself (tokens, status, latency, findings) is still written.
Query the log
GET /admin/audit on the console host (https://app.manylayers.io) returns the newest records first. A workspace-bound key sees its own workspace; pass ?workspace_id= for another workspace you can reach. Callers who may read analytics across the whole Gateway product, such as organization administrators, can omit it for a cross-workspace view; everyone else must name one.
| Parameter | Meaning |
|---|---|
limit, offset | Paging; limit 1–10000, default 100. |
id | One record, by X-ManyLayers-Trace-Id. |
team_id, model, endpoint, config_id | Exact matches. |
status, min_status | One HTTP status, or that status and above (100–599). |
from, to | RFC 3339 time bounds. |
has_pii, verdict, retried | true for records with PII findings, guardrail verdicts, or router retries. |
filter | JSON array of up to 20 {field, op, values}. Fields: model, user, team, api_key, endpoint, status, latency_ms, input_tokens, output_tokens, retries, retried, guardrail_flagged, guardrail, guardrail_action, provider, virtual_model, auto_routed, policy. Ops: in, not_in (text); gte, lte, between, in, not_in (numbers); in (booleans). |
{"records": […], "bodies_recorded": …}; bodies_recorded is the deployment default (audit.log_bodies), so it tells “body recording is off” apart from “this body was withheld”. GET /admin/audit/filters returns the field catalog and the values seen in a time window.
In the console, Request Traces shows the same records with their policy, guardrail and routing detail; its Guardrail executions tab lists guardrail runs. Redacted values appear as [REDACTED] unless you hold gateway.traces.sensitive.read and use Show Original.
Verify the chain
first_bad_index is the position, oldest first, of the first record whose hash or link does not match.
Retention deletes whole records from the oldest end, which does not break verification: the check starts from the oldest remaining record’s stored previous hash.
Next steps
PII Detection & Redaction
Redaction before storage, and revealing originals.
Tracing & OpenTelemetry Export
Send the same requests to your tracing backend.
Policies
How policy scopes and precedence work.
Metrics
Aggregate views of what the log records one by one.