UNPKG

21.9 kBMarkdownView Raw
1<p align="center">
2 <img alt="NestJS-Pino logo" src="https://raw.githubusercontent.com/iamolegga/nestjs-pino/master/logo.png"/>
3</p>
4
5<h1 align="center">NestJS-Pino</h1>
6
7<p align="center">
8 <a href="https://www.npmjs.com/package/nestjs-pino">
9 <img alt="npm" src="https://img.shields.io/npm/v/nestjs-pino" />
10 </a>
11 <a href="https://travis-ci.org/iamolegga/nestjs-pino">
12 <img alt="Travis (.org)" src="https://img.shields.io/travis/iamolegga/nestjs-pino" />
13 </a>
14 <a href="https://coveralls.io/github/iamolegga/nestjs-pino?branch=master">
15 <img alt="Coverage Status" src="https://coveralls.io/repos/github/iamolegga/nestjs-pino/badge.svg?branch=master" />
16 </a>
17 <a href="https://snyk.io/test/github/iamolegga/nestjs-pino">
18 <img alt="Snyk Vulnerabilities for npm package" src="https://img.shields.io/snyk/vulnerabilities/npm/nestjs-pino" />
19 </a>
20 <img alt="David" src="https://img.shields.io/david/iamolegga/nestjs-pino">
21 <img alt="Dependabot" src="https://badgen.net/dependabot/iamolegga/nestjs-pino/?icon=dependabot">
22 <img alt="Supported platforms: Express & Fastify" src="https://img.shields.io/badge/platforms-Express%20%26%20Fastify-green" />
23</p>
24
25<p align="center">✨✨✨ Platform agnostic logger for NestJS based on Pino with <b>REQUEST CONTEXT IN EVERY LOG</b> ✨✨✨</p>
26
27## Example
28
29Import module with `LoggerModule.forRoot(...)` or `LoggerModule.forRootAsync(...)`:
30
31```ts
32import { LoggerModule } from "nestjs-pino";
33
34@Module({
35 imports: [LoggerModule.forRoot()],
36 controllers: [AppController],
37 providers: [MyService]
38})
39class MyModule {}
40```
41
42In controller let's use `Logger` - class with the same API as [built-in NestJS logger](https://docs.nestjs.com/techniques/logger):
43
44```ts
45import { Logger } from "nestjs-pino";
46
47@Controller()
48export class AppController {
49 constructor(
50 private readonly myService: MyService,
51 private readonly logger: Logger
52 ) {}
53
54 @Get()
55 getHello(): string {
56 // pass message
57 this.logger.log("getHello()");
58
59 // also we can pass context
60 this.logger.log("getHello()", AppController.name);
61
62 return `Hello ${this.myService.getWorld()}`;
63 }
64}
65```
66
67Let's compare it to another one logger - `PinoLogger`, it has same _logging_ API as `pino` instance.
68
69For example in service it will be used instead of previous one:
70
71```ts
72import { PinoLogger } from "nestjs-pino";
73
74@Injectable()
75export class MyService {
76 constructor(private readonly logger: PinoLogger) {}
77
78 getWorld(...params: any[]) {
79 this.logger.info({ context: MyService.name }, "getWorld(%o)", params);
80 return "World!";
81 }
82}
83```
84
85Also context can be set just once in `constructor` instead of every call:
86
87```ts
88import { PinoLogger } from "nestjs-pino";
89
90@Injectable()
91export class MyService {
92 constructor(private readonly logger: PinoLogger) {
93 logger.setContext(MyService.name);
94 }
95
96 getWorld(...params: any[]) {
97 this.logger.info("getWorld(%o)", params);
98 return "World!";
99 }
100}
101```
102
103Also context can be set at injection via decorator `@InjectPinoLogger(...)`:
104
105```ts
106import { PinoLogger, InjectPinoLogger } from "nestjs-pino";
107
108@Injectable()
109export class MyService {
110 constructor(
111 @InjectPinoLogger(MyService.name) private readonly logger: PinoLogger
112 ) {}
113
114 getWorld(...params: any[]) {
115 this.logger.info("getWorld(%o)", params);
116 return "World!";
117 }
118}
119```
120
121And also `Logger` can be set as app logger, as it is compatible with [built-in NestJS logger](https://docs.nestjs.com/techniques/logger):
122
123```ts
124import { Logger } from "nestjs-pino";
125
126const app = await NestFactory.create(AppModule, { logger: false });
127app.useLogger(app.get(Logger));
128```
129
130Output:
131
132```json
133// Logs by app itself
134{"level":30,"time":1570470154387,"pid":17383,"hostname":"my-host","context":"RoutesResolver","msg":"AppController {/}: true","v":1}
135{"level":30,"time":1570470154391,"pid":17383,"hostname":"my-host","context":"RouterExplorer","msg":"Mapped {/, GET} route true","v":1}
136{"level":30,"time":1570470154405,"pid":17383,"hostname":"my-host","context":"NestApplication","msg":"Nest application successfully started true","v":1}
137
138// Logs by injected Logger and PinoLogger in Services/Controllers
139// Every log has it's request data and unique `req.id` (per process)
140{"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}
141{"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}
142
143// Automatic logs of every request/response
144{"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}
145```
146
147## Comparison with others
148
149There are other Nestjs loggers. The key purposes of this module are:
150
151- to be compatible with built-in `LoggerService`
152- to log with JSON (thanks to `pino` - [super fast logger](https://github.com/pinojs/pino/blob/master/docs/benchmarks.md)) ([why JSON?](https://jahed.dev/2018/07/05/always-log-to-json/))
153- to log every request/response automatically (thanks to `pino-http`)
154- to bind request data to the logs automatically from any service on any application layer without passing request context
155- to have another one alternative `PinoLogger` for experienced `pino` users for more comfortable usage.
156
157| Logger | Nest App logger | Logger service | Autobind request data to logs |
158| ------------------ | :-------------: | :------------: | :---------------------------: |
159| nest-morgan | - | - | - |
160| nest-winston | + | + | - |
161| nestjs-pino-logger | + | + | - |
162| **nestjs-pino** | + | + | + |
163
164## Install
165
166```sh
167npm i nestjs-pino
168```
169
170## Register module
171
172### Zero configuration
173
174Just import `LoggerModule` to your module:
175
176```ts
177import { LoggerModule } from 'nestjs-pino';
178
179@Module({
180 imports: [LoggerModule.forRoot()],
181 ...
182})
183class MyModule {}
184```
185
186### Configuration params
187
188`nestjs-pino` can be configured with params object of next interface:
189
190```ts
191interface Params {
192 /**
193 * Optional parameters for `pino-http` module
194 * @see https://github.com/pinojs/pino-http#pinohttpopts-stream
195 */
196 pinoHttp?:
197 | pinoHttp.Options
198 | DestinationStream
199 | [pinoHttp.Options, DestinationStream];
200
201 /**
202 * Optional parameter for routing. It should implement interface of
203 * parameters of NestJS buil-in `MiddlewareConfigProxy['forRoutes']`.
204 * @see https://docs.nestjs.com/middleware#applying-middleware
205 * It can be used for disabling automatic req/res logs (see above).
206 * Keep in mind that it will remove context data from logs that are called
207 * inside not included or excluded routes and controlles.
208 */
209 forRoutes?: Parameters<MiddlewareConfigProxy["forRoutes"]>;
210
211 /**
212 * Optional parameter for routing. It should implement interface of
213 * parameters of NestJS buil-in `MiddlewareConfigProxy['exclude']`.
214 * @see https://docs.nestjs.com/middleware#applying-middleware
215 * It can be used for disabling automatic req/res logs (see above).
216 * Keep in mind that it will remove context data from logs that are called
217 * inside not included or excluded routes and controlles.
218 */
219 exclude?: Parameters<MiddlewareConfigProxy["exclude"]>;
220
221 /**
222 * Optional parameter to skip `pino` configuration in case you are using
223 * Fastify adapter, and already configuring it on adapter level.
224 * Pros and cons of this approach are descibed in the last section.
225 */
226 useExisting?: true;
227
228 /**
229 * Optional parameter to change property name `context` in resulted logs,
230 * so logs will be like:
231 * {"level":30, ... "RENAME_CONTEXT_VALUE_HERE":"AppController" }
232 * Works with both `Logger` and `PinoLogger`
233 */
234 renameContext?: string;
235}
236```
237
238### Synchronous configuration
239
240Use `LoggerModule.forRoot` method with argument of [Params interface](#configuration-params):
241
242```ts
243import { LoggerModule } from 'nestjs-pino';
244
245@Module({
246 imports: [
247 LoggerModule.forRoot({
248 pinoHttp: [
249 {
250 name: 'add some name to every JSON line',
251 level: process.env.NODE_ENV !== 'production' ? 'debug' : 'info',
252 // install 'pino-pretty' package in order to use the following option
253 prettyPrint: process.env.NODE_ENV !== 'production',
254 useLevelLabels: true,
255 // and all the others...
256 },
257 someWritableStream
258 ],
259 forRoutes: [MyController],
260 exclude: [{ method: RequestMethod.ALL, path: "check" }]
261 })
262 ],
263 ...
264})
265class MyModule {}
266```
267
268### Asynchronous configuration
269
270With `LoggerModule.forRootAsync` you can, for example, import your `ConfigModule` and inject `ConfigService` to use it in `useFactory` method.
271
272`useFactory` should return object with [Params interface](#configuration-params) or undefined
273
274Here's an example:
275
276```ts
277import { LoggerModule } from 'nestjs-pino';
278
279@Injectable()
280class ConfigService {
281 public readonly level = "debug";
282}
283
284@Module({
285 providers: [ConfigService],
286 exports: [ConfigService]
287})
288class ConfigModule {}
289
290@Module({
291 imports: [
292 LoggerModule.forRootAsync({
293 imports: [ConfigModule],
294 inject: [ConfigService],
295 useFactory: async (config: ConfigService) => {
296 await somePromise();
297 return {
298 pinoHttp: { level: config.level },
299 };
300 }
301 })
302 ],
303 ...
304})
305class TestModule {}
306```
307
308Or you can just pass `ConfigService` to `providers`, if you don't have any `ConfigModule`:
309
310```ts
311import { LoggerModule } from "nestjs-pino";
312
313@Injectable()
314class ConfigService {
315 public readonly level = "debug";
316 public readonly stream = stream;
317}
318
319@Module({
320 imports: [
321 LoggerModule.forRootAsync({
322 providers: [ConfigService],
323 inject: [ConfigService],
324 useFactory: (config: ConfigService) => {
325 return {
326 pinoHttp: [{ level: config.level }, config.stream]
327 };
328 }
329 })
330 ],
331 controllers: [TestController]
332})
333class TestModule {}
334```
335
336### Extreme mode
337
338> In essence, `extreme` mode enables even faster performance by Pino.
339
340Please, read [pino extreme mode docs](https://github.com/pinojs/pino/blob/master/docs/extreme.md#extreme-mode) first. There is a risk of some logs being lost, but you can [minimize it](https://github.com/pinojs/pino/blob/master/docs/extreme.md#log-loss-prevention).
341
342If you know what you're doing, you can enable it like so:
343
344```ts
345import * as pino from 'pino';
346import { LoggerModule } from 'nestjs-pino';
347
348const dest = pino.extreme();
349const logger = pino(dest);
350
351@Module({
352 imports: [LoggerModule.forRoot({ pinoHttp: { logger } })],
353 ...
354})
355class MyModule {}
356```
357
358## Usage as Logger service
359
360As it said before, there are 2 logger classes:
361
362- `Logger` - implements standard NestJS `LoggerService` interface. So if you are familiar with [built-in NestJS logger](https://docs.nestjs.com/techniques/logger), you are good to go.
363- `PinoLogger` - implements standard `pino` _logging_ methods: [trace](https://github.com/pinojs/pino/blob/master/docs/api.md#loggertracemergingobject-message-interpolationvalues), [debug](https://github.com/pinojs/pino/blob/master/docs/api.md#loggerdebugmergingobject-message-interpolationvalues), [info](https://github.com/pinojs/pino/blob/master/docs/api.md#loggerinfomergingobject-message-interpolationvalues), [warn](https://github.com/pinojs/pino/blob/master/docs/api.md#loggerwarnmergingobject-message-interpolationvalues), [error](https://github.com/pinojs/pino/blob/master/docs/api.md#loggererrormergingobject-message-interpolationvalues), [fatal](https://github.com/pinojs/pino/blob/master/docs/api.md#loggerfatalmergingobject-message-interpolationvalues). So if you are familiar with it, you are also good to go.
364
365### Logger
366
367```ts
368// my.service.ts
369import { Logger } from "nestjs-pino";
370
371@Injectable()
372export class MyService {
373 constructor(private readonly logger: Logger) {}
374
375 getWorld(...params: any[]) {
376 this.logger.log("getWorld(%o)", MyService.name, params);
377 return "World!";
378 }
379}
380```
381
382### PinoLogger
383
384See [pino logging method parameters](https://github.com/pinojs/pino/blob/master/docs/api.md#logging-method-parameters) for more logging examples.
385
386```ts
387// my.service.ts
388import { PinoLogger, InjectPinoLogger } from "nestjs-pino";
389
390@Injectable()
391export class MyService {
392 // regular injecting
393 constructor(private readonly logger: PinoLogger) {}
394
395 // regular injecting and set context
396 constructor(private readonly logger: PinoLogger) {
397 logger.setContext(MyService.name);
398 }
399
400 // inject and set context via `InjectPinoLogger`
401 constructor(
402 @InjectPinoLogger(MyService.name) private readonly logger: PinoLogger
403 ) {}
404
405 getWorld(...params: any[]) {
406 this.logger.info("getWorld(%o)", params);
407 return "World!";
408 }
409}
410```
411
412#### Testing a class that uses @InjectPinoLogger
413
414This package exposes a getLoggerToken() function that returns a prepared injection token based on the provided context.
415Using 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.
416
417```ts
418 const module: TestingModule = await Test.createTestingModule({
419 providers: [
420 MyService,
421 {
422 provide: getLoggerToken(MyService.name),
423 useValue: mockLogger,
424 },
425 ],
426 }).compile();
427```
428
429## Usage as NestJS app logger
430
431According to [official docs](https://docs.nestjs.com/techniques/logger#dependency-injection), loggers with Dependency injection should be set via following construction:
432
433```ts
434import { Logger } from "nestjs-pino";
435
436const app = await NestFactory.create(MyModule, { logger: false });
437app.useLogger(app.get(Logger));
438```
439## Extend the Logger class
440
441You can extend the Logger class to add your own business logic.
442
443```ts
444// logger.service.ts
445import { Logger, PinoLogger, Params, PARAMS_PROVIDER_TOKEN } from "nestjs-pino";
446
447@Injectable()
448class LoggerService extends Logger() {
449 // regular injecting
450 constructor(
451 logger: PinoLogger,
452 @Inject(PARAMS_PROVIDER_TOKEN) params: Params
453 ) {
454 ...
455 }
456
457 // extended method
458 myMethod(): any {}
459}
460
461import { PinoLogger, Params, PARAMS_PROVIDER_TOKEN } from "nestjs-pino";
462
463@Injectable()
464class LoggerService extends PinoLogger() {
465 // regular injecting
466 constructor(
467 @Inject(PARAMS_PROVIDER_TOKEN) params: Params
468 ) {
469 ...
470 }
471
472 // extended method
473 myMethod(): any {}
474}
475
476
477// logger.module.ts
478@Module({
479 providers: [LoggerService],
480 exports: [LoggerService],
481 imports: [LoggerModule.forRoot()],
482})
483class LoggerModule {}
484```
485
486## Migrating
487
488### v1
489
490- All parameters of v.0 are moved to `pinoHttp` property (except `useExisting`).
491- `useExisting` now accept only `true`, because `false` does not make any sense
492
493## FAQ
494
495**Q**: _How does it work?_
496
497**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 [async_hooks](https://nodejs.org/api/async_hooks.html) `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`.
498
499---
500
501**Q**: _Why use [async_hooks](https://nodejs.org/api/async_hooks.html) instead of [REQUEST scope](https://docs.nestjs.com/fundamentals/injection-scopes#per-request-injection)?_
502
503**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 responce times.
504
505---
506
507**Q**: _I'm using old nodejs version, will it work for me?_
508
509**A**: Please read [this](https://github.com/jeff-lewis/cls-hooked#continuation-local-storage--hooked-).
510
511---
512
513**Q**: _What about pino built-in methods/levels?_
514
515**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:
516
517| `pino` method | `PinoLogger` method | `Logger` method |
518| ------------- | ------------------- | --------------- |
519| trace | trace | **verbose** |
520| debug | debug | debug |
521| info | info | **log** |
522| warn | warn | warn |
523| error | error | error |
524| fatal | fatal | - |
525
526---
527
528**Q**: _Fastify already includes pino, and I want to configure it on `Adapter` level, and use this config for logger_
529
530**A**: You can do it by providing `useExisting: true`. But there is one caveat:
531
532Fastify creates logger with your config per every request. And this logger is used by `Logger`/`PinoLogger` services inside that context underhood.
533
534But 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.
535
536**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.**
537
538And 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.
539
540So these property (`useExisting: true`) is not recommended and useful only for cases when:
541
542- this logger is not using for lifecycle events and application level logging in Nest apps based on fastify
543- pino using with default params in Nest apps based on fastify
544
545All the other cases are lead to either code duplication or unexpected behaviour.
546
547<h2 align="center">Do you use this library?<br/>Don't be shy to give it a star! ★</h2>
548
549Also if you are into NestJS ecosystem you may be interested in one of my other libs:
550
551[nestjs-pino](https://github.com/iamolegga/nestjs-pino)
552
553[![GitHub stars](https://img.shields.io/github/stars/iamolegga/nestjs-pino?style=flat-square)](https://github.com/iamolegga/nestjs-pino)
554[![npm](https://img.shields.io/npm/dm/nestjs-pino?style=flat-square)](https://www.npmjs.com/package/nestjs-pino)
555
556Platform agnostic logger for NestJS based on [pino](http://getpino.io/) with request context in every log
557
558---
559
560[nestjs-session](https://github.com/iamolegga/nestjs-session)
561
562[![GitHub stars](https://img.shields.io/github/stars/iamolegga/nestjs-session?style=flat-square)](https://github.com/iamolegga/nestjs-session)
563[![npm](https://img.shields.io/npm/dm/nestjs-session?style=flat-square)](https://www.npmjs.com/package/nestjs-session)
564
565Idiomatic session module for NestJS. Built on top of [express-session](https://www.npmjs.com/package/express-session)
566
567---
568
569[nestjs-cookie-session](https://github.com/iamolegga/nestjs-cookie-session)
570
571[![GitHub stars](https://img.shields.io/github/stars/iamolegga/nestjs-cookie-session?style=flat-square)](https://github.com/iamolegga/nestjs-cookie-session)
572[![npm](https://img.shields.io/npm/dm/nestjs-cookie-session?style=flat-square)](https://www.npmjs.com/package/nestjs-cookie-session)
573
574Idiomatic cookie session module for NestJS. Built on top of [cookie-session](https://www.npmjs.com/package/cookie-session)
575
576---
577
578[nestjs-roles](https://github.com/iamolegga/nestjs-roles)
579
580[![GitHub stars](https://img.shields.io/github/stars/iamolegga/nestjs-roles?style=flat-square)](https://github.com/iamolegga/nestjs-roles)
581[![npm](https://img.shields.io/npm/dm/nestjs-roles?style=flat-square)](https://www.npmjs.com/package/nestjs-roles)
582
583Type safe roles guard and decorator made easy
584
585---
586
587[nest-ratelimiter](https://github.com/iamolegga/nestjs-ratelimiter)
588
589[![GitHub stars](https://img.shields.io/github/stars/iamolegga/nestjs-ratelimiter?style=flat-square)](https://github.com/iamolegga/nestjs-ratelimiter)
590[![npm](https://img.shields.io/npm/dm/nest-ratelimiter?style=flat-square)](https://www.npmjs.com/package/nest-ratelimiter)
591
592Distributed consistent flexible NestJS rate limiter based on Redis
593
594---
595
596[create-nestjs-middleware-module](https://github.com/iamolegga/create-nestjs-middleware-module)
597
598[![GitHub stars](https://img.shields.io/github/stars/iamolegga/create-nestjs-middleware-module?style=flat-square)](https://github.com/iamolegga/create-nestjs-middleware-module)
599[![npm](https://img.shields.io/npm/dm/create-nestjs-middleware-module?style=flat-square)](https://www.npmjs.com/package/create-nestjs-middleware-module)
600
601Create simple idiomatic NestJS module based on Express/Fastify middleware in just a few lines of code with routing out of the box
602
603---
604
605[nestjs-configure-after](https://github.com/iamolegga/nestjs-configure-after)
606
607[![GitHub stars](https://img.shields.io/github/stars/iamolegga/nestjs-configure-after?style=flat-square)](https://github.com/iamolegga/nestjs-configure-after)
608[![npm](https://img.shields.io/npm/dm/nestjs-configure-after?style=flat-square)](https://www.npmjs.com/package/nestjs-configure-after)
609
610Declarative configuration of NestJS middleware order