Nest log package pino, winston configuration – lazy people’s toss

nest log

Three kinds of node server log selection

  • winston
  • pino
  • log4js

May 23, 2023

Look at the number of stars: winston > pino > log4js

Experience:

  1. Pino has a simple format and fast speed. It supports inputting logs to any database. The logs are not automatically cleaned up (maybe my method is wrong? Friends who know it can tell me in the comments, thank you very much~)
  2. winston is universal, stable, and the log can be cleaned up automatically.
  3. log4js I don’t know why, I don’t want to use it unless you persuade me.

pino

Currently using pino

npm install pino nestjs-pino pino-http
npm install nanoid
npm install pino-pretty -D
npm install pino-roll -D

pino-pretty: for formatting
pino-roll: log, set file size, split period, log name.

Features:

  • Pino’s internal architecture is based on Worker Threads
  • Simple configuration
  • Support log type to specify output to different files (will cause slow speed)
  • Support output logs to any database

Problem: Logs need to be cleaned up by scheduled tasks to avoid occupying memory.

main.ts

import {<!-- --> Logger } from 'nestjs-pino';
const app = await NestFactory.create<NestExpressApplication>(AppModule, {<!-- -->
    bufferLogs: true, // use the external log package pino
  });
  app.useLogger(app.get(Logger));
  app.flushLogs();

logger.config.ts

import {<!-- --> nanoid } from 'nanoid';
import type {<!-- --> Request } from 'express';
import {<!-- --> IncomingMessage } from 'http';
import type {<!-- --> Params } from 'nestjs-pino';
import {<!-- --> multistream } from 'pino';
import type {<!-- --> ReqId } from 'pino-http';
import {<!-- --> join } from 'path';
import {<!-- --> format } from 'date-fns';
const passUrl = new Set(['./health']);
export const loggerOptions: Params = {<!-- -->
  pinoHttp: [
    {<!-- -->
      // https://getpino.io/#/docs/api?id=timestamp-boolean-function
      quietReqLogger: true,
      genReqId: (req: IncomingMessage): ReqId =>
        (<Request>req).header('X-Request-id') || nanoid(),
      ...(process.env.NODE_ENV === 'production'
        ? {<!-- -->
            // https://www.npmjs.com/package/pino-roll
            level: 'error',
            transport: {<!-- -->
              target: 'pino-roll',
              options: {<!-- -->
                file: join('logs', 'log'),
                size: '10m',
                // period
                frequency: 'daily',
                mkdir: true,
                extension: `.${<!-- -->format(new Date(), 'yyyy-MM-dd')}.json`,
              },
            },
          }
        : {<!-- -->
            level: 'debug',
            transport: {<!-- -->
              // https://github.com/pinojs/pino-pretty
              target: 'pino-pretty',
              options: {<!-- --> sync: true, colorize: true },
              // options: { sync: true, singleLine: true, colorize: true }, // production environment and then set compression to one line
            },
          }),
      autoLogging: {<!-- -->
        ignore: (req: IncomingMessage) =>
          passUrl.has((<Request>req).originalUrl),
      },
    },
    multistream(
      [
        {<!-- --> level: 'debug', stream: process.stdout },
        {<!-- --> level: 'error', stream: process.stdout },
        {<!-- --> level: 'fatal', stream: process.stdout },
      ],
      {<!-- --> dedupe: true }, // delete duplicate output
    ),
  ],
};

Other setting modes are not as good as the above setting, which lacks log segmentation and file size configuration, but pino also has stream operations, which can write logic, write how to divide logs or limit file size, etc., which is equal to self-configuration.

// 1. pino/file
const file =
  process.cwd() + `/logs/log-${<!-- -->format(new Date(), 'yyyy-MM-dd')}.json`;
{<!-- -->
  level: 'info',
  transport: {<!-- -->
    target: 'pino/file',
    options: {<!-- -->
      destination: file, // 1 is direct output, 2 is output when an error occurs, and other directory files set for themselves
      mkdir: true,
    },
  },
}
// The difference between using the built-in pino/file transfer and using pino.destination is that pino.destination runs in the main thread, while pino/file sets pino.destimation in the worker thread

// 2. pino/file
target: 'pino-pretty',

Using winston

  • [nest-winston][nest-winston] logging
  • [winston-daily-rotate-file][winston-daily-rotate-file] Configure production environment logs

npm install –save nest-winston winston
npm install winston-daily-rotate-file

Use [bootstrap configuration method][replacing-the-nest-logger-also-for-bootstrapping]

main.ts

const app = await NestFactory.create<NestExpressApplication>(AppModule, {<!-- -->
  logger: WinstonModule.createLogger({<!-- -->
    instance: createLogger({<!-- --> ...loggerOptions }),
  }),
});

logger.config.ts

import * as winston from 'winston';
import {<!-- --> utilities as nestWinstonModuleUtilities } from 'nest-winston';
import DailyRotateFile from 'winston-daily-rotate-file';
const format = (label?: string) => {<!-- -->
  switch (label) {<!-- -->
    case 'isDev':
      return {<!-- -->
        format: winston.format.combine(
          winston.format.timestamp(),
          winston. format. ms(),
          nestWinstonModuleUtilities.format.nestLike('nestBk', {<!-- -->
            colors: true,
            prettyPrint: true,
          }),
        ),
      };
    default:
      return {<!-- -->
        format: winston.format.combine(
          winston.format.timestamp(),
          winston. format. ms(),
        ),
      };
  }
};
const prodLoggerConfig = [
  new DailyRotateFile({<!-- -->
    level: 'warn',
    filename: 'nestBk-?TE%.log',
    dirname: 'logs',
    datePattern: 'YYYY-MM-DD-HH',
    maxSize: '20m',
    maxFiles: '14d',
    zippedArchive: true,
    ...format(),
  }),
  new DailyRotateFile({<!-- -->
    level: 'info',
    filename: 'info-?TE%.log',
    dirname: 'logs',
    datePattern: 'YYYY-MM-DD-HH',
    maxSize: '20m',
    maxFiles: '14d',
    zippedArchive: true,
    ...format(),
  }),
];

export const loggerOptions = {<!-- -->
  transports: [
    new winston.transports.Console({<!-- -->
      level: 'info',
      ...format('isDev'),
    }),
    ...(IsProMode? prodLoggerConfig: []),
  ],
};

when using it
xxx.module.ts

 providers: [CatsService, Logger],

xxx.controller.ts

 import {<!-- --> Logger } from '@nestjs/common';
  private readonly logger: Logger,

  this.logger.log('request successful');
  this.logger.warn('request warning');
  this.logger.error('request error');


The style has not changed much (I like the simple style of pino instead)

The main reason is that there is an additional configuration for automatic expiration and deletion, so there is no need to manually delete the log

QA:

  1. Nanoid esm problem, downgrade directly
    Official issues

“nanoid”: “^.4.x.x” => “nanoid”: “^3.3.6”,

refer to