@xenterprises/fastify-xlogger
v1.3.0
Published
Fastify plugin for standardized logging with Pino - context, redaction, and canonical schema
Readme
@xenterprises/fastify-xlogger
Standardized structured logging for Fastify, built on Pino. Automatic request context extraction, secret redaction, canonical log schema, boundary logging for external API calls, and background job correlation. Optional Betterstack/Logtail transport.
Install
npm install @xenterprises/fastify-xlogger fastify@5Minimal example
import Fastify from "fastify";
import xLogger, { getLoggerOptions } from "@xenterprises/fastify-xlogger";
const fastify = Fastify({
logger: getLoggerOptions({ serviceName: "my-api" }),
});
await fastify.register(xLogger, { serviceName: "my-api" });
fastify.get("/users/:id", async (request) => {
request.contextLog.info({ userId: request.params.id }, "Fetching user");
return { id: request.params.id };
});Options
All configuration is passed at registration. The plugin never reads process.env — pass environment-derived values in yourself.
| Option | Type | Required | Default | Description |
|--------|------|----------|---------|-------------|
| active | boolean | no | true | Set false to skip registration entirely |
| serviceName | string | no | "fastify-app" | Service identifier stored in config |
| environment | string | no | "development" | Environment name stored in config |
| redactPaths | string[] | no | [] | Additional paths to redact (extends defaults) |
| redactClobber | boolean | no | false | Replace default redact paths instead of extending |
| includeRequestBody | boolean | no | false | Attach the parsed request body to the per-request log line (debug level) |
| includeResponseBody | boolean | no | false | Attach the response body to the per-request log line (debug level) |
| contextExtractor | function | no | null | (request) => object merged into log context |
| enableBoundaryLogging | boolean | no | true | Emit boundary.request.start / boundary.request.end debug events around each request |
getLoggerOptions(options)
Helper that returns Pino options for the Fastify({ logger }) constructor. Also env-free.
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| level | string | "debug" ("info" when environment is "production") | Log level |
| environment | string | "development" | Drives default level and pretty printing |
| serviceName | string | "fastify-app" | Value for base.service |
| redactPaths | string[] | [] | Additional redact paths |
| pretty | boolean | false | Force pino-pretty transport |
| transport | object | pino-pretty outside production | Custom Pino transport (single target or targets array) |
const fastify = Fastify({
logger: getLoggerOptions({
serviceName: "my-api",
environment: process.env.NODE_ENV, // consumer owns env access
transport: {
target: "@logtail/pino", // npm i @logtail/pino (optional peer)
options: { sourceToken: process.env.BETTERSTACK_SOURCE_TOKEN },
},
}),
});When a custom transport is provided it overrides the default environment-based transport.
Decorators
| Decorator | Description |
|-----------|-------------|
| fastify.xLogger.config | Resolved plugin configuration |
| fastify.xLogger.extractContext(request) | Extract context (requestId, orgId, userId, route, method, traceId/spanId) |
| fastify.xLogger.logEvent(params) | Log a business event: { event, msg?, level?, data?, request? } |
| fastify.xLogger.logBoundary(params) | Log an external API call: { vendor, operation, externalId?, durationMs?, statusCode?, success?, retryCount?, metadata?, err?, request? } |
| fastify.xLogger.createBoundaryLogger(vendor, operation, request?) | Timed boundary logger with retry(), success(params), fail(err, params) |
| fastify.xLogger.createJobContext(params) | Job correlation context with context, log, start(data), complete(data), fail(err, data) |
| fastify.xLogger.levels | Log level constants (LOG_LEVELS) |
| fastify.xLogger.redactPaths | Effective redact paths |
| request.contextLog | Child logger with request context, set on every request |
Context extraction pulls orgId/userId from request.user, x-org-id / x-tenant-id / x-user-id headers, and OpenTelemetry traceparent headers, plus anything returned by contextExtractor.
Redaction
Default redact paths (censored as [REDACTED]):
req.headers.authorization,req.headers.cookie,req.headers['set-cookie'],req.headers['x-api-key']password,token,secret,apiKey,api_key,accessToken,access_token,refreshToken,refresh_token,privateKey,private_keycardNumber,card_number,cvv,ssn,creditCard*.password,*.token,*.secret,*.apiKey,*.api_key
Redaction and body logging
Redaction is applied by Pino at log time, against the logger options you created with
getLoggerOptions() (or your own redact config). When includeRequestBody /
includeResponseBody are enabled, bodies are logged as structured objects, so they
flow through the same redaction paths: a body field one level deep (e.g.
requestBody.password, responseBody.token) is caught by the default wildcard paths
(*.password, *.token, ...). Add your own patterns via redactPaths (e.g.
"*.creditCard" covers requestBody.creditCard).
Bodies are deep-copied before logging with safety caps — strings truncated at 2048
chars, nesting capped at depth 5, arrays capped at 100 items, and circular references
replaced with "[Circular]" — so logging a body can never crash the process. Stream
payloads are not buffered; they log as "[Stream]".
Routes
None. The plugin adds no routes; it adds an onRequest hook (context logger, plus a
boundary.request.start debug event when enableBoundaryLogging is on), an onSend
hook (response body capture, only registered when includeResponseBody is enabled),
and an onResponse hook (canonical http.response log line, warn for 4xx, error
for 5xx, plus a boundary.request.end debug event when enableBoundaryLogging is on).
When body logging is enabled, bodies are attached to the http.response line as
requestBody / responseBody and the line is logged at debug level for non-error
responses (4xx/5xx keep warn/error).
Error behavior
Registration fails fast when options are invalid — messages name the plugin, the option, and show a correct example:
| Error | When |
|-------|------|
| xlogger: option \redactPaths` must be an array of strings, e.g. ...|redactPathsis not an array of strings |
|xlogger: option `contextExtractor` must be a function, e.g. ...|contextExtractoris not a function |
|xlogger: option `serviceName` must be a string, e.g. ...|serviceNameis not a string |
|xlogger: option `environment` must be a string, e.g. ...|environmentis not a string |
|xlogger: option `includeRequestBody` must be a boolean, e.g. ...| wrong type |
|xlogger: option `includeResponseBody` must be a boolean, e.g. ...| wrong type |
|xlogger: option `redactClobber` must be a boolean, e.g. ...| wrong type |
|xlogger: option `enableBoundaryLogging` must be a boolean, e.g. ...` | wrong type |
The decorator methods also validate their required arguments at call time:
| Error | When |
|-------|------|
| [xLogger] logEvent requires a string 'event' parameter | logEvent() without a string event |
| [xLogger] logBoundary requires a string 'vendor' parameter | logBoundary() without a string vendor |
| [xLogger] logBoundary requires a string 'operation' parameter | logBoundary() without a string operation |
| [xLogger] createBoundaryLogger requires a string 'vendor' parameter | missing vendor |
| [xLogger] createBoundaryLogger requires a string 'operation' parameter | missing operation |
| [xLogger] createJobContext requires a string 'jobName' parameter | missing jobName |
Log Levels
| Level | Value | Use For |
|-------|-------|---------|
| fatal | 60 | Process cannot continue |
| error | 50 | Failures requiring attention |
| warn | 40 | Recoverable issues |
| info | 30 | Business events, normal operations |
| debug | 20 | Detailed debugging information |
| trace | 10 | Very detailed tracing |
How it works
The plugin registers up to three Fastify hooks:
onRequest— creates a child Pino logger bound torequest.contextLogwith extracted context (requestId, orgId, userId, route, method, OpenTelemetry trace). WhenenableBoundaryLoggingis on (default), also emits aboundary.request.startdebug event.onSend(only whenincludeResponseBodyis enabled) — captures the response payload for logging; stream payloads are skipped.onResponse— logs every completed response as a canonicalhttp.responseevent with status code, duration, and request context (errorfor 5xx,warnfor 4xx,infootherwise —debugwhen body logging is enabled). Emitsboundary.request.endat debug level whenenableBoundaryLoggingis on.
The decorator methods are stateless utilities that write to request.log (when a request is provided) or fastify.log.
Requirements
- Node.js >= 20
- Fastify ^5.0.0 (peer dependency)
@logtail/pinois an optional peer dependency (only needed for the Betterstack/Logtail transport)
License
Proprietary — All Rights Reserved X Enterprises. See LICENSE.
