Skip to content

Logs & Audit Trail

Brutor keeps three distinct log streams, each with a different job:

Stream Job Loss tolerance
Proxy logs Forensics: every runtime request with full context Best-effort under extreme load
Usage logs Metering ledger: billing, quotas, cost attribution Never dropped
Audit logs Evidence: admin config changes + governance decisions Immutable, insert-only

Every request through the Core Proxy — LLM, MCP, Virtual MCP, skill, OAuth, A2A — writes a proxy-log row. The field-by-field reference, including which fields the tamper-evident hash covers, is The audit row; in summary a row carries:

  • Classificationproxy_type (llm, mcp, virtual-mcp, skill, oauth, a2a) and proxy_subtype (e.g. chat_completions, embeddings, anthropic_messages, sse_stream, and governance outcomes like approval_required, semantic_policy_block, residency_violation)
  • Outcomehttp_status_code, success, duration_ms
  • LLM economics — resolved model, llm_prompt_tokens, llm_completion_tokens, llm_total_tokens, llm_estimated_cost_usd; media counters for image/audio/video
  • Governanceguardrail_blocked, argument_policy_denied, pii_detected, agent-authz decision, compliance tags
  • Attribution — user, group, API key, agent identity, and the full call-chain (root_task_id / parent_task_id / delegation_depth) so multi-agent delegations read as one trace
  • Provenancesource (gateway for inline traffic, import:* for observed/imported telemetry)
  • Bodies — request and response previews
  • Integrityrequest_content_hash / response_content_hash: SHA-256 over the content exactly as the gateway captured it, sealed before any retention mode truncates, redacts or drops the preview, and inside the row’s audit hash — so a body can be pruned and the row still proves what it was
  • Trace contexttrace_id / span_id from the caller’s W3C traceparent header (the span’s own ids on OTel-inlet rows), so a row can be found from your own tracing tool; asserted by the client, so outside the hash
  • Tool provenanceserver_version, as announced by the MCP server at initialize, and tool_definition_hash, the SHA-256 of the canonical tool definition in force for the call: a changed description or schema under the same tool name is a different hash. Witnessed, and inside the hash
  • Deciding modeldeciding_model: on a tool, skill or A2A action, the model whose completion decided it (the run’s most recent model call), resolved at write time so the answer is on the row rather than in a join

In the Admin UI, the proxy log explorer lives under Monitoring — filter by type, user, server, model, status, duration, correlation ID, or compliance tags, and expand a row for the full request/response detail:

Proxy logs in the Admin UI

Programmatically, the same power is one endpoint:

Terminal window
curl -X POST http://localhost:5050/v1/admin/proxy-logs/query \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"proxy_type": "llm",
"success": false,
"limit": 50
}'

System-level views (including live tailing) are under Syslogs:

Proxy logs

The usage ledger is the money-and-quota record: one row per metered event with user_id, group_id, ancestry_path (for hierarchical roll-ups), resource_type (llm / mcp / skill), token counts, estimated_cost with prompt/completion split, MCP tool metrics, and source provenance. Quota enforcement, Mission Control cost views, and per-group usage tabs are all built on it.

Two immutable trails cover the “who changed what, who decided what” questions:

  1. Admin audit log — every Admin API write (create/update/delete of groups, models, guardrails, keys, …) with the acting admin, before/after details, IP and user agent. Viewable under Monitoring → Audit Logs:

    Audit logs in the Admin UI

  2. Governance decisions — enforcement events are audit rows in the proxy-log stream: guardrail blocks, policy denials, approval decisions (approval_approved / approval_rejected, spliced into the originating request’s trace), residency violations. Each guardrail block writes exactly one audit row — counts in reports match rows in the log one-to-one.

System-operator actions from the operations console (cache flushes, counter resets, log-level changes, …) land in a separate insert-only system_operation_log with before/after state snapshots.

Admin audit logs

Telemetry writes go through bounded in-memory channels with batch consumers (multi-row inserts, up to 500 rows per batch), so logging never blocks the request path. The backpressure policy differs by stream:

  • Proxy logs & metrics — best-effort: under extreme sustained load the channel can fill and rows are shed (a drop counter records exactly how many).
  • Usage logs — never dropped: when the channel is full the writer waits for capacity. Billing data survives load spikes.

Retention is a per-tenant policy — body storage mode (full, redacted, none), a body window, a log window and a run window — swept hourly by the Control Plane, with every deletion declared to the audit-chain verifier so the surviving rows still verify. Reading stored bodies needs a separate permission (proxy:logs:read_bodies) from reading the rows, and a data subject can be erased from the ledger on request. All of it is on Data handling. A one-off prune is still available:

Terminal window
curl -X POST http://localhost:5050/v1/admin/proxy-logs/cleanup \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"days_to_keep": 90}'
  • Mission Control — the aggregate view over these streams
  • Metrics & Grafana — counters and histograms for alerting, without row-level detail
  • Compliance — turning tagged rows into auditor-ready reports
  • The audit row — every field of the proxy-log row, and what the hash covers