"Vovchansk (2024-06-02) 1513" by National Police of Ukraine (Liut Brigade) is licensed under CC BY 4.0.
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:
・Armed Forces of Ukraine・"The Come Back Alive" foundation・
Thanks for your support! 🇺🇦
NestJS-Pino
✨✨✨ Platform agnostic logger for NestJS based on Pino with REQUEST CONTEXT IN EVERY LOG ✨✨✨
This is the documentation for v5. Compatibility with earlier majors:
| nestjs-pino | NestJS | pino | pino-http | Node.js |
|---|---|---|---|---|
| v5 | 11.0.8+, 12.0.2+ | 10 | 11 | >=22.12 |
| v4 | 8, 9, 10, 11 | 7.5+, 8, 9, 10 | 6.4+, 7, 8, 9, 10, 11 | >=14 |
| v1 | [!WARNING] |
Register
LoggerModuleonly viaforRoot(...)/forRootAsync(...), and only once, in the root module. Never add the bareLoggerModuleclass to a feature module'simports, not even just to injectPinoLogger. BecauseLoggerModuleis@Global(), bothLoggerandPinoLoggerare 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 thepino-httpmiddleware again and makes every request log twice. The failure is completely silent: no compile error, no injection failure, no warning (#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:
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:
{"level":"log","pid":17580,"timestamp":1765305000999,"message":"Hello World","context":"AppService"}
NativeLogger + nativeLoggerOptions output:
{"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 wayConsoleLoggerdoes.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
paramsfield 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.NativeLoggerfollows theConsoleLoggerof 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
messagefield - 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:
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,
},
});
{"level":"log","message":"foo","context":"AppService","a":1,"b":2}
Unlike NestJS, both options are honoured on every supported NestJS version — the collecting is