Gathering detailed insights and metrics for nestjs-pino
Gathering detailed insights and metrics for nestjs-pino
Gathering detailed insights and metrics for nestjs-pino
Gathering detailed insights and metrics for nestjs-pino
@nestjs-mod/pino
Pino logger for NestJS-mod (Wrapper for https://www.npmjs.com/package/nestjs-pino)
nestjs-pino-stackdriver
Configured to be stackdriver compliant.
nestjs-pino-batch-http-transport
A NestJS module providing a Pino transport to batch logs and send them via HTTP, compatible with NestJS v11.
nestjs-pino-fastify
Pino logger for NestJS for Fastify
Platform agnostic logger for NestJS based on Pino with REQUEST CONTEXT IN EVERY LOG
npm install nestjs-pino
Typescript
Module System
Min. Node Version
Node Version
NPM Version
4.4.0: allow publishing source map files
Updated on Mar 19, 2025
4.3.1: add pino and rxjs as peer dependencies
Updated on Feb 22, 2025
4.3.0: nestjs@11 support
Updated on Jan 21, 2025
4.2.0: add `assignResponse` parameter
Updated on Dec 23, 2024
4.1.0: add pino-http@10 support
Updated on May 16, 2024
4.0.0: add pino-http@9 support, drop nodejs@16 support
Updated on Jan 08, 2024
TypeScript (93.28%)
JavaScript (6.72%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
1,389 Stars
3,534 Commits
105 Forks
4 Watchers
2 Branches
22 Contributors
Updated on Jul 10, 2025
Latest Version
4.4.0
Package Id
nestjs-pino@4.4.0
Unpacked Size
86.19 kB
Size
22.70 kB
File Count
28
NPM Version
10.8.2
Node Version
20.18.3
Published on
Mar 19, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
4
33
"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! 🇺🇦
✨✨✨ Platform agnostic logger for NestJS based on Pino with REQUEST CONTEXT IN EVERY LOG ✨✨✨
This is documentation for v2+ which works with NestJS 8+.
Please see documentation for the previous major version which works with NestJS < 8 here.
1npm i nestjs-pino pino-http
Firstly, import module with LoggerModule.forRoot(...)
or LoggerModule.forRootAsync(...)
only once in root module (check out module configuration docs below):
1import { LoggerModule } from 'nestjs-pino'; 2 3@Module({ 4 imports: [LoggerModule.forRoot()], 5}) 6class AppModule {}
Secondly, set up app logger:
1import { Logger } from 'nestjs-pino'; 2 3const app = await NestFactory.create(AppModule, { bufferLogs: true }); 4app.useLogger(app.get(Logger));
Now you can use one of two loggers:
1// NestJS standard built-in logger. 2// Logs will be produced by pino internally 3import { Logger } from '@nestjs/common'; 4 5export class MyService { 6 private readonly logger = new Logger(MyService.name); 7 foo() { 8 // All logger methods have args format the same as pino, but pino methods 9 // `trace` and `info` are mapped to `verbose` and `log` to satisfy 10 // `LoggerService` interface of NestJS: 11 this.logger.verbose({ foo: 'bar' }, 'baz %s', 'qux'); 12 this.logger.debug('foo %s %o', 'bar', { baz: 'qux' }); 13 this.logger.log('foo'); 14 } 15}
Usage of the standard logger is recommended and idiomatic for NestJS. But there is one more option to use:
1import { PinoLogger, InjectPinoLogger } from 'nestjs-pino'; 2 3export class MyService { 4 constructor( 5 private readonly logger: PinoLogger 6 ) { 7 // Optionally you can set context for logger in constructor or ... 8 this.logger.setContext(MyService.name); 9 } 10 11 constructor( 12 // ... set context via special decorator 13 @InjectPinoLogger(MyService.name) 14 private readonly logger: PinoLogger 15 ) {} 16 17 foo() { 18 // PinoLogger has same methods as pino instance 19 this.logger.trace({ foo: 'bar' }, 'baz %s', 'qux'); 20 this.logger.debug('foo %s %o', 'bar', { baz: 'qux' }); 21 this.logger.info('foo'); 22 } 23}
Output:
1// Logs by app itself 2{"level":30,"time":1629823318326,"pid":14727,"hostname":"my-host","context":"NestFactory","msg":"Starting Nest application..."} 3{"level":30,"time":1629823318326,"pid":14727,"hostname":"my-host","context":"InstanceLoader","msg":"LoggerModule dependencies initialized"} 4{"level":30,"time":1629823318327,"pid":14727,"hostname":"my-host","context":"InstanceLoader","msg":"AppModule dependencies initialized"} 5{"level":30,"time":1629823318327,"pid":14727,"hostname":"my-host","context":"RoutesResolver","msg":"AppController {/}:"} 6{"level":30,"time":1629823318327,"pid":14727,"hostname":"my-host","context":"RouterExplorer","msg":"Mapped {/, GET} route"} 7{"level":30,"time":1629823318327,"pid":14727,"hostname":"my-host","context":"NestApplication","msg":"Nest application successfully started"} 8 9// Logs by injected Logger and PinoLogger in Services/Controllers. Every log 10// has it's request data and unique `req.id` (by default id is unique per 11// process, but you can set function to generate it from request context and 12// for example pass here incoming `X-Request-ID` header or generate UUID) 13{"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"} 14{"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\"}"} 15{"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"} 16 17// Automatic logs of every request/response 18{"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"}
There are other Nestjs loggers. Key purposes of this module are:
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 | + | + | + |
Just import LoggerModule
to your module:
1import { LoggerModule } from 'nestjs-pino'; 2 3@Module({ 4 imports: [LoggerModule.forRoot()], 5 ... 6}) 7class MyModule {}
The following interface is using for the configuration:
1interface Params { 2 /** 3 * Optional parameters for `pino-http` module 4 * @see https://github.com/pinojs/pino-http#api 5 */ 6 pinoHttp?: 7 | pinoHttp.Options 8 | DestinationStream 9 | [pinoHttp.Options, DestinationStream]; 10 11 /** 12 * Optional parameter for routing. It should implement interface of 13 * parameters of NestJS built-in `MiddlewareConfigProxy['forRoutes']`. 14 * @see https://docs.nestjs.com/middleware#applying-middleware 15 * It can be used for both disabling automatic req/res logs (see above) and 16 * removing request context from following logs. It works for all requests by 17 * default. If you only need to turn off the automatic request/response 18 * logging for some specific (or all) routes but keep request context for app 19 * logs use `pinoHttp.autoLogging` field. 20 */ 21 forRoutes?: Parameters<MiddlewareConfigProxy['forRoutes']>; 22 23 /** 24 * Optional parameter for routing. It should implement interface of 25 * parameters of NestJS built-in `MiddlewareConfigProxy['exclude']`. 26 * @see https://docs.nestjs.com/middleware#applying-middleware 27 * It can be used for both disabling automatic req/res logs (see above) and 28 * removing request context from following logs. It works for all requests by 29 * default. If you only need to turn off the automatic request/response 30 * logging for some specific (or all) routes but keep request context for app 31 * logs use `pinoHttp.autoLogging` field. 32 */ 33 exclude?: Parameters<MiddlewareConfigProxy['exclude']>; 34 35 /** 36 * Optional parameter to skip pino configuration in case you are using 37 * FastifyAdapter, and already configure logger in adapter's config. The Pros 38 * and cons of this approach are described in the FAQ section of the 39 * documentation: 40 * @see https://github.com/iamolegga/nestjs-pino#faq. 41 */ 42 useExisting?: true; 43 44 /** 45 * Optional parameter to change property name `context` in resulted logs, 46 * so logs will be like: 47 * {"level":30, ... "RENAME_CONTEXT_VALUE_HERE":"AppController" } 48 */ 49 renameContext?: string; 50}
Use LoggerModule.forRoot
method with argument of Params interface:
1import { LoggerModule } from 'nestjs-pino';
2
3@Module({
4 imports: [
5 LoggerModule.forRoot({
6 pinoHttp: [
7 {
8 name: 'add some name to every JSON line',
9 level: process.env.NODE_ENV !== 'production' ? 'debug' : 'info',
10 // install 'pino-pretty' package in order to use the following option
11 transport: process.env.NODE_ENV !== 'production'
12 ? { target: 'pino-pretty' }
13 : undefined,
14
15
16 // and all the other fields of:
17 // - https://github.com/pinojs/pino-http#api
18 // - https://github.com/pinojs/pino/blob/HEAD/docs/api.md#options-object
19
20
21 },
22 someWritableStream
23 ],
24 forRoutes: [MyController],
25 exclude: [{ method: RequestMethod.ALL, path: 'check' }]
26 })
27 ],
28 ...
29})
30class MyModule {}
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 or undefined
Here's an example:
1import { LoggerModule } from 'nestjs-pino'; 2 3@Injectable() 4class ConfigService { 5 public readonly level = 'debug'; 6} 7 8@Module({ 9 providers: [ConfigService], 10 exports: [ConfigService] 11}) 12class ConfigModule {} 13 14@Module({ 15 imports: [ 16 LoggerModule.forRootAsync({ 17 imports: [ConfigModule], 18 inject: [ConfigService], 19 useFactory: async (config: ConfigService) => { 20 await somePromise(); 21 return { 22 pinoHttp: { level: config.level }, 23 }; 24 } 25 }) 26 ], 27 ... 28}) 29class TestModule {}
In essence, asynchronous logging enables even faster performance by
pino
.
Please, read pino asynchronous mode docs 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:
1import pino from 'pino';
2import { LoggerModule } from 'nestjs-pino';
3
4@Module({
5 imports: [
6 LoggerModule.forRoot({
7 pinoHttp: {
8 stream: pino.destination({
9 dest: './my-file', // omit for stdout
10 minLength: 4096, // Buffer before writing
11 sync: false, // Asynchronous logging
12 }),
13 },
14 }),
15 ],
16 ...
17})
18class MyModule {}
See pino.destination
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
.
1 const module: TestingModule = await Test.createTestingModule({ 2 providers: [ 3 MyService, 4 { 5 provide: getLoggerToken(MyService.name), 6 useValue: mockLogger, 7 }, 8 ], 9 }).compile();
Logger
and PinoLogger
classes can be extended.
1// logger.service.ts 2import { Logger, PinoLogger, Params, PARAMS_PROVIDER_TOKEN } from 'nestjs-pino'; 3 4@Injectable() 5class LoggerService extends Logger { 6 constructor( 7 logger: PinoLogger, 8 @Inject(PARAMS_PROVIDER_TOKEN) params: Params 9 ) { 10 ... 11 } 12 // extended method 13 myMethod(): any {} 14} 15 16import { PinoLogger, Params, PARAMS_PROVIDER_TOKEN } from 'nestjs-pino'; 17 18@Injectable() 19class LoggerService extends PinoLogger { 20 constructor( 21 @Inject(PARAMS_PROVIDER_TOKEN) params: Params 22 ) { 23 // ... 24 } 25 // extended method 26 myMethod(): any {} 27} 28 29 30// logger.module.ts 31@Module({ 32 providers: [LoggerService], 33 exports: [LoggerService], 34 imports: [LoggerModule.forRoot()], 35}) 36class LoggerModule {}
Logger
injection in constructorSince 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.
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:
1 2@Controller('/') 3class TestController { 4 constructor( 5 private readonly logger: PinoLogger, 6 private readonly service: MyService, 7 ) {} 8 9 @Get() 10 get() { 11 // assign extra fields in one place... 12 this.logger.assign({ userID: '42' }); 13 return this.service.test(); 14 } 15} 16 17@Injectable() 18class MyService { 19 private readonly logger = new Logger(MyService.name); 20 21 test() { 22 // ...and it will be logged in another one 23 this.logger.log('hello world'); 24 } 25}
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
.
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. Example:
1@Controller('/') 2class TestController { 3 @Post('/change-loggin-level') 4 setLevel() { 5 PinoLogger.root.level = 'info'; 6 return null; 7 } 8}
err
propertyBy 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:
1import { LoggerErrorInterceptor } from 'nestjs-pino'; 2 3const app = await NestFactory.create(AppModule); 4app.useGlobalInterceptors(new LoggerErrorInterceptor());
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).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):
1// main.ts 2import { Logger } from 'nestjs-pino'; 3// ... 4 const app = await NestFactory.create(AppModule, { bufferLogs: true }); 5 app.useLogger(app.get(Logger)); 6// ...
Note that for standalone applications, buffering has to be flushed using app.flushLogs() manually after custom logger is ready to be used by NestJS (refer to this issue for more details):
1// main.ts
2import { Logger } from 'nestjs-pino';
3
4// ...
5 const app = await NestFactory.createApplicationContext(AppModule, { bufferLogs: true });
6 app.useLogger(app.get(Logger));
7 app.flushLogs();
8// ...
In all the other places you can use built-in Logger
:
1// my-service.ts 2import { Logger } from '@nestjs/common'; 3class MyService { 4 private readonly logger = new Logger(MyService.name); 5}
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 callingapp.useLogger()
.That way if we follow the steps from the previous section and call
app.useLogger(app.get(MyLogger))
, the following calls tothis.logger.log()
fromMyService
would result in calls to methodlog
fromMyLogger
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.
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.
Q: How to disable automatic request/response logs?
A: check out autoLogging field of pino-http 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 that are set in pinoHttp
field of Params
Q: How does it work?
A: It uses pino-http under hood, so every request has it's own child-logger, and with help of AsyncLocalStorage Logger
and PinoLogger
can get it while calling own methods. So your logs can be grouped by req.id
.
Q: Why use AsyncLocalStorage instead of REQUEST scope?
A: REQUEST scope can have perfomance issues. 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.
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, 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:
pino
is using with default params in NestJS apps based on fastifyAll the other cases are lead to either code duplication or unexpected behavior.
No vulnerabilities found.
Reason
30 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10
Reason
no binaries found in the repo
Reason
no dangerous workflow patterns detected
Reason
license file detected
Details
Reason
1 existing vulnerabilities detected
Details
Reason
dependency not pinned by hash detected -- score normalized to 4
Details
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Score
Last Scanned on 2025-07-07
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More