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(opt-in HTTP audit),@Auditdecorator (action/resource/result metadata), audit transports (console, file with JSONL rotation, Kafka with JSON/Avro), actor tracking. - Dependencies:
@hazeljs/core, optionally@hazeljs/kafkafor Kafka transport. - Common patterns: Register
AuditModule.forRoot()with transport config → attachAuditInterceptoron controllers/routes → use@Auditfor semantic action names → callAuditService.log()for business events. - Common mistakes: Expecting audit without attaching
AuditInterceptor; not configuring file rotation (disk fills up); logging extra user fields instead ofactorFromContext().
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 — Attach
AuditInterceptorto log handled requests (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 — Opt-in HTTP request audit when attached to controllers or routes
- 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 and attach the interceptor
import { HazelModule, Controller, Get, UseInterceptors } from '@hazeljs/core';
import { AuditModule, AuditInterceptor, Audit } from '@hazeljs/audit';
@HazelModule({
imports: [AuditModule.forRoot()],
})
export class AppModule {}
@Controller('/orders')
@UseInterceptors(AuditInterceptor)
export class OrderController {
@Get()
@Audit({ action: 'order.list', resource: 'Order' })
list() {
return this.orderService.findAll();
}
}
Registering AuditModule provides AuditService and AuditInterceptor. HTTP requests are audited only where you attach the interceptor. Use AuditService.log() for custom business events.
2. Module options
AuditModule.forRoot({
transports: [new ConsoleAuditTransport()], // default
includeRequestContext: true,
redactKeys: ['password', 'token', 'secret', 'authorization'],
redactNested: true,
actorFields: ['id', 'username', 'role'],
interceptor: {
excludePaths: ['/health', '/metrics'],
},
});
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(denied when a guard throwsUnauthorizedError) - timestamp — ISO string (set automatically if omitted)
- requestId — from correlation headers or router-generated id
- method / path — from HTTP context when
includeRequestContextis true (default) - metadata — extra structured data (sensitive keys redacted by default, nested when
redactNestedis true)
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
When AuditInterceptor is attached, @Audit sets the audit action/resource and can include a redacted result snapshot:
import { Controller, Post, Body, UseInterceptors } from '@hazeljs/core';
import { Audit, AuditInterceptor } from '@hazeljs/audit';
@Controller('/orders')
@UseInterceptors(AuditInterceptor)
export class OrderController {
@Post()
@Audit({ action: 'order.create', resource: 'Order', includeResult: true })
async create(@Body() dto: CreateOrderDto) {
return this.orderService.create(dto);
}
}
Use getAuditMetadata(target, propertyKey) and hasAuditMetadata(target, propertyKey) to read decorator metadata in other code.
License
Apache-2.0
Recipes
Recipe: Audit Business Events with AuditService
// File: src/orders/order.service.ts
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 createOrder(userId: string, items: unknown[], context: RequestContext) {
const order = { id: `ORD-${Date.now()}`, items, status: 'created' };
await this.audit.log({
action: 'order.created',
resource: 'order',
resourceId: order.id,
actor: this.audit.actorFromContext(context) ?? { id: userId },
requestId: context.requestId,
metadata: { itemCount: items.length },
});
return order;
}
}
Recipe: HTTP Audit Logging with AuditInterceptor
// File: src/app.module.ts
import { HazelModule, Controller, Get, UseInterceptors } from '@hazeljs/core';
import {
AuditModule,
AuditInterceptor,
FileAuditTransport,
Audit,
} from '@hazeljs/audit';
@HazelModule({
imports: [
AuditModule.forRoot({
transports: [
new FileAuditTransport({
filePath: './logs/audit.jsonl',
maxSizeBytes: 10 * 1024 * 1024,
rollDaily: true,
}),
],
interceptor: { excludePaths: ['/health'] },
}),
],
})
export class AppModule {}
@Controller('/orders')
@UseInterceptors(AuditInterceptor)
export class OrderController {
@Get()
@Audit({ action: 'order.list', resource: 'Order' })
list() {
return [];
}
}
Related Resources
- Auth Package – Authentication for audit actor tracking
- CASL Package – Authorization for permission-based auditing
- Kafka Package – Async audit log transport
- Agent Gatekeeper – use
createAuditTransportSink(KafkaAuditTransport)for shared Gatekeeper audit on multi-replica deployments - hazeljs-audit-starter – Full audit logging example