Audit Log
ChimerAI's Audit Log captures every significant action in your application — who did what, when, from which IP — providing a tamper-evident trail for compliance (SOC 2, ISO 27001, GDPR) and incident response.
What you get
- Automatic action capture — Middleware hooks for auth events, admin actions, data changes
- Structured log entries — Actor, action, resource, metadata, IP, user agent
- Searchable & filterable UI — Filter by user, action type, date range
- Export — Download logs as CSV or JSON
- Retention policy — Configurable auto-deletion of old entries
- API access — Query logs programmatically
Quick setup
npx chimerai add audit-log
Scaffolds:
app/api/audit-log/route.ts ← GET logs (admin only)
lib/audit.ts ← logAction() helper
middleware.ts (updated) ← Auto-log auth events
app/audit-log/page.tsx ← Admin log viewer
Logging an action
Use logAction() anywhere in your API routes or server actions:
// lib/audit.ts
import { db } from '@/lib/db';
export interface AuditEntry {
actorId: string;
actorEmail: string;
action: string; // e.g. 'user.deleted', 'billing.plan_changed'
resourceType?: string; // e.g. 'user', 'workspace'
resourceId?: string;
metadata?: Record<string, unknown>;
ip?: string;
userAgent?: string;
}
export async function logAction(entry: AuditEntry) {
await db.auditLog.create({ data: entry });
}
// In your API route:
await logAction({
actorId: session.user.id,
actorEmail: session.user.email!,
action: 'user.role_changed',
resourceType: 'user',
resourceId: targetUserId,
metadata: { oldRole: 'member', newRole: 'admin' },
ip: req.headers.get('x-forwarded-for') ?? undefined,
});
Querying logs
// app/api/audit-log/route.ts
import { db } from '@/lib/db';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
export async function GET(req: Request) {
const session = await getServerSession(authOptions);
if (session?.user?.role !== 'admin') return new Response('Forbidden', { status: 403 });
const { searchParams } = new URL(req.url);
const page = Number(searchParams.get('page') ?? 1);
const action = searchParams.get('action') ?? undefined;
const pageSize = 50;
const [logs, total] = await Promise.all([
db.auditLog.findMany({
where: action ? { action: { contains: action } } : {},
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { createdAt: 'desc' },
}),
db.auditLog.count({ where: action ? { action: { contains: action } } : {} }),
]);
return Response.json({ logs, total });
}
Prisma schema
model AuditLog {
id String @id @default(cuid())
actorId String
actorEmail String
action String
resourceType String?
resourceId String?
metadata Json?
ip String?
userAgent String?
createdAt DateTime @default(now())
@@index([action])
@@index([actorId])
@@index([createdAt])
}