Product
Socket Now Supports uv.lock Files
Socket now supports uv.lock files to ensure consistent, secure dependency resolution for Python projects and enhance supply chain security.
nestjs-pino
Advanced tools
The `nestjs-pino` package is a logging module for NestJS applications that integrates the Pino logger. Pino is a fast, low-overhead logging library for Node.js. This package allows you to leverage Pino's performance and features within a NestJS application.
Basic Logging
This feature allows you to set up basic logging in a NestJS application using Pino. The `LoggerModule.forRoot` method configures the logger with the specified options.
const { LoggerModule } = require('nestjs-pino');
@Module({
imports: [
LoggerModule.forRoot({
pinoHttp: { level: 'info' }
})
]
})
export class AppModule {}
Request Logging
This feature enables logging of HTTP requests. The `pinoHttp` option allows you to configure the logging level and other settings for HTTP request logging.
const { LoggerModule } = require('nestjs-pino');
@Module({
imports: [
LoggerModule.forRoot({
pinoHttp: { level: 'info', prettyPrint: true }
})
]
})
export class AppModule {}
Custom Logger Configuration
This feature allows you to provide a custom configuration for the logger using a factory function. This is useful for dynamic configuration based on environment variables or other runtime conditions.
const { LoggerModule } = require('nestjs-pino');
@Module({
imports: [
LoggerModule.forRootAsync({
useFactory: () => ({
pinoHttp: { level: 'debug', prettyPrint: true }
})
})
]
})
export class AppModule {}
Winston is another popular logging library for Node.js. It is highly configurable and supports multiple transports (e.g., console, file, HTTP). Compared to Pino, Winston is more feature-rich but generally slower in performance.
Bunyan is a simple and fast JSON logging library for Node.js. Like Pino, it focuses on performance and structured logging. However, Pino is generally considered faster and more modern.
Log4js is a logging library inspired by the Java library log4j. It supports various appenders (e.g., console, file, SMTP) and is highly configurable. While it offers more features, it is not as performant as Pino.
✨✨✨ Platform agnostic logger for NestJS based on Pino with REQUEST CONTEXT IN EVERY LOG ✨✨✨
Import module with LoggerModule.forRoot(...)
or LoggerModule.forRootAsync(...)
:
import { LoggerModule } from "nestjs-pino";
@Module({
imports: [LoggerModule.forRoot()],
controllers: [AppController],
providers: [MyService]
})
class MyModule {}
In controller let's use Logger
- class with the same API as built-in NestJS logger:
import { Logger } from "nestjs-pino";
@Controller()
export class AppController {
constructor(
private readonly myService: MyService,
private readonly logger: Logger
) {}
@Get()
getHello(): string {
// pass message
this.logger.log("getHello()");
// also we can pass context
this.logger.log("getHello()", AppController.name);
return `Hello ${this.myService.getWorld()}`;
}
}
Let's compare it to another one logger - PinoLogger
, it has same logging API as pino
instance.
For example in service it will be used instead of previous one:
import { PinoLogger } from "nestjs-pino";
@Injectable()
export class MyService {
constructor(private readonly logger: PinoLogger) {}
getWorld(...params: any[]) {
this.logger.info({ context: MyService.name }, "getWorld(%o)", params);
return "World!";
}
}
Also context can be set just once in constructor
instead of every call:
import { PinoLogger } from "nestjs-pino";
@Injectable()
export class MyService {
constructor(private readonly logger: PinoLogger) {
logger.setContext(MyService.name);
}
getWorld(...params: any[]) {
this.logger.info("getWorld(%o)", params);
return "World!";
}
}
Also context can be set at injection via decorator @InjectPinoLogger(...)
:
import { PinoLogger, InjectPinoLogger } from "nestjs-pino";
@Injectable()
export class MyService {
constructor(
@InjectPinoLogger(MyService.name) private readonly logger: PinoLogger
) {}
getWorld(...params: any[]) {
this.logger.info("getWorld(%o)", params);
return "World!";
}
}
And also Logger
can be set as app logger, as it is compatible with built-in NestJS logger:
import { Logger } from "nestjs-pino";
const app = await NestFactory.create(AppModule, { logger: false });
app.useLogger(app.get(Logger));
Output:
// Logs by app itself
{"level":30,"time":1570470154387,"pid":17383,"hostname":"my-host","context":"RoutesResolver","msg":"AppController {/}: true","v":1}
{"level":30,"time":1570470154391,"pid":17383,"hostname":"my-host","context":"RouterExplorer","msg":"Mapped {/, GET} route true","v":1}
{"level":30,"time":1570470154405,"pid":17383,"hostname":"my-host","context":"NestApplication","msg":"Nest application successfully started true","v":1}
// Logs by injected Logger and PinoLogger in Services/Controllers
// Every log has it's request data and unique `req.id` (per process)
{"level":30,"time":1570470161805,"pid":17383,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","headers":{...},"remoteAddress":"::1","remotePort":53957},"context":"AppController","msg":"getHello()","v":1}
{"level":30,"time":1570470161805,"pid":17383,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","headers":{...},"remoteAddress":"::1","remotePort":53957},"context":"MyService","msg":"getWorld([])","v":1}
// Automatic logs of every request/response
{"level":30,"time":1570470161819,"pid":17383,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","headers":{...},"remoteAddress":"::1","remotePort":53957},"res":{"statusCode":304,"headers":{...}},"responseTime":15,"msg":"request completed","v":1}
There are other Nestjs loggers. The key purposes of this module are:
LoggerService
pino
- super fast logger) (why JSON?)pino-http
)PinoLogger
for experienced pino
users for more comfortable usage.Logger | Nest App logger | Logger service | Autobind request data to logs |
---|---|---|---|
nest-morgan | - | - | - |
nest-winston | + | + | - |
nestjs-pino-logger | + | + | - |
nestjs-pino | + | + | + |
npm i nestjs-pino
Just import LoggerModule
to your module:
import { LoggerModule } from 'nestjs-pino';
@Module({
imports: [LoggerModule.forRoot()],
...
})
class MyModule {}
nestjs-pino
can be configured with params object of next interface:
interface Params {
/**
* Optional parameters for `pino-http` module
* @see https://github.com/pinojs/pino-http#pinohttpopts-stream
*/
pinoHttp?:
| pinoHttp.Options
| DestinationStream
| [pinoHttp.Options, DestinationStream];
/**
* Optional parameter for routing. It should implement interface of
* parameters of NestJS buil-in `MiddlewareConfigProxy['forRoutes']`.
* @see https://docs.nestjs.com/middleware#applying-middleware
* It can be used for disabling automatic req/res logs (see above).
* Keep in mind that it will remove context data from logs that are called
* inside not included or excluded routes and controlles.
*/
forRoutes?: Parameters<MiddlewareConfigProxy["forRoutes"]>;
/**
* Optional parameter for routing. It should implement interface of
* parameters of NestJS buil-in `MiddlewareConfigProxy['exclude']`.
* @see https://docs.nestjs.com/middleware#applying-middleware
* It can be used for disabling automatic req/res logs (see above).
* Keep in mind that it will remove context data from logs that are called
* inside not included or excluded routes and controlles.
*/
exclude?: Parameters<MiddlewareConfigProxy["exclude"]>;
/**
* Optional parameter to skip `pino` configuration in case you are using
* Fastify adapter, and already configuring it on adapter level.
* Pros and cons of this approach are descibed in the last section.
*/
useExisting?: true;
/**
* Optional parameter to change property name `context` in resulted logs,
* so logs will be like:
* {"level":30, ... "RENAME_CONTEXT_VALUE_HERE":"AppController" }
* Works with both `Logger` and `PinoLogger`
*/
renameContext?: string;
}
Use LoggerModule.forRoot
method with argument of Params interface:
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',
prettyPrint: process.env.NODE_ENV !== 'production',
useLevelLabels: true,
// and all the others...
},
someWritableStream
],
forRoutes: [MyController],
exclude: [{ method: RequestMethod.ALL, path: "check" }]
})
],
...
})
class 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:
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 {}
Or you can just pass ConfigService
to providers
, if you don't have any ConfigModule
:
import { LoggerModule } from "nestjs-pino";
@Injectable()
class ConfigService {
public readonly level = "debug";
public readonly stream = stream;
}
@Module({
imports: [
LoggerModule.forRootAsync({
providers: [ConfigService],
inject: [ConfigService],
useFactory: (config: ConfigService) => {
return {
pinoHttp: [{ level: config.level }, config.stream]
};
}
})
],
controllers: [TestController]
})
class TestModule {}
In essence,
extreme
mode enables even faster performance by Pino.
Please, read pino extreme mode docs first. There is a risk of some logs being lost, but you can minimize it.
If you know what you're doing, you can enable it like so:
import * as pino from 'pino';
import { LoggerModule } from 'nestjs-pino';
const dest = pino.extreme();
const logger = pino(dest);
@Module({
imports: [LoggerModule.forRoot({ pinoHttp: { logger } })],
...
})
class MyModule {}
As it said before, there are 2 logger classes:
Logger
- implements standard NestJS LoggerService
interface. So if you are familiar with built-in NestJS logger, you are good to go.PinoLogger
- implements standard pino
logging methods: trace, debug, info, warn, error, fatal. So if you are familiar with it, you are also good to go.// my.service.ts
import { Logger } from "nestjs-pino";
@Injectable()
export class MyService {
constructor(private readonly logger: Logger) {}
getWorld(...params: any[]) {
this.logger.log("getWorld(%o)", MyService.name, params);
return "World!";
}
}
See pino logging method parameters for more logging examples.
// my.service.ts
import { PinoLogger, InjectPinoLogger } from "nestjs-pino";
@Injectable()
export class MyService {
// regular injecting
constructor(private readonly logger: PinoLogger) {}
// regular injecting and set context
constructor(private readonly logger: PinoLogger) {
logger.setContext(MyService.name);
}
// inject and set context via `InjectPinoLogger`
constructor(
@InjectPinoLogger(MyService.name) private readonly logger: PinoLogger
) {}
getWorld(...params: any[]) {
this.logger.info("getWorld(%o)", params);
return "World!";
}
}
This package exposes a getLoggerToken() function that returns a prepared injection token based on the provided context. Using this token, you can easily provide a mock implementation of the logger using any of the standard custom provider techniques, including useClass, useValue, and useFactory.
const module: TestingModule = await Test.createTestingModule({
providers: [
MyService,
{
provide: getLoggerToken(MyService.name),
useValue: mockLogger,
},
],
}).compile();
According to official docs, loggers with Dependency injection should be set via following construction:
import { Logger } from "nestjs-pino";
const app = await NestFactory.create(MyModule, { logger: false });
app.useLogger(app.get(Logger));
pinoHttp
property (except useExisting
).useExisting
now accept only true
, because false
does not make any senseQ: How does it work?
A: It uses pino-http under hood, so every request has it's own child-logger, and with help of async_hooks Logger
and PinoLogger
can get it while calling own methods. So your logs can be grouped by req.id
. If you run several instances, unique key is pair: pid
+ req.id
.
Q: Why use async_hooks 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 responce times.
Q: I'm using old nodejs version, will it work for me?
A: Please read this.
Q: What about pino built-in methods/levels?
A: Pino built-in methods are not compatible with NestJS built-in LoggerService
methods. So for now there is option which logger to use, here is methods mapping:
pino method | PinoLogger method | Logger method |
---|---|---|
trace | trace | verbose |
debug | debug | debug |
info | info | log |
warn | warn | warn |
error | error | error |
fatal | fatal | - |
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 it and pass to that out of context logger.
And if you not pass config via forRoot
/forRootAsync
out of context logger will be instantiated with default params. So if you want to configure it anyway with the same options, then you have to provide the same config. And then If you are already provide that then you don't have to duplicate your code and provide pino config via fastify.
So these property (useExisting: true
) is not recommended and useful only for cases when:
All the other cases are lead to either code duplication or unexpected behaviour.
Also if you are into NestJS ecosystem you may be interested in one of my other libs:
Platform agnostic logger for NestJS based on pino with request context in every log
Idiomatic session module for NestJS. Built on top of express-session
Idiomatic cookie session module for NestJS. Built on top of cookie-session
Type safe roles guard and decorator made easy
Distributed consistent flexible NestJS rate limiter based on Redis
create-nestjs-middleware-module
Create simple idiomatic NestJS module based on Express/Fastify middleware in just a few lines of code with routing out of the box
Declarative configuration of NestJS middleware order
FAQs
Pino logger for NestJS
The npm package nestjs-pino receives a total of 232,146 weekly downloads. As such, nestjs-pino popularity was classified as popular.
We found that nestjs-pino demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Socket now supports uv.lock files to ensure consistent, secure dependency resolution for Python projects and enhance supply chain security.
Research
Security News
Socket researchers have discovered multiple malicious npm packages targeting Solana private keys, abusing Gmail to exfiltrate the data and drain Solana wallets.
Security News
PEP 770 proposes adding SBOM support to Python packages to improve transparency and catch hidden non-Python dependencies that security tools often miss.