Любая программа должна записывать бизнес-логи, поэтому в разных языках есть соответствующие библиотеки журналов, такие как 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})
}