Skip to main content
Your progress
0 of 6 complete0%
T4 / M4.3 / L1 OF 5 / Architect LEVEL / 9 min

What's logged

Outcome

By the end of this lesson, you will be able to identify what the audit log captures and what it deliberately excludes, explain the mutations-only design choice, and describe the per-event payload that lands in storage.


PrerequisitesM4.1: RBAC fundamentals
Time9 minutes

1. Concept

Everything that changes something is recorded, except personal preference writes (pins and settings under /my/preferences), whether it was done in the product or through the API. The product calls the API anyway; the only difference in the row is its source tag.

What gets stored is the request and the response, up to a size cap, except in the areas that carry credentials.

Reading is not recorded. That is a decision rather than an oversight, and the reason behind it changes what the audit log can and cannot be used for.

Terminal window
ACTION LOGGED? WHY
──────────────────────────────────────────────────────────────────
Create / update / delete Yes State change
schedules, recs, budgets, = audit-worthy
resource-groups, etc.
Apply remediation Yes High-impact mutation
Set / cancel override Yes Affects spend
Connect cloud account Yes Provenance critical
Rotate / revoke credentials Yes Security event
Modify role / user / team Yes Authorization change
Read schedule / recommendation No Read access already
View dashboard / cost report No gated by RBAC; volume
Browse resources No too high to log usefully

Why mutations only

Three reasons converge on the same answer. First, volume: reads far outnumber mutations in any active org, so logging them at the same fidelity would multiply audit storage with little forensic value. Second, redundancy: RBAC already controls read access. If a user does not have resource:view, they cannot read the resource; recording every successful resource:view does not tell you anything policy-table + role assignment does not already say. Third, focus: in an incident or audit, the relevant question is almost always “what changed” not “who looked.” Mutations-only keeps the log readable.

There is one exception and two gaps to know about:

Terminal window
EXCEPTION
MCP tool calls are logged, reads included. An assistant
calling a read tool produces a row (stored as method GET,
with the request but not the response).
GAPS
A request refused by RBAC (403) is not logged: the
refusal happens before the audit step.
Sign-in attempts are not logged: sign-in runs in
ZopNight's external auth service, outside your org.

Per-org Redis Streams

The audit log uses Redis Streams as the transport layer, scoped per organization.

Terminal window
PATH: api request → gateway (audit step) → backend service
↓ (async)
org-scoped Redis Stream
↓ (every minute)
MySQL audit table
↓
Audit Logs page + API

Per-org streams give two properties: org-scoped isolation (one customer’s audit volume cannot affect another’s) and a durable buffer between the stream write and the MySQL sync (so a database hiccup does not lose events). Nothing reads the stream directly, so an entry shows up on the page after the next sync, usually within about a minute.

MySQL sync

Every minute, accumulated stream entries are copied into the MySQL audit table. After that, MySQL is the canonical store: the Audit Logs page and the API both read it.

Terminal window
TIME WHERE EVENTS LIVE STATUS
──────────────────────────────────────────────────────────
T+0 Redis Stream (org-scoped) Buffered, not yet visible
T+1 min MySQL audit table Canonical, visible
Later Still in MySQL Kept indefinitely

Rows are kept indefinitely. There is no retention setting and no automatic purge. If you need your own long-term copy, export it (L3).

What gets stored per event

Terminal window
EVERY ROW
timestamp UTC, to the second
user who made the request (email)
method POST / PUT / PATCH / DELETE (GET for MCP reads)
path the resource path, e.g. /schedules
status_code response status
source ui (browser session), api (personal
access token) or mcp
error_message if the request failed
BODIES
body the request body
response_body the response body
(each up to the size cap, L2)
MCP ROWS ALSO CARRY
tool, session_id, resource_uid

There is no latency, trace ID, IP address, user agent, query string or header in the row.

Credentials are protected by dropping, not masking. For the areas that carry secrets (cloud accounts, connect flows, integrations, AI, app secrets and basic auth), the request and response bodies are not stored at all. For MCP calls, the specific arguments a tool declares as secret are replaced with [REDACTED]. L2 covers both.

Body capture

The request body and response body are stored for every logged mutation outside those credential areas. The audit log answers not just “what endpoint was hit” but “with what data, returning what result.” A year later when an auditor asks “what did the May 20 schedule update actually do?” you can show them the body.

There is no per-org switch to turn body capture off. The only setting is the size cap, which ZopNight sets for the whole deployment (L2).

Reasoning about what is NOT logged

Several categories of activity are absent from the audit log:

Terminal window
NOT LOGGED WHY
──────────────────────────────────────────────────────────────────
Successful reads through the app or API Volume + redundancy
Requests refused by RBAC Refused before the audit step
Sign-in attempts Handled by the auth service
Internal service-to-service calls Not user-initiated
User browser activity (page loads, clicks) Frontend telemetry separate
LLM conversations (with MCP) Tool calls logged; chat is not

