HazelJS Audit Package
@hazeljs/audit provides audit logging and event trails for HazelJS — record who did what, when, and with what outcome. Events go to console, file (JSONL with rotation), or Kafka (JSON or Avro).
Quick Reference
- Purpose:
@hazeljs/auditprovides audit logging for compliance, security, and debugging — capturing HTTP requests, business events, actors, and outcomes with pluggable sinks (console, file, Kafka). - When to use: Use
@hazeljs/auditwhen a HazelJS application needs compliance audit trails, security logging, or event tracking for debugging. - Key concepts:
AuditInterceptor(HTTP audit),@Auditable()decorator (business events), audit sinks (console, file with JSONL rotation, Kafka with JSON/Avro), actor tracking. - Dependencies:
@hazeljs/core, optionally@hazeljs/kafkafor Kafka sink. - Common patterns: Register
AuditModulewith sink config → applyAuditInterceptorglobally for HTTP audit → use@Auditable()on service methods for business event audit. - Common mistakes: Not configuring file rotation (disk fills up); not including actor/user info in audit events; using console sink in production.
Purpose
Applications often need a reliable audit trail for compliance, security, and debugging. Building this from scratch involves capturing HTTP requests, business events, actors, and outcomes, then shipping them to logs or external systems. The @hazeljs/audit package simplifies this by providing:
- HTTP audit —
AuditInterceptorlogs every request (method, path, result) - Custom events — Inject
AuditServiceand calllog()with action, resource, actor - Pluggable transports — Console (default), file (JSONL with rotation), Kafka (JSON or Avro)
- @Audit decorator — Mark handlers with action/resource for metadata and tooling
- Redaction — Sensitive keys (password, token, etc.) redacted by default
- Actor from context —
actorFromContext()maps request user to audit actor
Architecture
The package uses a transport-based architecture: events flow through AuditService to all configured transports.
graph TD A["HTTP Request / Business Logic"] --> B["AuditInterceptor<br/>or AuditService.log()"] B --> C["AuditService<br/>(Sanitize, Redact)"] C --> D["Transports"] D --> E["ConsoleAuditTransport"] D --> F["FileAuditTransport"] D --> G["KafkaAuditTransport"] D --> H["Custom Transport"] style A fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff style B fill:#8b5cf6,stroke:#a78bfa,stroke-width:2px,color:#fff style C fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff style D fill:#3b82f6,stroke:#60a5fa,stroke-width:2px,color:#fff style E fill:#10b981,stroke:#34d399,stroke-width:2px,color:#fff style F fill:#10b981,stroke:#34d399,stroke-width:2px,color:#fff style G fill:#10b981,stroke:#34d399,stroke-width:2px,color:#fff style H fill:#10b981,stroke:#34d399,stroke-width:2px,color:#fff
Key Components
- AuditModule — Registers the module via
forRoot(options) - AuditService — Injected service; call
log(event)andactorFromContext(context) - AuditInterceptor — Logs every HTTP request as an audit event
- Transports — Console, File, Kafka; implement
AuditTransportfor custom backends - @Audit decorator — Metadata for action/resource on handler methods
Advantages
1. Compliance-ready
Structured events with action, actor, resource, result, and timestamp suit compliance and security reviews.
2. Multiple outputs
Send the same events to stdout, file, and Kafka (or your own transport) without duplicating logic.
3. Redaction by default
Sensitive keys in metadata are redacted so you don’t log passwords or tokens by mistake.
4. Kafka and Avro
Optional Kafka transport with custom serialization (e.g. Avro via serialize) for event pipelines.
5. Simple API
One interceptor for HTTP; one audit.log() call for business events. Actor is derived from request context when available.
6. Type safety
Full TypeScript support for AuditEvent, transports, and decorator options.
Installation
npm install @hazeljs/audit @hazeljs/core
Quick Start
1. Register the module
import { HazelModule } from '@hazeljs/core';
import { AuditModule } from '@hazeljs/audit';
@HazelModule({
imports: [AuditModule.forRoot()],
})
export class AppModule {}
With the module registered, use AuditInterceptor to log every HTTP request, or inject AuditService and call log() for custom events.
2. Module options
AuditModule.forRoot({
transports: [new ConsoleAuditTransport()], // default
includeRequestContext: true,
redactKeys: ['password', 'token', 'secret', 'authorization'],
});
3. Custom events with AuditService
import { Service } from '@hazeljs/core';
import { AuditService } from '@hazeljs/audit';
import type { RequestContext } from '@hazeljs/core';
@Service()
export class OrderService {
constructor(private readonly audit: AuditService) {}
async create(data: CreateOrderDto, context: RequestContext) {
const order = await this.repo.create(data);
this.audit.log({
action: 'order.create',
actor: this.audit.actorFromContext(context),
resource: 'Order',
resourceId: order.id,
result: 'success',
metadata: { amount: order.total },
});
return order;
}
}
Audit event shape
- action — e.g.
user.login,order.create - actor —
{ id, username?, role? }from request context when available - resource / resourceId — what was affected
- result —
success|failure|denied - timestamp — ISO string (set automatically if omitted)
- method / path — from HTTP context when using the interceptor
- metadata — extra structured data (sensitive keys redacted by default)
Transports
Console (default)
Writes one JSON line per event to stdout.
import { ConsoleAuditTransport } from '@hazeljs/audit';
transports: [new ConsoleAuditTransport()],
File (JSONL)
Appends to a file. Creates the file and parent dir on first event. Supports rotation by size or by day.
import { FileAuditTransport } from '@hazeljs/audit';
transports: [
new FileAuditTransport({
filePath: 'logs/audit.jsonl',
ensureDir: true,
maxSizeBytes: 10 * 1024 * 1024, // 10MB
rollDaily: true, // audit.2025-03-01.jsonl
}),
],
Kafka
Sends each event to a Kafka topic. Use with @hazeljs/kafka: pass KafkaProducerService as the sender. Optional key for partitioning; optional serialize for custom format (default JSON). For Avro, pass a serialize function that returns a Buffer.
import { KafkaAuditTransport } from '@hazeljs/audit';
import { KafkaProducerService } from '@hazeljs/kafka';
const transport = new KafkaAuditTransport({
sender: kafkaProducerService, // from your app's container
topic: 'audit',
key: (e) => e.actor?.id?.toString(), // optional partition key
// serialize: (e) => avroType.toBuffer(e), // optional Avro
});
You can add the Kafka transport at runtime after the app is created (e.g. after resolving KafkaProducerService) using auditService.addTransport(transport).
Custom transport
Implement the AuditTransport interface (log(event: AuditEvent)) to send events to your database, SIEM, or logging service.
@Audit decorator
Mark handlers with custom action/resource for metadata and tooling:
import { Controller, Post, Body } from '@hazeljs/core';
import { Audit } from '@hazeljs/audit';
@Controller('/orders')
export class OrderController {
@Post()
@Audit({ action: 'order.create', resource: 'Order' })
async create(@Body() dto: CreateOrderDto) {
return this.orderService.create(dto);
}
}
Use getAuditMetadata(target, propertyKey) and hasAuditMetadata(target, propertyKey) to read decorator metadata in interceptors or other code.
License
Apache-2.0
Recipes
Recipe: Audit Business Events with @Auditable
// File: src/orders/order.service.ts
import { Service } from '@hazeljs/core';
import { AuditService } from '@hazeljs/audit';
@Service()
export class OrderService {
constructor(private readonly audit: AuditService) {}
async createOrder(userId: string, items: any[]) {
const order = { id: `ORD-${Date.now()}`, items, status: 'created' };
await this.audit.log({
action: 'order.created',
resource: 'order',
resourceId: order.id,
actor: userId,
metadata: { itemCount: items.length },
});
return order;
}
}
Recipe: Global HTTP Audit Logging
// File: src/app.module.ts
import { HazelModule } from '@hazeljs/core';
import { AuditModule, AuditInterceptor } from '@hazeljs/audit';
import { APP_INTERCEPTOR } from '@hazeljs/core';
@HazelModule({
imports: [
AuditModule.register({
transport: 'file',
filePath: './logs/audit.jsonl',
rotation: { maxSize: '10mb', maxFiles: 5 },
}),
],
providers: [
{ provide: APP_INTERCEPTOR, useClass: AuditInterceptor },
],
})
export class AppModule {}
Related Resources
- Auth Package – Authentication for audit actor tracking
- CASL Package – Authorization for permission-based auditing
- Kafka Package – Async audit log transport
- hazeljs-audit-starter – Full audit logging example