Skip to content

Fastify Integration Server

NPM Version

Source

A Fastify plugin that provides request-scoped logging with automatic request/response logging and error handling. The auto-logging format follows pino-http conventions.

Installation

sh
npm i @loglayer/fastify loglayer @loglayer/transport-simple-pretty-terminal serialize-error
sh
pnpm add @loglayer/fastify loglayer @loglayer/transport-simple-pretty-terminal serialize-error
sh
yarn add @loglayer/fastify loglayer @loglayer/transport-simple-pretty-terminal serialize-error

We're using Simple Pretty Terminal here as an example to get nicely formatted logs. Any LogLayer-compatible transport can be used, including Pino, LogTape, Structured, Console, and others.

Basic Usage

typescript
import Fastify from "fastify";
import { LogLayer } from "loglayer";
import { serializeError } from "serialize-error";
import { getSimplePrettyTerminal, moonlight } from "@loglayer/transport-simple-pretty-terminal";
import { fastifyLogLayer } from "@loglayer/fastify";

const log = new LogLayer({
  errorSerializer: serializeError,
  transport: getSimplePrettyTerminal({
    runtime: "node",
    theme: moonlight,
  }),
});

// Do NOT set logger: true — the plugin handles all logging
const app = Fastify();
await app.register(fastifyLogLayer, { instance: log });

app.get("/", (request, reply) => {
  request.log.info("Hello from route!");
  reply.send("Hello World!");
});

app.get("/api/users/:id", (request, reply) => {
  const { id } = request.params as { id: string };
  request.log.withMetadata({ userId: id }).info("Fetching user");
  reply.send({ id, name: "John" });
});

await app.listen({ port: 3000 });

Each request automatically gets:

  • A child logger with a unique requestId in its context
  • Automatic request and response logging following pino-http conventions
  • Error logging via the onError hook

The plugin overrides Fastify's request.log with a LogLayer child logger, so you can use request.log directly in your route handlers with full access to LogLayer's API.

Important

Do not set logger: true or provide a loggerInstance on the Fastify constructor when using this plugin — the plugin handles all logging itself. Using both would result in duplicate log entries.

TypeScript Support

The plugin augments Fastify's FastifyBaseLogger interface with ILogLayer, so request.log has all LogLayer methods available with full type safety.

Configuration

OptionTypeDefaultDescription
instanceILogLayerrequiredThe LogLayer instance to use
requestIdboolean | (request: FastifyRequest) => stringtrueControls request ID generation
autoLoggingboolean | FastifyAutoLoggingConfigtrueControls automatic request/response logging
contextFn(request: FastifyRequest) => Record<string, any>-Extract additional context from requests

Auto-Logging Configuration

When autoLogging is an object:

OptionTypeDefaultDescription
logLevelstring"info"Default log level for request/response logs
ignoreArray<string | RegExp>[]Paths to exclude from auto-logging
requestboolean | FastifyRequestLoggingConfigtrueControls request logging (fires when request is received)
responseboolean | FastifyResponseLoggingConfigtrueControls response logging (fires after response is sent)

Both request and response accept an object with a logLevel property to override the default log level.

Request Log Output

When enabled (default), request logging produces:

  • Message: "incoming request"
  • Metadata: { req: { method, url, remoteAddress } }

Response Log Output

When enabled (default), response logging produces:

  • Message: "request completed"
  • Metadata: { req: { method, url, remoteAddress }, res: { statusCode }, responseTime }

The remoteAddress is resolved from Fastify's request.ip, which respects the trustProxy configuration.

Example Log Output

With the default configuration using Structured Transport, a GET /api/users request produces two log entries:

json
// incoming request
{
  "level": "info",
  "time": "2026-02-12T10:30:45.123Z",
  "msg": "incoming request",
  "req": { "method": "GET", "url": "/api/users", "remoteAddress": "127.0.0.1" },
  "requestId": "550e8400-e29b-41d4-a716-446655440000"
}

// request completed
{
  "level": "info",
  "time": "2026-02-12T10:30:45.135Z",
  "msg": "request completed",
  "req": { "method": "GET", "url": "/api/users", "remoteAddress": "127.0.0.1" },
  "res": { "statusCode": 200 },
  "responseTime": 12,
  "requestId": "550e8400-e29b-41d4-a716-446655440000"
}

Examples

Custom Log Levels

typescript
await app.register(fastifyLogLayer, {
  instance: log,
  autoLogging: {
    request: { logLevel: "debug" },
    response: { logLevel: "info" },
  },
});

Disable Request Logging (Response Only)

typescript
await app.register(fastifyLogLayer, {
  instance: log,
  autoLogging: {
    request: false,
  },
});

Custom Request ID

typescript
await app.register(fastifyLogLayer, {
  instance: log,
  requestId: (request) =>
    (request.headers["x-request-id"] as string) ?? crypto.randomUUID(),
});

Disable Auto-Logging

typescript
await app.register(fastifyLogLayer, {
  instance: log,
  autoLogging: false,
});

Ignore Health Check Paths

typescript
await app.register(fastifyLogLayer, {
  instance: log,
  autoLogging: {
    ignore: ["/health", "/ready", /^\/internal\//],
  },
});

Additional Context from Request

typescript
await app.register(fastifyLogLayer, {
  instance: log,
  contextFn: (request) => ({
    userAgent: request.headers["user-agent"],
    host: request.headers.host,
  }),
});

Error Handling

Errors thrown in route handlers are automatically caught and logged:

typescript
app.get("/fail", () => {
  throw new Error("Something went wrong");
  // Automatically logged with the error object
});

Setting fastify.log with loggerInstance

By default, the plugin only sets request.log (per-request). If you also want fastify.log (the app-level logger) to use LogLayer, use the createLogLayerFastifyLogger adapter:

typescript
import Fastify from "fastify";
import { LogLayer } from "loglayer";
import { createLogLayerFastifyLogger, fastifyLogLayer } from "@loglayer/fastify";

const log = new LogLayer({ transport: /* ... */ });

const app = Fastify({
  loggerInstance: createLogLayerFastifyLogger(log),
  disableRequestLogging: true, // Disable Fastify's native logging — the plugin handles it
});

// Register the plugin for request-scoped logging, auto-logging, etc.
await app.register(fastifyLogLayer, { instance: log });

TIP

Setting disableRequestLogging: true prevents Fastify's built-in request/response logging from conflicting with the plugin's auto-logging.

Using with Other Fastify Plugins

typescript
import cors from "@fastify/cors";

const app = Fastify();
await app.register(fastifyLogLayer, { instance: log });
await app.register(cors);

app.get("/", (request, reply) => {
  request.log.info("Works with other plugins!");
  reply.send("ok");
});