# middleware/audit-middleware _(no submodules)_ ## Purpose Builds the global Hono middleware that records one audit event per non-excluded request through the underlying `AuditLogger`. The middleware also installs the `c.set("audit", ...)` slot before invoking `next()` so handlers can fire explicit, semantic events through `c.get("audit").log(...)` regardless of whether the request itself was recorded. This file is the per-request hot path; everything else in the plugin exists to support it. The middleware never throws into the request response — sink failures are caught, logged at warn, and surfaced via the `audit.sink_failures_total` counter. Telemetry must not 500 the caller. ## Features - Per-request event capture: method, path, status, duration (ms), request id, principal id, tenant id, response size, severity (info < 400, warning < 500, error otherwise). - Configurable redaction of query, headers, and (opt-in) JSON body via `redactPayload`. - Head-end sampling gate via `samplingRate` and caller-supplied (or default `Math.random`) RNG. - Exclude paths (exact match) skip recording while still installing the `audit` context slot. - Never throws — sink failures logged at warn, counted, propagated through `audit.requests_recorded_total{outcome="error",sampled="true"}`. - Re-throws any error the downstream handler raised AFTER recording the audit (so the host error-handler still runs). - Records four OTel observations: recorded counter (with `outcome` + `sampled` labels), excluded counter, sink-failures counter, duration histogram. ## Usage: Basic ```ts import { AuditLogger, MemoryAuditAdapter } from "@suaveplan/audit-logging"; import { createAuditMiddleware } from "@suaveplan/api-plugin-audit/middleware/audit-middleware"; import { Hono } from "hono"; const app = new Hono(); const logger = new AuditLogger({ storage: new MemoryAuditAdapter(), secretKey: process.env.AUDIT_SECRET_KEY!, }); app.use( "*", createAuditMiddleware({ logger, config: { sink: logger.getLogger ? logger : (undefined as never), secretKey: "", samplingRate: 1, excludePaths: ["/health"], redactQueryParams: ["token"], redactHeaders: ["authorization"], redactBodyFields: ["password"], recordResponseSize: true, recordRequestBody: false, enableChainIntegrity: false, rng: Math.random, }, hostLogger: console as never, }) ); ``` ## Usage: Advanced ```ts import { AuditLogger, MemoryAuditAdapter } from "@suaveplan/audit-logging"; import { createAuditMiddleware } from "@suaveplan/api-plugin-audit/middleware/audit-middleware"; // Deterministic sampling for tests: let i = 0; const seq = [0.1, 0.9, 0.4, 0.8]; const rng = () => seq[i++ % seq.length] as number; const middleware = createAuditMiddleware({ logger: new AuditLogger({ storage: new MemoryAuditAdapter(), secretKey: "x".repeat(32), }), config: { sink: new MemoryAuditAdapter(), secretKey: "x".repeat(32), samplingRate: 0.5, excludePaths: [], redactQueryParams: ["secret"], redactHeaders: ["authorization"], redactBodyFields: ["password"], recordResponseSize: true, recordRequestBody: true, // includes JSON bodies in the audit detail enableChainIntegrity: true, rng, }, hostLogger: console as never, }); ``` ## API Reference - `createAuditMiddleware(deps: { logger: AuditLogger; config: AuditPluginConfig; hostLogger: LoggerLike }): MiddlewareHandler` - `logger` — the underlying `AuditLogger` constructed in `onInit`. - `config` — the resolved `AuditPluginConfig`. - `hostLogger` — the logger used to surface sink-write failures at warn. The returned middleware is a standard Hono `MiddlewareHandler` you can register via `app.use("*", ...)`. ## Implementation Notes `c.set("audit", ...)` is set BEFORE the excluded-path / sampling gates so handlers on excluded paths can still emit explicit events. The middleware reads the response status from `c.res?.status` after `next()`; when the downstream handler throws, the middleware captures the throw, sets status to 500 in the audit record, then re-throws so the host error handler still produces the actual response. JSON body capture clones `c.req.raw` to avoid consuming the body the handler needs to read; non-JSON bodies are not captured (binary uploads would balloon the audit volume). Sink-write failures never propagate to the request — they update three OTel counters and a warn-level log instead.