⚡ You're viewing a live demo of ChimerAI. Data resets daily at midnight UTC.Get the CLI →

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])
}

Further reading

ChimerAI Docs · Back to Demo