Audit Module - Overview
Complete guide to the EduShade Audit & Activity logging system
Audit Module
The Audit Module is the platform's tamper-evident record of who did what, when, where, and to which resource. It captures two distinct streams — audit logs for admin/system mutations and activity logs for user actions and HTTP traffic — and exposes them through admin and user-facing UIs.
What This Module Covers
| Feature | Description |
|---|---|
| Audit Logs | Admin-visible trail of every CRUD mutation across the platform (with before/after snapshots) |
| Activity Logs | Admin-visible trail of all user activity (logins, enrollments, HTTP calls, impersonation) |
| My Activity Logs | Self-service activity log shown to end users in their account settings |
| Event Ingestion | Pub/sub topics, payload contracts, subscribers, and retry semantics |
| Audit Middleware | Drop-in Gin middleware that auto-publishes audit events from any service |
| Filters & Search | All supported filter dimensions, sorting, and pagination |
| Export | Download audit logs as a JSON file with date-range and filter scoping |
| Retention & Cleanup | Per-stream retention policy, scheduled cleanup, and the system cleanup endpoint |
Architecture Overview
The audit system is an independent microservice (audit-service) that consumes events from a pub/sub bus and serves a small set of read APIs. No application code writes audit logs directly — every record arrives through the event bus.
Key Components
- Backend: Go (Gin framework) with Bun ORM on PostgreSQL
- Frontend: Next.js admin pages + an embedded user-account widget (
/admin/audit-logs,/admin/activity-logs,/dashboard/profile/settings/activity-logs) - Transport: Redis-backed pub/sub (Watermill) on the topics
audit.eventsandactivity.events - Storage: Two PostgreSQL tables —
audit_logsandactivity_logs— in the dedicatededushade_auditdatabase - Multi-Tenancy: Tenant scoping is enforced on every read query; every audit row carries a
tenant_id
Service Topology
The audit-service connects to three PostgreSQL databases:
| Database | Purpose |
|---|---|
edushade_audit | Owns the audit_logs and activity_logs tables |
edushade_auth (RBAC DB) | Read-only — used for permission checks and to enrich logs with actor user details |
edushade_tenant | Read-only — used by the tenant resolver middleware |
Default service port: 8096.
Two Streams: Audit vs Activity
EduShade keeps two distinct event streams. They look similar but answer different questions.
| Stream | Answers | Captured by | Stored in |
|---|---|---|---|
| Audit logs | "Who changed what, and what did the data look like before and after?" | Mutation handlers + the AuditMiddleware on admin route groups | audit_logs |
| Activity logs | "What did users do — login, enrol, submit a quiz, hit this endpoint?" | Service handlers (auth-service, learning-service, etc.) publishing on user actions | activity_logs |
A useful rule of thumb:
If the row exists to prove an admin did something, it's an audit log. If the row exists to show what a user did, it's an activity log.
Both streams support impersonation (the activity log additionally carries an impersonated_by column).
How a Log Gets There (High-Level Flow)
1. Some service does a mutation OR a user action happens
2. Service builds an AuditEventPayload or ActivityEventPayload
3. Service publishes it to "audit.events" or "activity.events" via Redis pub/sub
4. audit-service subscriber picks the message up
5. Subscriber unmarshals, validates, persists to PostgreSQL
6. Admin or user reads it back via REST endpoints (with tenant scoping)
7. Retention scheduler periodically deletes rows older than the cutoffThe end-to-end path is fully asynchronous — publishing services do not wait for the audit-service to acknowledge. This means a slow or briefly-down audit-service does not block user-facing requests; missed events are recovered from Redis on next consumption (with bounded retry).
Multi-Tenancy
Tenant isolation is non-negotiable:
- Every
audit_logsrow has atenant_id(NOT NULL). activity_logsrows have a nullabletenant_idso genuinely cross-tenant system events can be recorded — but read APIs always filter by the caller's tenant.- The composite index
(tenant_id, created_at DESC)exists on both tables — pagination and date-range queries are tenant-rooted by design. - Cross-tenant reads are not exposed by any endpoint.
Getting Started
If you're an administrator, start with:
- Audit Logs — Browse the admin mutation trail
- Activity Logs — Browse user action and HTTP request history
- Filters & Search — Narrow results by action, module, date range, and more
- Export — Pull a JSON snapshot for offline review
If you're a service author wiring a new microservice into the audit stream, read:
- Event Ingestion — The payload shapes you must publish
- Audit Middleware — The drop-in Gin middleware that does most of the work for you
If you're an operator, also see:
- Retention & Cleanup — Schedule, batch sizes, and manual triggers
- Event Ingestion — Topic names, retry semantics, and how to wire a new publisher