MCP tool calls are in the same audit log as everything else, tagged source = mcp, so you can filter to them. On the Audit Logs page the Source filter offers All sources, MCP and Platform (everything except MCP); the API’s source parameter takes the raw values ui, api and mcp (internal and unknown are reserved and not produced today). The audit log is intentionally about the user-facing API surface.

How ZopNight uses it

The audit log feeds two surfaces: the Audit Logs page (Activity → Audit Logs), where admins browse and filter events, and the API at GET /orgs/{orgID}/audit-logs, which returns the same rows, 20 per page by default and up to 100. The filters are method, resource path, status (success or failed), user, a text search, source and a date range. Export is a CSV file, emailed to you (L3).

There are no pre-packaged compliance reports. For an audit you run the filter yourself (for example, every mutation on /roles or /assignments in the audit window) and export the result.


2. Demo

A typical audit log entry for a schedule creation:

Terminal window
AUDIT LOG ENTRY
─────────────────────────────────────────────────────────────
timestamp: 2026-05-20T16:42:11Z
user: jane@example.com
method: POST
path: /schedules
status_code: 201
source: ui
REQUEST BODY:
{
"name": "business-hours-eu",
"timezone": "Europe/London",
"crons": [
{"cronExpression": "0 8 * * 1-5", "action": "start"},
{"cronExpression": "0 20 * * 1-5", "action": "stop"}
]
}
RESPONSE BODY:
{
"id": "sch_xyz789",
"name": "business-hours-eu",
"created_at": "2026-05-20T16:42:11Z",
"resource_count": 14
}

A year later, when finance asks “what was the EU dev schedule when we started saving $X/month?” the audit log has the canonical answer, including the cron expressions and timezone. Attaching the resource group is a separate request, so it is a separate entry.


3. Hands-on (5 min)

Open Activity → Audit Logs. Set the date range to the last 7 days and filter method = POST:

Terminal window
EVENTS COUNTED (last 7 days):
POST events: _____
PUT events: _____
PATCH events: _____
DELETE events: _____
TOP ACTIONS (count by path):
__________________________ : _____ events
__________________________ : _____ events
__________________________ : _____ events
PICK ONE EVENT. Read its full payload:
Endpoint: __________
User: __________
What changed: __________
If your team's last 7 days has <5 mutations, RBAC is probably very
restrictive (or the team is on vacation). If >500, that's an active org.

4. Knowledge check

Q1

GET requests are:

A. Logged with full body and headers
B. Logged but summarized
C. Logged for admins only
D. Not logged

Show answer

Correct: D. Reads far outnumber mutations, and RBAC already controls who can read, so recording them adds storage cost without forensic value. Mutations-only is the deliberate design. The one kind of read that is logged is an MCP read-tool call; REST reads, including refused ones, are not.

Q2

The audit log captures:

A. The request body only, on the grounds that the response is far too sensitive to retain anywhere at all in the log
B. Headers only, with both of the bodies discarded at ingest time to keep the stream small enough to retain for the full retention window
C. Both request and response body up to a size cap, with the bodies dropped entirely for the areas that carry credentials
D. The response body only, on the grounds that it reflects what actually happened rather than what was asked for by the caller

Show answer

Correct: C. Request and response bodies are stored for every logged mutation, up to the size cap, except in credential areas such as cloud accounts, where both bodies are dropped. There is no per-org switch to turn capture off. “What was the schedule’s exact config?” is the kind of question this answers.

Q3

Redis Streams is used for:

A. Long-term durable storage of the entire audit log history itself
B. Org-scoped transport and a durable buffer until the per-minute MySQL sync
C. A backup copy of the MySQL audit tables, kept continuously in sync
D. Fan-out to the customer’s own SIEM, bypassing the MySQL write path

Show answer

Correct: B. Each org has its own stream, isolating audit volume between tenants. Nothing reads the stream directly: the page and API read MySQL, so entries appear after the next per-minute sync.


5. Apply

Browse the audit log at Activity → Audit Logs. The page filters on method, resource path, status, user, text search, source and date range. Export a CSV (it arrives by email) or call the Audit API (GET /orgs/{orgID}/audit-logs) if you want to copy entries into your own systems.

A useful habit: review audit-log volume weekly. Sudden spikes often indicate a runaway script; sudden drops often indicate a logging-pipeline issue.


Glossary terms touched

Audit log


Start with the bill.

Foundations takes about five hours. The first lesson is nine minutes.

Free curriculum. No paywall. 290 lessons across 7 courses, plus an optional 24-lesson beginner course. Browse every course without an account; a free account opens the lessons, saves your progress and opens the exams for three publicly verifiable credentials. Read it on the train, take the exam on a Saturday, list the credential on your résumé Monday.

5h median time to finish Foundations
free account, every course, no paywall
3 certifications, each with a public verifier
Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console· Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console·