Skip to main content
Your progress
0 of 5 lessons complete0%
T3 / M3.3 / L1 OF 5 / Architect TIER / 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.


TierArchitect
JTBD”Know exactly what evidence the audit log will and will not have when an auditor or incident responder asks.”
PersonasSecurity/Compliance · Platform Engineer · FinOps Lead
PrerequisitesM3.1: RBAC fundamentals
Time9 minutes
Bloom verbIdentify (Remember), Explain (Understand), Describe (Apply)

1. Concept

Everything that changes something is recorded, whether it was done in the product or through the API. The product calls the API anyway, so there is no difference between the two.

What gets stored is the whole request and the whole response.

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: a typical mid-size customer issues ~1,000 read requests for every mutation. Logging reads at the same fidelity would multiply the audit storage by 1,000× with minimal 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.

Reads do get logged in two narrow cases: when a read fails authorization (the 403/404 is recorded so anomalous access attempts surface), and when MCP-driven reads happen (M6 covers this: MCP calls are logged because the agent-driven access pattern is a newer surface that auditors specifically ask about).

Per-org Redis Streams

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

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

Per-org streams give three properties: real-time delivery (no batch lag; events show up within a second), org-scoped isolation (one customer’s audit volume cannot affect another’s), and durable buffer during the brief window between stream write and MySQL flush (so a database hiccup does not lose events).

MySQL sync

Every minute, accumulated stream entries flush to the MySQL audit table. After flush, MySQL is the canonical store. The Audit Log page in ZopNight UI queries MySQL; the API queries MySQL; long-term retention is MySQL-backed.

Terminal window
TIME WHERE EVENTS LIVE STATUS
──────────────────────────────────────────────────────────
T+0 Redis Stream (org-scoped) Real-time queryable
T+1 min MySQL audit table Canonical
T+1 min Stream entries trimmed Cleaned up
T+30 d Still in MySQL Default retention
T+1 yr Still in MySQL (Enterprise) Extended retention

Retention is org-controlled. Most customers retain indefinitely for compliance; some configure 1-year or 90-day windows; export-to-SIEM is supported for orgs that want their own long-term storage.

What gets stored per event

Terminal window
ALWAYS PRESENT
timestamp UTC, microsecond precision
org_id tenant scope
user_id who made the request (email + UID)
pat_id if PAT-authenticated (else null)
method POST / PUT / PATCH / DELETE
path /orgs/{org}/schedules
status_code response status
latency_ms server-side processing time
trace_id for cross-log correlation
REQUEST FIELDS
body full payload (redacted if sensitive)
query_params ?account_id=…
selected headers user_agent, ip_address, x-forwarded-for
RESPONSE FIELDS
body full response payload (redacted as needed)
error_message if applicable

Sensitive fields are redacted at the audit middleware before storage. Examples: cloud-account secret keys (replaced with ***), OAuth client secrets, PAT values on creation (only the PAT ID and description are stored, never the secret).

Body capture

By default, the full request body and response body are stored. This is “body capture mode”; the audit log answers not just “what endpoint was hit” but “with what data, returning what result.” For most customers, this is exactly the right default: a year later when an auditor asks “what did the May 20 schedule update actually do?” you can show them the body.

A small number of orgs disable body capture for storage cost or data-residency reasons. The L2 lesson covers when this trade-off is worth making.

Reasoning about what is NOT logged

Three categories of activity are conspicuously absent from the audit log:

Terminal window
NOT LOGGED WHY
──────────────────────────────────────────────────────────────────
Successful reads (most GETs) Volume + redundancy
Internal service-to-service calls Different audit channel
Database queries below the API layer Not user-initiated
User browser activity (page loads, clicks) Frontend telemetry separate
LLM conversations (with MCP) Tool calls logged; chat is not

When an auditor asks for evidence in one of these categories, the answer is “we have a different log for that”; frontend telemetry for browser activity, MCP-specific logs for agent tool calls, internal service logs for backend operations. The audit log is intentionally about the user-API surface.

How ZopNight uses it

The audit log feeds two surfaces directly: the Audit Log page in Settings, where admins browse and filter events; and the Audit API at /v1/audit/logs, which supports export to SIEM systems (Splunk, Datadog, Sumo Logic). The API is paginated, supports filtering by user, time, method, path, and status, and returns the same payload visible in the UI.

For compliance evidence collection (SOC 2, ISO 27001), ZopNight provides pre-formatted exports: “RBAC changes for the audit window,” “Cloud-account credential rotations,” “Schedule changes by team.” These exports are queries against the audit log packaged for auditor consumption.


2. Demo

A typical audit log entry for a schedule creation:

Terminal window
AUDIT LOG ENTRY
─────────────────────────────────────────────────────────────
timestamp: 2026-05-20T16:42:11.234Z
org_id: org_abc123
user_id: jane@example.com (uid_user_xyz)
pat_id: null (UI session, not PAT)
method: POST
path: /orgs/org_abc123/schedules
status: 201 Created
latency_ms: 87
trace_id: trc_001abc
ip_address: 198.51.100.42
user_agent: Mozilla/5.0 (Macintosh; ...)
REQUEST BODY:
{
"name": "business-hours-eu",
"timezone": "Europe/London",
"crons": [
{"expression": "0 8 * * 1-5", "action": "start"},
{"expression": "0 20 * * 1-5", "action": "stop"}
],
"resource_group_id": "rg_eu_dev"
}
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, timezone, and which resource group was affected.


3. Hands-on (5 min)

Open ZopNight Settings → Audit Log. Filter to the last 7 days, 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. Failed reads (auth denied) are logged separately Volume is ~1,000× mutations; RBAC already controls who can read; recording reads adds storage cost without forensic value. Mutations-only is the deliberate design. Failed reads are captured because they may indicate probing; successful reads 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, with sensitive fields (cloud-account secrets, PAT values, OAuth secrets) redacted at the middleware before storage
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. Full body capture is the default; some orgs disable it for cost/residency. Full request + response with redaction. “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. Real-time delivery and a durable buffer until the per-minute MySQL flush
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 and surfacing events in the UI within seconds. Streams are the transport + buffer; MySQL is the canonical long-term store.


5. Apply

Browse the audit log at Settings → Audit Log. The page filters on user, method, path, time, and status. Export to CSV from the page or query the Audit API for SIEM integration.

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 · Redis Stream · Body capture · Trace ID · Mutating action


Start with the bill.

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

Open curriculum. No login. No paywall. 290 lessons across 7 courses, 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
0 logins, paywalls, or marketing forms
open curriculum, public credential 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·