Audit Logging & Compliance

9 min read Module 7 of 10 Lesson 21 of 28

What you'll learn

  • Design an audit log schema covering the security-relevant events from Lessons 19 and 20, not just successful requests
  • Explain why an audit log needs to record rejected and blocked requests, not only successful ones
  • Write the query a compliance review typically asks for: every call a specific team made to a specific provider in a date range
  • Identify what 'tamper-evident' means for an audit log and why append-only storage matters
Building this at your company? For enterprise and company teams taking this to production: book a 30-minute session with our AI engineers for architecture guidance, code review, and a rollout plan for your use case.
Book a Team Session

Lessons 19 and 20 built two enforcement mechanisms, RBAC and PII redaction, and both lessons ended by noting that their events should be logged, not just enforced silently. This lesson builds that log properly: what it needs to capture, why rejected and blocked events matter as much as successful ones, and the queries a real compliance review actually runs against it.

Schema: What Belongs in Every Entry

from dataclasses import dataclass, field
from datetime import datetime, timezone

@dataclass
class AuditLogEntry:
    request_id: str
    timestamp: str
    team: str                    # from virtual key metadata, Lesson 19
    key_id: str                  # which specific virtual key, for revocation-scope investigations
    requested_model: str         # the alias the caller asked for
    served_by_model: str | None  # the real deployment that answered, if the request succeeded (Lesson 14)
    outcome: str                 # "success" | "rejected_budget" | "rejected_auth" | "blocked_guardrail"
    detail: str | None = None    # e.g. which PII entity types were found, which budget was exceeded
    cost_usd: float | None = None

def write_audit_entry(entry: AuditLogEntry):
    # Append-only: this store is never updated or deleted in place, only
    # ever appended to, so that altering history requires visibly removing
    # or replacing whole records rather than editing one quietly.
    append_only_store.write(entry)

The outcome field is deliberately broad enough to cover every enforcement point built across this module: rejected_auth for the RBAC scope violations from Lesson 19, rejected_budget for the hard budget caps from Lesson 18, blocked_guardrail for a PII redaction or content-moderation event from Lesson 20, and success for everything that completed normally. Every one of these gets a row.

Why Rejected and Blocked Events Matter Most

A log limited to successful requests would be blind to exactly the events a security investigation cares about most: a key repeatedly attempting to reach a model outside its granted scope, a burst of PII-redaction events from one service (worth investigating even though the redaction worked correctly, because it suggests that service is routinely handling data it maybe shouldn’t be), or a caller repeatedly hitting its budget ceiling in a pattern that looks more like a runaway loop than normal usage. Recording only what succeeded would make the audit log good for cost reporting (Lesson 18) but useless for security review, which is a different purpose the same underlying event stream can and should serve.

The Query a Compliance Review Actually Asks For

-- "Show every request the fraud-detection team made to Anthropic models
-- in the last 90 days, including anything that was rejected or blocked."
SELECT
    request_id,
    timestamp,
    requested_model,
    served_by_model,
    outcome,
    detail,
    cost_usd
FROM audit_log
WHERE team = 'fraud-detection'
  AND requested_model LIKE '%claude%'
  AND timestamp >= now() - interval '90 days'
ORDER BY timestamp DESC;
flowchart TD
    Auth["Lesson 19: RBAC decision\n(success or rejected_auth)"] --> Log["Audit log entry"]
    Budget["Lesson 18: budget check\n(success or rejected_budget)"] --> Log
    Guardrail["Lesson 20: PII/guardrail check\n(success or blocked_guardrail)"] --> Log
    Log --> Store["Append-only store\n(tamper-evident)"]
    Store --> Query["Compliance query:\nby team, model, date range, outcome"]

    style Auth fill:#EEF0F7,stroke:#6366F1,color:#0F172A,rx:8px,ry:8px
    style Budget fill:#EEF0F7,stroke:#6366F1,color:#0F172A,rx:8px,ry:8px
    style Guardrail fill:#EEF0F7,stroke:#6366F1,color:#0F172A,rx:8px,ry:8px
    style Log fill:#4338CA,stroke:#4338CA,color:#ffffff,rx:8px,ry:8px
    style Store fill:#F7F8FC,stroke:#0D9488,color:#0F172A,rx:8px,ry:8px
    style Query fill:#F7F8FC,stroke:#0D9488,color:#0F172A,rx:8px,ry:8px

This query only works because every enforcement point across Module 7 writes to the same schema with consistent team, outcome, and timestamp fields, exactly the discipline of centralizing policy at one chokepoint that has run through this entire course, applied here to the record of what the policy actually did. With RBAC, PII redaction, and audit logging in place, Module 7’s governance layer is complete. Module 8 turns from “was this request authorized and logged” to a different question: “was this request’s answer any good,” instrumenting every model call for tracing, quality monitoring, and controlled experimentation across providers.

Knowledge Check

3 questions to test your understanding

1 Why does a useful audit log need to record rejected requests (failed auth, over-budget, blocked by a guardrail), not just successful ones?

2 What does 'tamper-evident' mean for an audit log, and why does append-only storage matter for it?

3 A compliance reviewer asks: 'show me every request the fraud-detection team made to Anthropic in the last 90 days, including any that were blocked.' What does answering this require the audit log to have recorded consistently?

Go further with expert guidance

Ready to build production AI?
Talk to our R&D team.

These courses give you the foundation. Our embedded AI teams take you from prototype to production in 30–90 days, with your team, your codebase, your goals. Book a free strategy call to see how we can accelerate your AI initiative.

30 minutes · No obligation · Expert AI engineers, not sales reps

AI Architecture Review

Audit your current stack and identify high-impact improvements

Project Review

Get expert feedback on your AI implementation and codebase

Team Mentoring

Upskill your engineers with hands-on AI coaching sessions

AI Strategy

Define your AI roadmap, prioritization, and implementation plan