Add logger in Nodejs app with winston

Introduction

Logs là vấn đề không thể thiếu trong một dự án bất kỳ nào dù lớn hay nhỏ và trong javascript cũng vậy. Chúng ta thường hay log theo cách đơn giản là dùng method console.log, cách nhanh gọn 😄 để debug hay log ra lỗi. Nhưng cách đó không thực sự hiệu quả, vì vậy chúng ta cần phải quản lý log của ứng dụng thông qua việc 1 file log hoặc tương tự như vậy để dễ dàng biết tiến trình chạy của ứng dụng,... Cụ thể ở bài này mình xin giới thiệu cách tạo log trong Nodejs app với winston - hỗ trợ nhiều hình thức như: file, console,...

Getting started

Ok, just do it. Đầu tiên chúng ta cần cài đặt thư viện vào trong project của chúng ta qua npm:

npm i winston # hoặc yarn add winston

Chúng ta có thể tạo logger đơn giản bằng cách sử dụng method winston.createLogger như sau:

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    //
    // - Write to all logs with level `info` and below to `combined.log` 
    // - Write all logs error (and below) to `error.log`.
    // 
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

//
// If we're not in production then log to the `console` with the format:
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
// 
if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

Nhưng đây chỉ là giới thiệu qua cách đơn giản nhất thôi chứ mình không khuyến khích mọi người dùng cách này mà nên tự mình custom hơn. Trước hết giới thiệu qua các level của log với winston:

const levels = { 
  error: 0, 
  warn: 1, 
  info: 2, 
  verbose: 3, 
  debug: 4, 
  silly: 5 
}

Level trên là theo thứ tự tăng dần. Bây giờ thì tự config log theo ý mình như sau:

# vendor/log/index.ts
import * as winston from 'winston';
import * as path from 'path';
import * as moment from 'moment';

const log = new (winston.Logger)({
    transports: [
    // info console log
    new (winston.transports.Console)({
      level: 'info',
      name: 'info-console',
      colorize: true,
      timestamp: () => moment().format('YYYY-MM-DD HH-mm-ss'),
      formatter: options => `[${options.timestamp()}]: ${options.message || ''}`
    }),
    // info log file
    new (winston.transports.File)({
      level: 'info',
      name: 'info-file',
      filename: path.resolve(__dirname, '../../..', 'storage/logs', 'development-info.log'),
      timestamp: () => moment().format('YYYY-MM-DD HH-mm-ss'),
      formatter: options => `[${options.timestamp()}]: ${options.message || ''}`,
      json: false
    }),
    // errors console log
    new (winston.transports.Console)({
      level: 'error',
      name: 'error-console',
      colorize: true,
      timestamp: () => moment().format('YYYY-MM-DD HH-mm-ss'),
      formatter: options => `[${options.timestamp()}]: ${options.message || ''}`
    }),
    // errors log file
    new (winston.transports.File)({
      level: 'error',
      name: 'error-file',
      filename: path.resolve(__dirname, '../../..', 'storage/logs', 'development-errors.log'),
      timestamp: () => moment().format('YYYY-MM-DD HH-mm-ss'),
      formatter: options => `[${options.timestamp()}]: ${options.message || ''}`,
      json: false
    })
  ]
});

export default log;

Thực ra cũng không có gì khó hiểu lắm 😄.

  • transports chính là cách thức chúng ta muốn log ra, ở đây mình chỉ muốn config để generate ra file log, console và chỉ log với 2 level chính là error và info.
  • level: khai báo mức độ log (một trong số level mình liệt kê ở phía trên).
  • colorize: là lựa chọn khi log ra console (màu mè chút).
  • filename: file output gồm đường dẫn cũng như tên file.
  • timestamp: mình dùng thêm moment.js để format lại timestamp
  • formater: options cụ thể để format.

Ok vậy đã custom xong và giờ tiến hành ghi log vào file chúng ta muốn:

import { Request, Response } from 'express';
import log from '../../../vendor/log';
...

export async function index(req: Request, res: Response) {
  try {
    const posts = await Post.find();
    res.ok(posts);
  } catch (error) {
    // ghi log lỗi
    log.error(error);
    res.serverError(error);
  }
}

Ở đây level log là error. Nếu các bạn muốn log info thì chỉ việc đơn giản gọi: log.info(params) 😄

Conclusion

Đây chỉ là cách custom đơn giản để generate log vì winston còn cung cấp cho chúng ta nhiều thứ như: custom log level, colors, custom transports, handle exceptions hay query logs, stream logs,... Mọi người có thể tìm hiểu thêm. Happy coding !

References

Winston