Back
Featured image of post Node 日志之 winston 实践

Node 日志之 winston 实践

winston 是 Node 中一个比较知名的处理日志的库,简单通用,支持多种传输方式。winston 将日志记录的过程进行了分离,分为了日志格式化以及日志消费等多个部分,使用上更加灵活易扩展。

快速开始

const winston = require("winston");

// 创建 logger 实例
const logger = winston.createLogger({
  // 输出
  transports: [
    // console 输出
    new winston.transports.Console(),
    // 写到文件
    new winston.transports.File({ filename: "combined.log" }),
  ],
});

// 打印 log
logger.log({
  level: "info",
  message: "Hello distributed log files!",
});

logger.info("Hello again distributed logs");

Formats

Formats 代表 winston 中处理、格式化日志的一些常见方法,可以通过 winston.format 使用。

通过 Formats ,可以实现给日志设置加额外的属性,包含时间、颜色、错误堆栈等,使得日志信息更加丰富。

import { createLogger, format, transports } from 'winston'
import { DBTransport } from './transport'

const { label, combine, timestamp, metadata, json, uncolorize, errors } = format

/**
 * 创建 winston log
 */
export const logger = createLogger({
  format: combine(
    metadata(),
    errors({ stack: true }),
    label({ label: '_RELOG_' }),
    timestamp(),
    uncolorize(),
    json()
  ),
  handleExceptions: true,
  transports: [
    new transports.File({
      filename: './log/test.log',
    }),
  ],
})

Transport

Transport 是对传输消费日志方法的抽象,如将日志打印到 console,或者日志写入到文件中,都是消费日志的一种方法。

winston 中内置了几种核心的传输方法,使用 Node 的基础功能进行日志传输

  • Console 将日志传输到标准输出中

  • File 将日志写入文件

  • HTTP 通过 HTTP 请求传输日志

  • Stream 通过流传输日志

更多的 winston transport 可以参考官方文档

自定义 Transport

自定义 Transport 需要继承 TransportStream 类,并实现 log 方法,下面是一个 Demo,展示了将日志写入到云开发数据库中的实现

interface IQueryOptions {
  /**
   * 日志开始时间
   * new Date() - (24 * 60 * 60 * 1000)
   */
  from: number

  /**
   * 日志结束时间
   * new Date()
   */
  until: number

  /**
   * 查询的日志条数,如 10
   */
  limit: number

  /**
   * 查询日志偏移条数,如 0
   */
  offset: number

  /**
   * 查询排序
   */
  order: 'asc' | 'desc'

  /**
   * 查询字段
   */
  fields: string[]
}


/**
 * 基于 CloudBase 数据库的 Log Transport
 * 参考 winston http transport 实现:
 * https://github.com/winstonjs/winston/blob/master/lib/winston/transports/http.js
 */
export class DBTransport extends TransportStream {
  collection: Database.CollectionReference

  constructor(options = {}) {
    super(options)

    const app = getCloudBaseApp()
    this.collection = app.database().collection('relog')
  }

  /**
   * 实现 winston log 方法
   */
  log(info, callback) {
    const { label, level, message, stack, timestamp, metadata } = info

    // 添加环境 ID 和函数信息
    const envId = process.env.SCF_NAMESPACE
    const functionName = process.env.SCF_FUNCTIONNAME

    const logData = {
      ...metadata,
      label,
      level,
      stack,
      message,
      envId,
      functionName,
      timestamp: new Date(timestamp),
    }

    this.collection
      .add(logData)
      .then(() => {
        this.emit('logged', info)
      })
      .catch((e) => {
        this.emit('error', e)
      })

    if (callback) {
      setImmediate(callback)
    }
  }

  /**
   * 实现日志的查询方法
   */
  query(options: Partial<IQueryOptions>, callback) {
    const now = Date.now()
    const { from = 0, until = now, limit = 10, offset = 0, order = 'asc', fields } = options

    const query = this.collection.where({})

    this.collection
      .where({})
      .get()
      .then((res) => {
        callback(null, res)
      })
      .catch((e) => {
        callback(e)
      })
  }
}

总结

winston 可以帮助我们更简单的处理 Node 应用中的日志,也能比较容易的进行扩展定制化,是一个不错的工具。这里只对 winston 进行了简单的介绍,更多的能力请保持关注

Licensed under CC BY-NC-SA 4.0