Audit Package

npm downloads

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 auditAuditInterceptor logs every request (method, path, result)
  • Custom events — Inject AuditService and call log() 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 contextactorFromContext() 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

  1. AuditModule — Registers the module via forRoot(options)
  2. AuditService — Injected service; call log(event) and actorFromContext(context)
  3. AuditInterceptor — Logs every HTTP request as an audit event
  4. Transports — Console, File, Kafka; implement AuditTransport for custom backends
  5. @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
  • resultsuccess | 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