Audit Package
The @hazeljs/audit package provides audit logging and event trails for HazelJS applications. Record who did what, when, and with what outcome for compliance and debugging. Events can go to console, file (JSONL with rotation), or Kafka (JSON or Avro).
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