Артефакт журнала Node.js (винстон)

Node.js

Любая программа должна записывать бизнес-логи, поэтому в разных языках есть соответствующие библиотеки журналов, такие как Log2j в Java, а в Node.js также есть много вариантов, таких какwinston,log4js,bunyanИ так далее, где winston прост в использовании и поддерживает несколько каналов передачи.

основное использование

const winston = require('winston')
winston.log('info', 'Hello World!')
winston.info('Hello World')

По умолчанию журналы выводятся на консоль. Мы также можем создать несколько экземпляров с помощью:

const logger1 = winston.createLogger()
const logger2 = winston.createLogger()
logger1.info('logger1')
logger2.info('logger2')

канал передачи

После того, как Winston получит журнал, он передаст журнал в виде сообщения по другому каналу (транспортному).Наша широко используемая консольная печать и хранилище файлов являются каналами передачи.

Встроенный канал передачи

Большую часть времени мы хотим получать логи на консоль и сохранять их в файл, что в винстоне очень просто:

const logger = winston.createLogger({
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({filename: 'combined.log'})
  ]
})
logger.info('console and file')

В этом случае консоль одновременно записывает выходные данные.combined.logВ файле у винстона по умолчанию 4 канала передачи:

  • Консоль: печать на консоль
  • Файл: запись в файл
  • Http: передача по http
  • Поток: передача по потоку

Следующий код демонстрирует использование всех вышеперечисленных встроенных каналов:

// 创建可写流
const {Writable} = require('stream')
const stream = new Writable({
  objectMode: false,
  write: raw => console.log('stream msg', raw.toString())
})
// 创建http服务
const http = require('http')
http
  .createServer((req, res) => {
    const arr = []
    req
      .on('data', chunk => arr.push(chunk))
      .on('end', () => {
        const msg = Buffer.concat(arr).toString()
        console.log('http msg', msg)
        res.end(msg)
      })
  })
  .listen(8080)
// 配置 4 种通道
const logger = winston.createLogger({
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({filename: 'combined.log'}),
    new winston.transports.Http({host: 'localhost', port: 8080}),
    new winston.transports.Stream({stream})
  ]
})
// 传输到通道
logger.info('winston transports')

Наконец, можно обнаружить, что консоль, файл, HTTP-сервер и доступный для записи поток могут получать следующее сообщение:

{"message":"winston transports","level":"info"}

Пользовательский канал передачи

Встроенный канал уже очень мощный.Если вам кажется, что этого недостаточно, вы можете сами написать канал, например, передачу в MongoDB или Kafka или ElasticSearch и т.д.

class CustomTransport extends winston.Transport {
  constructor(opts) {
    super(opts)
  }

  log(info, callback) {
    console.log('info',info)
    callback()
  }
}

Просто напишите класс, который наследуется отwinston.TransportЗатем запускает класс после получения журнала winstonlogМетод выполняется, а параметром является объект сообщения, содержащий лог, поэтому процесс настройки канала передачи таков:

  • существуетconstructorУстановите удаленное соединение в конструкторе (MongoDB, Kafka, ElasticSearch...)
  • существуетlogспособ обработки и отправки сообщений

формат

По умолчанию вывод журнала winston осуществляется в формате JSON, содержимое журнала находится в поле сообщения, и в JSON есть некоторые другие поля, такие как уровень и так далее. Winston также имеет множество встроенных инструментов форматирования, таких как:

const logger = winston.createLogger({
  format: winston.format.combine(
    winston.format.label({ label: 'right meow!' }),
    winston.format.timestamp(),
    winston.format.prettyPrint(),
  ),
  transports: [new winston.transports.Console()]
})
logger.info('hello world')

выведет:

{
  message: 'hello world',
  level: 'info',
  label: 'right meow!',
  timestamp: '2020-08-28T06:30:32.836Z'
}

Мы можем полностью контролировать формат журнала самостоятельно, например

const customFormat = winston.format.printf((info) => {
  return `[do whatever you want] ${info.timestamp}:${info.label}:${info.message}`
})

Пока вы пишете функцию, параметром является объект сообщения, инкапсулированный winston.В функции вы можете делать все, что хотите.Наконец, вам нужно только вернуть отформатированную строку.

резка бревен

Если все журналы записывать в файл, файл через долгое время станет очень большим, а также очень хлопотным в обработке.В это время требуется резка журнала.Есть два широко используемых метода резки:

  • Вырезать по размеру файла
  • Сократить время записи

обрезать по размеру

Просто добавьте параметр maxsize при создании файлового канала, например:

const maxsizeTransport = new winston.transports.File({
  level: 'info',
  format: winston.format.printf(info => info.message),
  filename: path.join(__dirname, '..', 'logs', 'testmaxsize.log'),
  maxsize: 1024
})

Когда файл превышает 1024, он будет создаваться последовательноtestmaxsize1.log,testmaxsize2.logи т.п.

сократить по времени

Официальный предоставляет библиотеку сокращения времени под названиемwinston-daily-rotate-file, который можно сократить по дням:

new transports.DailyRotateFile({
  filename: path.join(__dirname, '..', 'logs', `%DATE%.log`),
  datePattern: 'YYYY-MM-DD',
  prepend: true,
  json: false
})

будет генерироваться последовательно2020-01-01.log,2020-01-02.logи т.п.

Динамический мультиэкземпляр

При росте масштаба приложения может возникнуть необходимость настройки разных журналов для разных функциональных областей, например, форматы журналов заказов и логинов разные, и их нужно записывать в разные файлы, а копию заказа нужно храниться в ElasticSearch. Несколько экземпляров можно добавить следующими способами:

winston.loggers.add('order', {format: orderFormat, transports: orderTransports})
winston.loggers.add('login', {format: loginFormat, transports: loginTransports})
const orderLog = winston.loggers.get('order')
const loginLog = winston.loggers.get('login')
orderLog.info('订单日志')
loginLog.error('登录错误')

При инициализации проекта вы можете сначала создать экземпляр по умолчанию, а затем динамически добавлять экземпляры домена по мере роста масштаба бизнеса:

if(!winston.loggers.has('xxx')) {
  winston.loggers.add('xxx', {format: xxxFormat, transports: xxxTransports})
}