✨✨✨ Platform agnostic logger for NestJS based on Pino with REQUEST CONTEXT IN EVERY LOG ✨✨✨
Import module:
import { LoggerModule } from 'nestjs-pino';
@Module({
imports: [LoggerModule.forRoot()],
controllers: [AppController],
providers: [MyService]
})
class MyModule {}
In controller:
import { Logger } from 'nestjs-pino';
@Controller()
export class AppController {
constructor(
private readonly myService: MyService,
private readonly logger: Logger
) {}
@Get()
getHello(): string {
this.logger.log("getHello()", AppController.name);
return `Hello ${this.myService.getWorld()}`;
}
}
In service:
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!";
}
}
Output:
// Logs by Nest itself, when set `app.useLogger(app.get(Logger))`
{"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 methods in Services/Controllers
// Every log has it's request data
{"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 one are:
- to be compatible with built in
LoggerService
- to log with JSON (thanks to
pino
- super fast logger) (why JSON?) - to log every request/response automatically (thanks to
pino-http
) - to bind request data to the logs automatically from any service on any application layer without passing request context
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 {}
LoggerModule.forRoot
has the same API as pino-http:
import { LoggerModule } from 'nestjs-pino';
@Module({
imports: [
LoggerModule.forRoot(
{
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
)
],
...
})
class MyModule {}
With LoggerModule.forRootAsync
you can for example import your ConfigModule
and inject ConfigService
to use it in useFactory
method.
useFactory
should return result typeof arguments of pino-http or null
or Promise
of it, 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 { level: config.level };
}
})
],
...
})
class TestModule {}
Or without ConfigModule
you can just pass ConfigService
to providers
:
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 [{ level: config.level }, config.stream];
}
})
],
controllers: [TestController]
})
class TestModule {}
If you want to enable extreme
mode you should read pino extreme mode docs first.
If you are ok with that, so you can configure module like this:
import * as pino from 'pino';
import { LoggerModule } from 'nestjs-pino';
const dest = pino.extreme();
const logger = pino(dest);
@Module({
imports: [LoggerModule.forRoot({ logger })],
...
})
class MyModule {}
Also you can read more about Log loss prevention.
Logger
implements standard NestJS LoggerService
interface. So if you are familiar with built in NestJS logger you are 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!";
}
}
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));
Q: How does it work?
A: It use pino-http under hood, so every request has it's own child-logger, and with help of async_hooks Logger
can get it while calling own methods. So your logs can be groupped by req.id
.
Q: Why use async_hooks instead of REQUEST scope?
A: REQUEST scope can have perfomance issues depending on your app. TL;DR: using it will cause to instantiating every class, that injects Logger
, as a result it will slow down your app.
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 to NestJS built in LoggerService
methods, so decision is to map pino methods to LoggerService
methods to save Logger
API:
pino | LoggerService |
---|---|
trace |
verbose |
debug |
debug |
info |
log |
warn |
warn |
error |
error |