# nestjs-pino

> Pino logger for NestJS

Latest version **5.2.0** (published 2026-09-14) · MIT license · 0 weekly downloads

## Install

```sh
npm install nestjs-pino
pnpm add nestjs-pino
yarn add nestjs-pino
bun add nestjs-pino
```

## Health

**Score 75/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; has provenance; recently updated; high maintenance score; high quality score.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 5.2.0 |
| Published | 2026-09-14 |
| First published | 2019-09-17 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Node | >=22.12.0 |
| Dependencies | 0 |
| Unpacked size | 204.6 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (GitHub Actions) |
| GitHub stars | 1541 |
| Author | iamolegga |
| Maintainers | iamolegga |
| Keywords | pino, nestjs, nest.js, nest, logger |

## Links

- npm: https://www.npmjs.com/package/nestjs-pino
- Repository: https://github.com/iamolegga/nestjs-pino
- Homepage: https://github.com/iamolegga/nestjs-pino#readme
- Issues: https://github.com/iamolegga/nestjs-pino/issues
- npm.io page: https://npm.io/package/nestjs-pino

## Alternatives

- [cli-color](https://npm.io/package/cli-color.md) — 3.4M weekly downloads
- [log](https://npm.io/package/log.md) — 1.3M weekly downloads
- [logstash-client](https://npm.io/package/logstash-client.md) — 4.5K weekly downloads
- [@nocobase/plugin-logger](https://npm.io/package/@nocobase/plugin-logger.md) — 2.0K weekly downloads
- [child-process-debug](https://npm.io/package/child-process-debug.md) — 695 weekly downloads

## Recent versions

- 5.2.0 (latest) — 2026-09-14
- 5.1.0-alpha.4a7f1b (alpha) — 2026-09-14
- 5.0.0-alpha.db0aa7 — 2026-09-01
- 5.1.0 — 2026-09-01
- 5.0.0 — 2026-08-31
- 5.0.0-alpha.75f515 — 2026-08-31
- 5.0.0-alpha.c5c1d6 — 2026-08-31
- 4.6.1 — 2026-03-13
- 4.6.0-alpha.42b087 — 2026-03-13
- 4.6.0 — 2026-02-17
- 4.5.0-alpha.03ab0c — 2026-02-17
- 4.5.0 — 2025-11-24
- 4.4.1-alpha.ba88ef — 2025-11-24
- 4.4.1 — 2025-09-22
- 4.4.0-alpha.c32772 — 2025-09-22
- … 1343 more at https://npm.io/package/nestjs-pino/versions

## README

<p align="center">
  <img alt="Bombed Vovchansk, Ukraine" src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/32/Vovchansk_%282024-06-02%29_1513.jpg/960px-Vovchansk_%282024-06-02%29_1513.jpg"/>
  <br />
  <small>"Vovchansk (2024-06-02) 1513" by National Police of Ukraine (Liut Brigade) is licensed under CC BY 4.0.</small>
</p>
<p align="center">
  This is Vovchansk, Ukraine, the city where the father of this library’s author was born. This is how it looks now, after the Russian invasion. If you find this library useful and would like to thank the author, please consider donating any amount via one of the following links:
  <br/>
  ・<a href="https://war.ukraine.ua/donate/">Armed Forces of Ukraine</a>・<a href="https://savelife.in.ua/en/">"The Come Back Alive" foundation</a>・
  <br/>
  Thanks for your support! 🇺🇦
</p>

<h1 align="center">NestJS-Pino</h1>

<p align="center">
  <a href="https://www.npmjs.com/package/nestjs-pino">
    <img alt="npm" src="https://img.shields.io/npm/v/nestjs-pino" />
  </a>
  <a href="https://www.npmjs.com/package/nestjs-pino">
    <img alt="npm" src="https://img.shields.io/npm/dm/nestjs-pino" />
  </a>
  <a href="https://github.com/iamolegga/nestjs-pino/actions">
    <img alt="GitHub branch checks state" src="https://badgen.net/github/checks/iamolegga/nestjs-pino/master">
  </a>
  <a href="https://qlty.sh/gh/iamolegga/projects/nestjs-pino">
    <img src="https://qlty.sh/gh/iamolegga/projects/nestjs-pino/coverage.svg" alt="Code Coverage" />
  </a>
  <a href="https://snyk.io/test/github/iamolegga/nestjs-pino">
    <img alt="Known Vulnerabilities" src="https://snyk.io/test/github/iamolegga/nestjs-pino/badge.svg" />
  </a>
  <a href="https://libraries.io/npm/nestjs-pino">
    <img alt="Libraries.io" src="https://img.shields.io/librariesio/release/npm/nestjs-pino">
  </a>
  <img alt="Dependabot" src="https://badgen.net/github/dependabot/iamolegga/nestjs-pino">
  <img alt="Supported platforms: Express & Fastify" src="https://img.shields.io/badge/platforms-Express%20%26%20Fastify-green" />
</p>

<p align="center">✨✨✨ Platform agnostic logger for NestJS based on Pino with <b>REQUEST CONTEXT IN EVERY LOG</b> ✨✨✨</p>

---

<p align="center"><b>This is the documentation for v5. Compatibility with earlier majors:</b></p>

| nestjs-pino | NestJS       | pino         | pino-http                     | Node.js  |
| ----------- | ------------ | ------------ | ----------------------------- | -------- |
| v5          | 11.0.8+, 12.0.2+ | 10       | 11                            | >=22.12  |
| [v4](https://github.com/iamolegga/nestjs-pino/tree/v4.6.1#readme) | 8, 9, 10, 11 | 7.5+, 8, 9, 10 | 6.4+, 7, 8, 9, 10, 11 | >=14     |
| [v1](https://github.com/iamolegga/nestjs-pino/tree/v1.4.0#readme) | < 8          |              |                               |          |

---

## Install

```sh
npm i nestjs-pino pino-http
```

## Example

Firstly, import module with `LoggerModule.forRoot(...)` or `LoggerModule.forRootAsync(...)` only once in root module (check out module configuration docs [below](#configuration)):

```ts
import { LoggerModule } from 'nestjs-pino';

@Module({
  imports: [LoggerModule.forRoot()],
})
class AppModule {}
```

Secondly, set up app logger:

```ts
import { Logger } from 'nestjs-pino';

const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useLogger(app.get(Logger));
```

Now you can use one of the loggers:

```ts
// NestJS standard built-in logger.
// Logs will be produced by pino internally
import { Logger } from '@nestjs/common';

export class MyService {
  private readonly logger = new Logger(MyService.name);
  foo() {
    // All logger methods have args format the same as pino, but pino methods
    // `trace` and `info` are mapped to `verbose` and `log` to satisfy
    // `LoggerService` interface of NestJS:
    this.logger.verbose({ foo: 'bar' }, 'baz %s', 'qux');
    this.logger.debug('foo %s %o', 'bar', { baz: 'qux' });
    this.logger.log('foo');
  }
}
```

Usage of the standard logger is recommended and idiomatic for NestJS. But there is one more option to use:

```ts
import { PinoLogger, InjectPinoLogger } from 'nestjs-pino';

export class MyService {
  constructor(
    private readonly logger: PinoLogger
  ) {
    // Optionally you can set context for logger in constructor or ...
    this.logger.setContext(MyService.name);
  }

  constructor(
    // ... set context via special decorator
    @InjectPinoLogger(MyService.name)
    private readonly logger: PinoLogger
  ) {}

  foo() {
    // PinoLogger has same methods as pino instance
    this.logger.trace({ foo: 'bar' }, 'baz %s', 'qux');
    this.logger.debug('foo %s %o', 'bar', { baz: 'qux' });
    this.logger.info('foo');
  }
}
```

> [!WARNING]
> Register `LoggerModule` **only** via `forRoot(...)` / `forRootAsync(...)`, and
> **only once**, in the root module. Never add the bare `LoggerModule` class to a
> feature module's `imports`, not even just to inject `PinoLogger`. Because
> `LoggerModule` is `@Global()`, both `Logger` and `PinoLogger` are already
> available everywhere after the single root registration, so you never need to
> re-import it. A bare import instantiates the module a second time, which
> registers the `pino-http` middleware **again** and makes every request log
> **twice**. The failure is completely silent: no compile error, no injection
> failure, no warning
> ([#3074](https://github.com/iamolegga/nestjs-pino/issues/3074)).

### Drop-in replacement: NativeLogger

`NativeLogger` is a **drop-in replacement** for NestJS's built-in `ConsoleLogger`. It produces **identical JSON output** — same field names, same argument handling, same error format — but powered by pino with request context in every log.

If you're already using `ConsoleLogger` with `{ json: true }` and want to switch to pino without changing any of your logging code, this is for you:

```ts
import { NativeLogger, nativeLoggerOptions } from 'nestjs-pino';

@Module({
  imports: [LoggerModule.forRoot({ pinoHttp: nativeLoggerOptions })],
})
class AppModule {}

const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useLogger(app.get(NativeLogger));
```

That's it. Your existing `new Logger(MyService.name)` calls throughout the codebase will work exactly as before — same `message`, `context`, `level`, `timestamp`, `pid`, and `stack` fields — but now with pino's performance and automatic request context binding.

**ConsoleLogger JSON output:**
```json
{"level":"log","pid":17580,"timestamp":1765305000999,"message":"Hello World","context":"AppService"}
```

**NativeLogger + nativeLoggerOptions output:**
```json
{"level":"log","pid":17580,"timestamp":1765305000999,"message":"Hello World","context":"AppService"}
```

#### How it differs from `Logger`

- **`Logger`** (pino-native): treats extra arguments as pino interpolation values. `this.logger.log('foo %s', 'bar')` → `{"msg":"foo bar"}`
- **`NativeLogger`** (NestJS-native): parses arguments the way `ConsoleLogger` does. `this.logger.log('foo', 'bar', 'Ctx')` → two logs, `{"message":"foo","context":"Ctx"}` and `{"message":"bar","context":"Ctx"}`

#### What matches ConsoleLogger exactly

- Argument parsing: last string = context, rest = separate log entries
- Structured params: on NestJS 12, plain objects after the message are merged into a single `params` field on one entry (`ConsoleLoggerOptions.structuredParams`, on by default) — `this.logger.log('foo', { a: 1 }, { b: 2 })` → `{"message":"foo","params":{"a":1,"b":2}}`. On NestJS 11 each of them is a separate entry. `NativeLogger` follows the `ConsoleLogger` of the NestJS version you actually have, so out of the box there is nothing to configure — see below to override it
- Error handling: `this.logger.error('msg', stackTrace, 'Ctx')` → `{"message":"msg","stack":"Error: ...","context":"Ctx"}`
- Error objects: `this.logger.log(new Error('oops'))` → full error+stack as message string
- Exception handler: thrown errors logged with full stack in `message` field
- Object messages: `this.logger.log({ foo: 'bar' })` → `{"message":{"foo":"bar"}}`
- Field names (with `nativeLoggerOptions`): `message`, `timestamp`, `pid`, `level`, `context`, `stack`

#### Keeping your ConsoleLogger options

If your application configures `ConsoleLogger` rather than relying on its
defaults, pass the same values to keep the output identical after the switch:

```ts
LoggerModule.forRoot({
  pinoHttp: nativeLoggerOptions,
  nativeLogger: {
    // NestJS 12 default is `true`, NestJS 11 has no such option and behaves
    // as `false`. Omit it to follow the ConsoleLogger you actually have.
    structuredParams: true,
    // Spread params into the root of the record instead of nesting them
    // under `params`. NestJS default is `false`.
    flattenParams: true,
  },
});
```

```json
{"level":"log","message":"foo","context":"AppService","a":1,"b":2}
```

Unlike NestJS, both options are honoured on every supported NestJS version —
the collecting is implemented by this library, and the installed `ConsoleLogger`
only decides the default of `structuredParams`. That also lets you switch to the
NestJS 12 output while still on NestJS 11, so upgrading NestJS later does not
change your logs.

A flattened param named `context` or `stack` is used as such when the call does
not set them itself, and loses to the explicit form when it does — so
`log('msg', { context: 'A' })` logs the context `A`, while
`log('msg', { context: 'A' }, 'B')` logs `B`.

Collisions with the fields pino adds — the level, the timestamp, the message
key, the base bindings — are not resolved at all: pino does not deduplicate keys
either, and their names depend on your pino options, so naming a param `level`
is your call to make.

Output:

```json
// Logs by app itself
{"level":30,"time":1629823318326,"pid":14727,"hostname":"my-host","context":"NestFactory","msg":"Starting Nest application..."}
{"level":30,"time":1629823318326,"pid":14727,"hostname":"my-host","context":"InstanceLoader","msg":"LoggerModule dependencies initialized"}
{"level":30,"time":1629823318327,"pid":14727,"hostname":"my-host","context":"InstanceLoader","msg":"AppModule dependencies initialized"}
{"level":30,"time":1629823318327,"pid":14727,"hostname":"my-host","context":"RoutesResolver","msg":"AppController {/}:"}
{"level":30,"time":1629823318327,"pid":14727,"hostname":"my-host","context":"RouterExplorer","msg":"Mapped {/, GET} route"}
{"level":30,"time":1629823318327,"pid":14727,"hostname":"my-host","context":"NestApplication","msg":"Nest application successfully started"}

// Logs by injected Logger and PinoLogger in Services/Controllers. Every log
// has it's request data and unique `req.id` (by default id is unique per
// process, but you can set function to generate it from request context and
// for example pass here incoming `X-Request-ID` header or generate UUID)
{"level":10,"time":1629823792023,"pid":15067,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","query":{},"params":{"0":""},"headers":{"host":"localhost:3000","user-agent":"curl/7.64.1","accept":"*/*"},"remoteAddress":"::1","remotePort":63822},"context":"MyService","foo":"bar","msg":"baz qux"}
{"level":20,"time":1629823792023,"pid":15067,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","query":{},"params":{"0":""},"headers":{"host":"localhost:3000","user-agent":"curl/7.64.1","accept":"*/*"},"remoteAddress":"::1","remotePort":63822},"context":"MyService","msg":"foo bar {\"baz\":\"qux\"}"}
{"level":30,"time":1629823792023,"pid":15067,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","query":{},"params":{"0":""},"headers":{"host":"localhost:3000","user-agent":"curl/7.64.1","accept":"*/*"},"remoteAddress":"::1","remotePort":63822},"context":"MyService","msg":"foo"}

// Automatic logs of every request/response
{"level":30,"time":1629823792029,"pid":15067,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","query":{},"params":{"0":""},"headers":{"host":"localhost:3000","user-agent":"curl/7.64.1","accept":"*/*"},"remoteAddress":"::1","remotePort":63822},"res":{"statusCode":200,"headers":{"x-powered-by":"Express","content-type":"text/html; charset=utf-8","content-length":"12","etag":"W/\"c-Lve95gjOVATpfV8EL5X4nxwjKHE\""}},"responseTime":7,"msg":"request completed"}
```

## Comparison with others

There are other Nestjs loggers. Key purposes of this module are:

- to be idiomatic NestJS logger
- to log in JSON format (thanks to [pino](https://getpino.io/#/) - [super fast logger](https://github.com/pinojs/pino/blob/master/docs/benchmarks.md)) [why](https://www.loggly.com/use-cases/json-logging-best-practices/) [you](https://hackernoon.com/log-everything-as-json-hmq32ax) [should](https://blog.treasuredata.com/blog/2012/04/26/log-everything-as-json/) [use](https://stackify.com/what-is-structured-logging-and-why-developers-need-it/) [JSON](https://softwareengineering.stackexchange.com/a/170528)
- to log every request/response automatically (thanks to [pino-http](https://github.com/pinojs/pino-http#pino-http))
- to bind request data to the logs automatically from any service on any application layer without passing request context (thanks to [AsyncLocalStorage](https://nodejs.org/api/async_context.html#async_context_class_asynclocalstorage))
- to have another alternative logger with same API as `pino` instance (`PinoLogger`) for experienced `pino` users to make more comfortable usage.

| Logger             | Nest App logger | Logger service | Auto-bind request data to logs |
| ------------------ | :-------------: | :------------: | :---------------------------: |
| nest-winston       |        +        |       +        |               -               |
| nestjs-pino-logger |        +        |       +        |               -               |
| **nestjs-pino**    |        +        |       +        |               +               |

## Configuration

### Zero configuration

Just import `LoggerModule` to your module:

```ts
import { LoggerModule } from 'nestjs-pino';

@Module({
  imports: [LoggerModule.forRoot()],
  ...
})
class MyModule {}
```

### Configuration params

The following interface is using for the configuration:

```ts
interface Params<
  IM = IncomingMessage,
  SR = ServerResponse,
  CustomLevels extends string = never,
> {
  /**
   * Optional parameters for `pino-http` module
   * @see https://github.com/pinojs/pino-http#api
   */
  pinoHttp?:
    | pinoHttp.Options<IM, SR, CustomLevels>
    | DestinationStream
    | [pinoHttp.Options<IM, SR, CustomLevels>, DestinationStream];

  /**
   * Optional parameter for routing. It should implement interface of
   * parameters of NestJS built-in `MiddlewareConfigProxy['forRoutes']`.
   * @see https://docs.nestjs.com/middleware#applying-middleware
   * It can be used for both disabling automatic req/res logs (see above) and
   * removing request context from following logs. It works for all requests by
   * default. If you only need to turn off the automatic request/response
   * logging for some specific (or all) routes but keep request context for app
   * logs use `pinoHttp.autoLogging` field.
   */
  forRoutes?: Parameters<MiddlewareConfigProxy['forRoutes']>;

  /**
   * Optional parameter for routing. It should implement interface of
   * parameters of NestJS built-in `MiddlewareConfigProxy['exclude']`.
   * @see https://docs.nestjs.com/middleware#applying-middleware
   * It can be used for both disabling automatic req/res logs (see above) and
   * removing request context from following logs. It works for all requests by
   * default. If you only need to turn off the automatic request/response
   * logging for some specific (or all) routes but keep request context for app
   * logs use `pinoHttp.autoLogging` field.
   */
  exclude?: Parameters<MiddlewareConfigProxy['exclude']>;

  /**
   * Optional parameter to skip pino configuration in case you are using
   * FastifyAdapter, and already configure logger in adapter's config. The Pros
   * and cons of this approach are described in the FAQ section of the
   * documentation:
   * @see https://github.com/iamolegga/nestjs-pino#faq.
   */
  useExisting?: true;

  /**
   * Optional parameter to change property name `context` in resulted logs,
   * so logs will be like:
   * {"level":30, ... "RENAME_CONTEXT_VALUE_HERE":"AppController" }
   */
  renameContext?: string;

  /**
   * Optional parameter to also assign the response logger during calls to
   * `PinoLogger.assign`. By default, `assign` does not impact response logs
   * (e.g.`Request completed`).
   */
  assignResponse?: boolean;

  /**
   * Optional parameters for automatic logging of microservice messages, and
   * for a logging context in message handlers. See the "Microservices"
   * section below. Pass `true` for the defaults.
   */
  microservice?: boolean | MicroserviceParams<CustomLevels>;
}
```

#### Typing the request, response and custom levels

All three type parameters are optional and default to what `pino-http` itself
defaults to, so `Params` keeps working unparameterised. Pass them when you want
your platform's request/response types inside the `pinoHttp` callbacks:

```ts
import type { Request, Response } from 'express';
import { LoggerModule, Params } from 'nestjs-pino';

const params: Params<Request, Response> = {
  pinoHttp: {
    // `req` is an express Request here, not a bare IncomingMessage
    genReqId: (req) => req.headers['x-correlation-id'] ?? randomUUID(),
    serializers: { req: (req: Request) => ({ id: req.id, url: req.url }) },
  },
};

LoggerModule.forRoot(params);
```

The third parameter carries pino's `customLevels`:

```ts
type CustomLevels = 'audit';

const params: Params<Request, Response, CustomLevels> = {
  pinoHttp: {
    customLevels: { audit: 35 },
    useLevel: 'audit',
  },
};
```

Custom levels are reachable through the underlying pino instance —
`pinoLogger.logger.audit('...')`, not `pinoLogger.audit('...')`. `PinoLogger` is
instantiated by the NestJS DI container, which cannot infer a type argument, so
the level methods cannot be synthesised onto the class itself.


### Synchronous configuration

Use `LoggerModule.forRoot` method with argument of [Params interface](#configuration-params):

```ts
import { LoggerModule } from 'nestjs-pino';

@Module({
  imports: [
    LoggerModule.forRoot({
      pinoHttp: [
        {
          name: 'add some name to every JSON line',
          level: process.env.NODE_ENV !== 'production' ? 'debug' : 'info',
          // install 'pino-pretty' package in order to use the following option
          transport: process.env.NODE_ENV !== 'production'
            ? { target: 'pino-pretty' }
            : undefined,


          // and all the other fields of:
          // - https://github.com/pinojs/pino-http#api
          // - https://github.com/pinojs/pino/blob/HEAD/docs/api.md#options-object


        },
        someWritableStream
      ],
      forRoutes: [MyController],
      exclude: [{ method: RequestMethod.ALL, path: 'check' }]
    })
  ],
  ...
})
class MyModule {}
```

### Asynchronous configuration

With `LoggerModule.forRootAsync` you can, for example, import your `ConfigModule` and inject `ConfigService` to use it in `useFactory` method.

`useFactory` should return object with [Params interface](#configuration-params) or undefined

Here's an example:

```ts
import { LoggerModule } from 'nestjs-pino';

@Injectable()
class ConfigService {
  public readonly level = 'debug';
}

@Module({
  providers: [ConfigService],
  exports: [ConfigService]
})
class ConfigModule {}

@Module({
  imports: [
    LoggerModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (config: ConfigService) => {
        await somePromise();
        return {
          pinoHttp: { level: config.level },
        };
      }
    })
  ],
  ...
})
class TestModule {}
```

### Asynchronous logging

> In essence, asynchronous logging enables even faster performance by `pino`.

Please, read [pino asynchronous mode docs](https://github.com/pinojs/pino/blob/master/docs/asynchronous.md) first. There is a possibility of the most recently buffered log messages being lost in case of a system failure, e.g. a power cut.

If you know what you're doing, you can enable it like so:

```ts
import pino from 'pino';
import { LoggerModule } from 'nestjs-pino';

@Module({
  imports: [
    LoggerModule.forRoot({
      pinoHttp: {
        stream: pino.destination({
          dest: './my-file', // omit for stdout
          minLength: 4096, // Buffer before writing
          sync: false, // Asynchronous logging
        }),
      },
    }),
  ],
  ...
})
class MyModule {}
```

See [pino.destination](https://github.com/pinojs/pino/blob/master/docs/api.md#pino-destination)

## Testing a class that uses @InjectPinoLogger

This package exposes a `getLoggerToken()` function that returns a prepared injection token based on the provided context.
Using this token, you can provide a mock implementation of the logger using any of the standard custom provider techniques, including `useClass`, `useValue` and `useFactory`.

```ts
  const module: TestingModule = await Test.createTestingModule({
    providers: [
      MyService,
      {
        provide: getLoggerToken(MyService.name),
        useValue: mockLogger,
      },
    ],
  }).compile();
```

## Logger/PinoLogger class extension

`Logger` and `PinoLogger` classes can be extended.

```ts
// logger.service.ts
import { Logger, PinoLogger, Params, PARAMS_PROVIDER_TOKEN } from 'nestjs-pino';

@Injectable()
class LoggerService extends Logger {
  constructor(
    logger: PinoLogger,
    @Inject(PARAMS_PROVIDER_TOKEN) params: Params
  ) {
    ...
  }
  // extended method
  myMethod(): any {}
}

import { PinoLogger, Params, PARAMS_PROVIDER_TOKEN } from 'nestjs-pino';

@Injectable()
class LoggerService extends PinoLogger {
  constructor(
    @Inject(PARAMS_PROVIDER_TOKEN) params: Params
  ) {
    // ...
  }
  // extended method
  myMethod(): any {}
}


// logger.module.ts
@Module({
  providers: [LoggerService],
  exports: [LoggerService],
  imports: [LoggerModule.forRoot()],
})
class LoggerModule {}
```

## Notes on `Logger` injection in constructor

Since logger substitution has appeared in NestJS@8 the main purpose of `Logger` class is to be registered via `app.useLogger(app.get(Logger))`. But that requires some internal breaking change, because with such usage NestJS pass logger's context as the last optional argument in logging function. So in current version `Logger`'s methods accept context as a last argument.

With such change it's not possible to detect if method was called by app internaly and the last argument is context or `Logger` was injected in some service via `constructor(private logger: Logger) {}` and the last argument is interpolation value for example.

## Assign extra fields for future calls

You can enrich logs before calling log methods. It's possible by using `assign` method of `PinoLogger` instance. As `Logger` class is used only for NestJS built-in `Logger` substitution via `app.useLogger(...)` this feature is only limited to `PinoLogger` class. Example:

```ts

@Controller('/')
class TestController {
  constructor(
    private readonly logger: PinoLogger,
    private readonly service: MyService,
  ) {}

  @Get()
  get() {
    // assign extra fields in one place...
    this.logger.assign({ userID: '42' });
    return this.service.test();
  }
}

@Injectable()
class MyService {
  private readonly logger = new Logger(MyService.name);

  test() {
    // ...and it will be logged in another one
    this.logger.log('hello world');
  }
}
```

By default, this does not extend `Request completed` logs. Set the `assignResponse` parameter to `true` to also enrich response logs automatically emitted by `pino-http`.

## Microservices

Message handlers get the same treatment HTTP requests do: every log made while
handling a message carries the message's context, `assign` works, and a line per
message is logged automatically. Add the `microservice` parameter:

```ts
LoggerModule.forRoot({
  pinoHttp: { level: 'debug' },  // as before: level, transport, redact, ...
  microservice: true,            // that's all
});
```

Nothing changes in `main.ts` — the module registers a [pre-request
hook](https://docs.nestjs.com/microservices/pre-request-hooks) by itself:

```ts
const app = await NestFactory.createMicroservice(AppModule, {
  transport: Transport.RMQ,
  options: { urls: ['amqp://localhost'], queue: 'users' },
  bufferLogs: true,
});
app.useLogger(app.get(Logger));
await app.listen();
```

```ts
@Controller()
export class UserController {
  constructor(private readonly logger: PinoLogger) {}

  @EventPattern('user.created')
  async handle(@Payload() data: UserCreated) {
    this.logger.assign({ userId: data.id });
    this.logger.info('provisioning');
    await this.provision(data);
  }
}
```

```json
{"level":30,"reqId":1,"rpc":{"type":"event","pattern":"user.created","transport":"rmq","controller":"UserController","handler":"handle"},"userId":"u_42","msg":"provisioning"}
{"level":30,"reqId":1,"rpc":{"type":"event","pattern":"user.created","transport":"rmq","controller":"UserController","handler":"handle"},"userId":"u_42","responseTime":37,"msg":"event completed"}
```

The automatic messages are `message completed`/`message errored` for a
`@MessagePattern`, where the caller is waiting for a reply, and `event
completed`/`event errored` for an `@EventPattern`, where nobody is. As in
`pino-http`, nothing is logged on arrival unless `customReceivedMessage` or
`customReceivedObject` says so. Unlike `pino-http`, a failed message defaults to
`error` rather than to `useLevel`, so that lowering `useLevel` to quiet down
events does not also hide failures. Note that the hook sees the error before
any `@Catch()` filter, so an `RpcException` thrown on purpose and mapped to a
client response arrives here too — reach for `customLogLevel` if that should
not be an `error`. No counterpart of `LoggerErrorInterceptor` is needed: the
hook receives the error directly.

**Requirements.** Pre-request hooks arrived in NestJS 12, and 12.0.2 is the
first release on which a hook leaves a handler's plain return value alone
([nestjs/nest#17644](https://github.com/nestjs/nest/pull/17644)) — hence the
lower bound of the peer range. On NestJS 11 the parameter is ignored with a
warning and everything else keeps working.

### Hybrid applications

A hybrid application must be connected with `inheritAppConfig`, because without
it NestJS gives the microservice an `ApplicationConfig` of its own that
dependency injection cannot reach:

```ts
app.connectMicroservice(
  { transport: Transport.RMQ, options: { urls: ['amqp://localhost'], queue: 'users' } },
  { inheritAppConfig: true },
);
```

If that does not suit you, wire it up by hand instead:

```ts
import { registerMicroserviceLogging } from 'nestjs-pino';

const ms = app.connectMicroservice(options, { deferInitialization: true });
registerMicroserviceLogging(ms);
```

### Microservice configuration params

Shaped after `pino-http`'s: every parameter that does not need the request or
the response keeps its name and meaning, and the ones that do take an
`ExecutionContext` instead. Everything pino itself is configured with — level,
transport, redact, serializers — stays in `pinoHttp`, which builds the one
logger both halves of the application share.

```ts
interface MicroserviceParams<CustomLevels extends string = never> {
  /**
   * Set to `false` to stop logging a line per message. The logging *context*
   * is still established either way, so `PinoLogger.assign` and the inherited
   * fields keep working.
   * @default true
   */
  autoLogging?:
    | boolean
    | {
        /** Skip the automatic logs for the messages this returns `true` for. */
        ignore?: (context: ExecutionContext) => boolean;
      };

  /**
   * Level of the `received` and `completed` logs. Errors default to `error`
   * regardless of this — unlike `pino-http`, where a failed request is logged
   * at `useLevel` too — so that lowering this to quiet down events does not
   * also hide failures. Use `customLogLevel` to change the error level.
   * Cannot be combined with `customLogLevel`.
   * @default 'info'
   */
  useLevel?: LevelWithSilent | CustomLevels;

  /**
   * Decides the level of every automatic log, including the error one. Note
   * that the hook runs before any `@Catch()` filter, so `error` is set for
   * exceptions the application goes on to handle itself, an `RpcException`
   * thrown on purpose included.
   */
  customLogLevel?: (
    context: ExecutionContext,
    error?: Error,
  ) => LevelWithSilent | CustomLevels;

  /**
   * Generates the value bound as `reqId`. Defaults to an incrementing counter,
   * as in `pino-http`; a correlation id off the transport context is usually a
   * better choice.
   */
  genReqId?: (context: ExecutionContext) => ReqId;

  /**
   * Setting this — or `customReceivedObject` — is what enables the log on
   * arrival. There is no default text, so nothing is logged on arrival unless
   * asked for, exactly as in `pino-http`.
   */
  customReceivedMessage?: (context: ExecutionContext) => string;

  /** @default `'message completed'`, or `'event completed'` for an `@EventPattern` */
  customSuccessMessage?: (
    context: ExecutionContext,
    result: unknown,
    responseTime: number,
  ) => string;

  /** @default `'message errored'`, or `'event errored'` for an `@EventPattern` */
  customErrorMessage?: (
    context: ExecutionContext,
    error: Error,
    responseTime: number,
  ) => string;

  /**
   * Fields of the log on arrival, which it enables the same way
   * `customReceivedMessage` does. There is no default object, so what it
   * returns is logged as is.
   */
  customReceivedObject?: (context: ExecutionContext) => object;

  /**
   * Replaces the fields of the `completed` log. `value` is what would be logged
   * otherwise — `responseTime` under its configured key — and is not merged
   * back in, so spread it if the extra fields should come on top.
   */
  customSuccessObject?: (
    context: ExecutionContext,
    result: unknown,
    value: object,
  ) => object;

  /**
   * Replaces the fields of the `errored` log. `value` holds `err` and
   * `responseTime` under their configured keys and, as with
   * `customSuccessObject`, is not merged back in.
   */
  customErrorObject?: (
    context: ExecutionContext,
    error: Error,
    value: object,
  ) => object;

  /** Extra fields bound to every log made while handling the message. */
  customProps?: (context: ExecutionContext) => object;

  /**
   * Renames the keys this adds to the log record. The `rpc` key is the
   * microservice counterpart of `pino-http`'s `req`/`res`.
   */
  customAttributeKeys?: {
    /** @default 'rpc' */
    rpc?: string;
    /** @default 'err' */
    err?: string;
    /** @default 'reqId' */
    reqId?: string;
    /** @default 'responseTime' */
    responseTime?: string;
  };

  /**
   * Bind only `reqId`, leaving the `rpc` object out of the logs made while
   * handling the message. Mirrors `pino-http`'s `quietReqLogger`.
   */
  quietRpcLogger?: boolean;

  /**
   * Leave the `rpc` object out of the `completed`/`errored` log, which repeats
   * what the surrounding logs already carry. Mirrors `quietResLogger`.
   */
  quietResLogger?: boolean;

  /**
   * Log the message payload as `rpc.payload`. Off by default: payloads are
   * unbounded in size and routinely carry personal data. `redact` and a
   * `serializers.rpc` entry in `pinoHttp` apply to it as to anything else.
   * @default false
   */
  includePayload?: boolean;
}
```

Use `getRpcInfo(ctx)` inside any of these to reach the pattern, the transport
and the kind of handler. It returns what is logged under `rpc`, and never
throws:

```ts
import { getRpcInfo } from 'nestjs-pino';

LoggerModule.forRoot({
  microservice: {
    // events are noisy, replies are not
    customLogLevel: (ctx, err) =>
      err ? 'error' : getRpcInfo(ctx).type === 'event' ? 'debug' : 'info',

    autoLogging: { ignore: (ctx) => getRpcInfo(ctx).handler === 'healthcheck' },

    genReqId: (ctx) =>
      ctx.switchToRpc().getContext<RmqContext>().getMessage()
         .properties.correlationId ?? randomUUID(),
  },
});
```

## Logging context anywhere else

Queue processors, cron jobs, CLI commands, standalone scripts, tests and
consumers wired up outside of NestJS have no request pipeline to hook into. Use
`runInContext` to open a logging context by hand, and `assign` and inherited
fields work there too:

```ts
@Processor('emails')
export class EmailProcessor extends WorkerHost {
  constructor(private readonly logger: PinoLogger) { super(); }

  async process(job: Job) {
    return this.logger.runInContext(
      async () => {
        this.logger.assign({ jobId: job.id, attempt: job.attemptsMade });
        this.logger.info('sending');
        await this.send(job.data);
      },
      { bindings: { queue: 'emails' } },
    );
  }
}
```

It returns whatever the function returns, and an async function keeps the
context across every `await` inside it.

Pass `inherit: true` to start from the surrounding context instead of from the
root logger — useful for `@OnEvent` handlers and for work started during a
request but finished after it. The store is a new one either way, so `assign`
inside never reaches the log that closes the surrounding request:

```ts
@OnEvent('order.paid')
async onPaid(e: OrderPaid) {
  await this.logger.runInContext(
    async () => {
      this.logger.assign({ orderId: e.id });
      await this.fulfil(e);
    },
    { inherit: true },
  );
}
```

## Change pino params at runtime

Pino root instance with passed via module registration params creates a separate child logger for every request. This root logger params can be changed at runtime via `PinoLogger.root` property which is the pointer to logger instance. Every log in the application descends from it — request logs, microservice message logs and logs made outside of any context alike — so a change reaches all of them. It is available in a microservice or standalone application too, where there is no HTTP middleware. Example:

```ts
@Controller('/')
class TestController {
  @Post('/change-loggin-level')
  setLevel() {
    PinoLogger.root.level = 'info';
    return null;
  }
}
```

## Expose stack trace and error class in `err` property

By default, `pino-http` exposes `err` property with a stack trace and error details, however, this `err` property contains default error details, which do not tell anything about actual error. To expose actual error details you need you to use a NestJS interceptor which captures exceptions and assigns them to the response object `err` property which is later processed by pino-http:   

```typescript
import { LoggerErrorInterceptor } from 'nestjs-pino';

const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggerErrorInterceptor());
```

## Migration

### v5

- **Requirements changed.** NestJS `11.0.8+` or `12.0.2+` (11.0.8 is where
  NestJS started preserving the `{/...}` route syntax the default middleware
  route relies on; 12.0.2 is where a microservice pre-request hook stopped
  corrupting a handler's plain return value), `pino@10`, `pino-http@11`,
  Node.js `>=22.12`. Support for NestJS
  8-10, pino 7-9 and pino-http 6-10 is dropped. `@nestjs/core` is now a peer
  dependency alongside `@nestjs/common`; every NestJS application already has
  it installed.
- **The package now ships from `dist/` behind an `exports` map.** The public
  entry point is unchanged, but deep imports such as
  `nestjs-pino/PinoLogger` no longer resolve — import from `nestjs-pino`
  instead.

Everything else is backwards compatible. In particular the warning NestJS used
to print on startup when a global prefix was set
(`Unsupported route path: "/v1/*"`) is gone, and requests hitting the prefix
root itself are now logged as well.

Paths excluded from the global prefix with
`app.setGlobalPrefix(prefix, { exclude })` are logged too. On Fastify this needs
`@nestjs/platform-fastify@11.2.0` or newer: until then the adapter prepended the
global prefix to every middleware path that did not already start with it, which
put excluded paths out of reach. Express has no such requirement.

### v1

- All parameters of v.0 are moved to `pinoHttp` property (except `useExisting`).
- `useExisting` now accept only `true` because you should already know if you want to use preconfigured fastify adapter's logger (and set `true`) or not (and just not define this field).

### v2

#### Logger substitution

A new more convenient way to inject a custom logger that implements `LoggerService` has appeared in recent versions of NestJS (mind the `bufferLogs` field, it will force NestJS to wait for logger to be ready instead of using built-in logger on start):

```ts
// main.ts
import { Logger } from 'nestjs-pino';
// ...
  const app = await NestFactory.create(AppModule, { bufferLogs: true });
  app.useLogger(app.get(Logger));
// ...
```

Note that for [standalone applications](https://docs.nestjs.com/standalone-applications), buffering has to be [flushed using app.flushLogs()](https://github.com/nestjs/nest/blob/24e6c821a0859448646fd88831f20e4c5ae50980/packages/core/nest-application-context.ts#L136) manually after custom logger is ready to be used by NestJS (refer to [this issue](https://github.com/iamolegga/nestjs-pino/issues/553) for more details):

```ts
// main.ts
import { Logger } from 'nestjs-pino';

// ... 
  const app = await NestFactory.createApplicationContext(AppModule, { bufferLogs: true });
  app.useLogger(app.get(Logger));
  app.flushLogs();
// ...
```

In all the other places you can use built-in `Logger`:

```ts
// my-service.ts
import { Logger } from '@nestjs/common';
class MyService {
  private readonly logger = new Logger(MyService.name);
}
```

To quote the official docs:

> If we supply a custom logger via `app.useLogger()`, it will actually be used by Nest internally. That means that our code remains implementation agnostic, while we can easily substitute the default logger for our custom one by calling `app.useLogger()`.
>
> That way if we follow the steps from the previous section and call `app.useLogger(app.get(MyLogger))`, the following calls to `this.logger.log()` from `MyService` would result in calls to method `log` from `MyLogger` instance.

---

**This is recommended to update all your existing `Logger` injections from `nestjs-pino` to `@nestjs/common`. And inject it only in your `main.ts` file as shown above. Support of injection of `Logger` (don't confuse with `PinoLogger`) from `nestjs-pino` directly in class constructors is dropped.**

---

Since logger substitution has appeared the main purpose of `Logger` class is to be registered via `app.useLogger(app.get(Logger))`. But that requires some internal breaking change, because with such usage NestJS pass logger's context as the last optional argument in logging function. So in current version `Logger`'s methods accept context as the last argument.

With such change it's not possible to detect if method was called by app internaly and the last argument is context or `Logger` was injected in some service via `constructor(private logger: Logger) {}` and the last argument is interpolation value for example. That's why logging with such injected class still works, but only for 1 argument.

#### NestJS LoggerService interface breaking change

In NestJS@8 all logging methods of built-in `LoggerService` now accept the same arguments without second `context` argument (which is set via injection, see above), for example: `log(message: any, ...optionalParams: any[]): any;`. That makes usage of built-in logger more convenient and compatible with `pino`'s logging methods. So this is a breaking change in NestJS, and you should be aware of it.

In NestJS <= 7 and `nestjs-pino@1` when you call `this.logger.log('foo', 'bar');` there would be such log: `{..."context":"bar","msg":"foo"}` (second argument goes to `context` field by desing). In NestJS 8 and `nestjs-pino@2` (with proper injection that shown above) same call will result in `{..."context":"MyService","msg":"foo"}`, so `context` is passed via injection, but second argument disappear from log, because now it treats as interpolation value and there should be placeholder for it in `message` argument. So if you want to get both `foo` and `bar` in log the right way to do this is: `this.logger.log('foo %s', 'bar');`. More info can be found in [pino docs](https://getpino.io/#/docs/api?id=logging-method-parameters).

## FAQ

**Q**: _How to disable automatic request/response logs?_

**A**: check out [autoLogging field of pino-http](https://github.com/pinojs/pino-http#pinohttpopts-stream) that are set in `pinoHttp` field of `Params`

---

**Q**: _How to pass `X-Request-ID` header or generate UUID for `req.id` field of log?_

**A**: check out [genReqId field of pino-http](https://github.com/pinojs/pino-http#pinohttpopts-stream) that are set in `pinoHttp` field of `Params`

---

**Q**: _How does it work?_

**A**: It uses [pino-http](https://github.com/pinojs/pino-http) under hood, so every request has it's own [child-logger](https://github.com/pinojs/pino/blob/master/docs/child-loggers.md), and with help of [AsyncLocalStorage](https://nodejs.org/api/async_context.html#async_context_class_asynclocalstorage) `Logger` and `PinoLogger` can get it while calling own methods. So your logs can be grouped by `req.id`.

---

**Q**: _Why use [AsyncLocalStorage](https://nodejs.org/api/async_context.html#async_context_class_asynclocalstorage) instead of [REQUEST scope](https://docs.nestjs.com/fundamentals/injection-scopes#per-request-injection)?_

**A**: [REQUEST scope](https://docs.nestjs.com/fundamentals/injection-scopes#per-request-injection) can have [perfomance issues](https://docs.nestjs.com/fundamentals/injection-scopes#performance). TL;DR: it will have to create an instance of the class (that injects `Logger`) on each request, and that will slow down your response times.

---

**Q**: _I'm using old nodejs version, will it work for me?_

**A**: Please check out [history of this feature](https://nodejs.org/api/async_context.html#async_context_class_asynclocalstorage).

---

**Q**: _What about `pino` built-in methods/levels?_

**A**: Pino built-in methods names are not fully compatible with NestJS built-in `LoggerService` methods names, and there is an option which logger you use. Here is methods mapping:

| `pino` method | `PinoLogger` method | NestJS built-in `Logger` method |
| ------------- | ------------------- | --------------------------------|
| **trace**     | **trace**           | **verbose**                     |
| debug         | debug               | debug                           |
| **info**      | **info**            | **log**                         |
| warn          | warn                | warn                            |
| error         | error               | error                           |
| fatal         | fatal               | fatal (since nestjs@10.2)       |

---

**Q**: _Fastify already includes `pino`, and I want to configure it on `Adapter` level, and use this config for logger_

**A**: You can do it by providing `useExisting: true`. But there is one caveat:

Fastify creates logger with your config per every request. And this logger is used by `Logger`/`PinoLogger` services inside that context underhood.

But Nest Application has another contexts of execution, for example [lifecycle events](https://docs.nestjs.com/fundamentals/lifecycle-events), where you still may want to use logger. For that `Logger`/`PinoLogger` services use separate `pino` instance with config, that provided via `forRoot`/`forRootAsync` methods.

So, when you want to configure `pino` via `FastifyAdapter` there is no way to get back this config from fastify and pass it to that _out of context_ logger.

And if you will not pass config via `forRoot`/`forRootAsync` _out of context_ logger will be instantiated with default params. So if you want to configure it with the same options for consistency you have to provide the same config to `LoggerModule` configuration too. But if you already provide it to `LoggerModule` configuration you can drop `useExisting` field from config and drop logger configuration on `FastifyAdapter`, and it will work without code duplication.

So this property (`useExisting: true`) is not recommended, and can be useful only for cases when:

- this logger is not using for lifecycle events and application level logging in NestJS apps based on fastify
- `pino` is using with default params in NestJS apps based on fastify

All the other cases are lead to either code duplication or unexpected behavior.

---

<h2 align="center">Do you use this library?<br/>Don't be shy to give it a star! ★</h2>

<h3 align="center">Also if you are into NestJS you might be interested in one of my <a href="https://github.com/iamolegga#nestjs">other NestJS libs</a>.</h3>

---
_Source: https://npm.io/package/nestjs-pino · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
