NestJS 日志进阶:Winston 集成 ELK、Graylog 最佳实践
为啥要搞日志聚合?
Winston:NestJS 的日志利器
安装 Winston
基本用法
集成 ELK Stack
安装 winston-elasticsearch
配置 winston-elasticsearch
集成 Graylog
安装 winston-graylog2
配置 winston-graylog2
最佳实践
总结
兄弟们,今天咱们聊聊 NestJS 的日志处理,特别是如何用 Winston 这个强大的日志库,把你的 NestJS 应用日志跟 ELK Stack (Elasticsearch, Logstash, Kibana) 和 Graylog 这些流行的日志聚合服务给串起来。别担心,我会一步步带你搞定,保证干货满满,让你看完就能上手!
为啥要搞日志聚合?
在咱开发和运维的过程中,日志可是个宝贝。出了问题,靠它排查;系统表现,靠它监控;用户行为,靠它分析。但是,如果你的应用部署在多个服务器上,或者你的系统变得越来越复杂,直接看分散在各个地方的日志文件,那可就太痛苦了。这时候,日志聚合就派上用场了。
日志聚合,简单来说,就是把各个地方的日志收集到一个统一的地方,方便你集中管理、搜索、分析。ELK Stack 和 Graylog 就是干这个的,它们能帮你把日志这事儿给整得明明白白。
Winston:NestJS 的日志利器
NestJS 默认用的是 ConsoleLogger
,简单场景下够用,但要玩转日志聚合,就得请出 Winston 了。Winston 是 Node.js 社区里最流行的日志库之一,功能强大,配置灵活,社区支持也贼好。
安装 Winston
npm install winston
基本用法
在 NestJS 里用 Winston,你可以创建一个自定义的 logger 服务,然后在各个模块里注入使用。下面是一个简单的例子:
// logger.service.ts import { Injectable, LoggerService } from '@nestjs/common'; import * as winston from 'winston'; @Injectable() export class MyLogger implements LoggerService { private readonly logger: winston.Logger; constructor() { this.logger = winston.createLogger({ level: 'info', // 设置日志级别 format: winston.format.combine( winston.format.timestamp(), // 添加时间戳 winston.format.json() // 输出 JSON 格式 ), transports: [ new winston.transports.Console(), // 输出到控制台 // 可以添加其他 transports,比如输出到文件 ], }); } log(message: string, context?: string) { this.logger.info(message, { context }); } error(message: string, trace: string, context?: string) { this.logger.error(message, { trace, context }); } warn(message: string, context?: string) { this.logger.warn(message, { context }); } debug(message: string, context?: string) { this.logger.debug(message, { context }); } verbose(message: string, context?: string) { this.logger.verbose(message, { context }); } }
// app.module.ts import { Module } from '@nestjs/common'; import { MyLogger } from './logger.service'; @Module({ providers: [MyLogger], exports: [MyLogger], // 导出,方便其他模块使用 }) export class AppModule {}
// 某个 controller.ts import { Controller, Get, Inject } from '@nestjs/common'; import { MyLogger } from './logger.service'; @Controller('test') export class TestController { constructor(private readonly logger: MyLogger) {} @Get() getHello(): string { this.logger.log('Hello World!', TestController.name); return 'Hello World!'; } }
集成 ELK Stack
要把 Winston 的日志输出到 ELK Stack,你需要用到一个叫 winston-elasticsearch
的 transport。
安装 winston-elasticsearch
npm install winston-elasticsearch @elastic/elasticsearch
配置 winston-elasticsearch
// logger.service.ts import { Injectable, LoggerService } from '@nestjs/common'; import * as winston from 'winston'; import * as Elasticsearch from 'winston-elasticsearch'; @Injectable() export class MyLogger implements LoggerService { private readonly logger: winston.Logger; constructor() { const esTransportOpts = { level: 'info', clientOpts: { node: 'http://your-elasticsearch-host:9200', // Elasticsearch 地址 // 如果有认证,需要配置用户名密码 // auth: { // username: 'your_username', // password: 'your_password' // } }, indexPrefix: 'nestjs-logs', // 索引前缀 }; this.logger = winston.createLogger({ format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), transports: [ new winston.transports.Console(), new Elasticsearch(esTransportOpts), ], }); } // ...log, error, warn, debug, verbose 方法... }
注意:
your-elasticsearch-host:9200
要替换成你自己的 Elasticsearch 地址。- 如果 Elasticsearch 有认证,需要配置
auth
选项。 indexPrefix
可以自定义,用于区分不同应用的日志。
配置完成后,你的 NestJS 应用的日志就会被发送到 Elasticsearch,你可以在 Kibana 里搜索和查看。
集成 Graylog
Graylog 也是一个很受欢迎的日志管理系统。要把 Winston 的日志输出到 Graylog,你可以用 winston-graylog2
这个 transport。
安装 winston-graylog2
npm install winston-graylog2
配置 winston-graylog2
// logger.service.ts import { Injectable, LoggerService } from '@nestjs/common'; import * as winston from 'winston'; import * as Graylog2 from 'winston-graylog2'; @Injectable() export class MyLogger implements LoggerService { private readonly logger: winston.Logger; constructor() { const graylogOptions = { name: 'Graylog', level: 'info', graylog: { servers: [{ host: 'your-graylog-host', port: 12201 }], // Graylog 地址和端口 // hostname: 'my-app', // 可选,主机名 // facility: 'NestJS', // 可选,设施名 }, staticMeta: { service: 'my-nestjs-app' }, // 添加静态元数据 }; this.logger = winston.createLogger({ format: winston.format.combine( winston.format.timestamp(), winston.format.json(), ), transports: [ new winston.transports.Console(), new Graylog2(graylogOptions), ], }); } // ...log, error, warn, debug, verbose 方法... }
注意:
your-graylog-host
和12201
要替换成你自己的 Graylog 地址和端口。staticMeta
可以添加一些静态的元数据,方便你在 Graylog 里过滤和搜索。
配置完成后,你的 NestJS 应用的日志就会被发送到 Graylog。
最佳实践
日志级别控制: 根据环境(开发、测试、生产)设置不同的日志级别。开发环境可以设置成
debug
,方便调试;生产环境设置成info
或warn
,避免过多日志影响性能。结构化日志: 尽量使用 JSON 格式输出日志,方便日志聚合系统解析和处理。添加必要的字段,比如时间戳、请求 ID、用户 ID 等,方便后续分析。
异常处理: 捕获未处理的异常,记录详细的错误信息和堆栈跟踪,方便排查问题。
//logger.service.ts exceptionHandlers: [ new winston.transports.File({ filename: 'exceptions.log' }) ], rejectionHandlers: [ new winston.transports.File({ filename: 'rejections.log' }) ] 日志轮转: 对于输出到文件的日志,配置日志轮转,避免单个日志文件过大。可以用
winston-daily-rotate-file
这个 transport。npm install winston-daily-rotate-file
// logger.service.ts new winston.transports.DailyRotateFile({ filename: 'application-%DATE%.log', datePattern: 'YYYY-MM-DD-HH', zippedArchive: true, maxSize: '20m', maxFiles: '14d' }) 异步日志: 对于高并发场景,可以考虑使用异步日志,避免日志写入阻塞主线程。Winston 的
transports
默认是同步的,但你可以通过一些方法(比如使用async
库)实现异步写入。日志脱敏: 对于包含敏感信息(比如密码、密钥)的日志,进行脱敏处理,避免泄露。
统一日志格式: 为了方便日志的后续处理和分析, 建议在整个应用中, 甚至整个组织内使用统一的日志格式. 可以在
winston.format.combine
中定义通用的日志格式.全局日志实例: 在
logger.service.ts
中创建的winston
实例, 可以在整个应用中共享. 避免在每个模块中都创建新的winston
实例, 造成资源浪费.Context信息: 在调用
logger
的各个方法时, 传入context
参数, 可以帮助你更好地追踪日志的来源. 例如, 可以传入Controller
或Service
的类名.
总结
好了,兄弟们,今天咱们把 NestJS 的日志处理给捋了一遍,从 Winston 的基本用法,到集成 ELK Stack 和 Graylog,再到一些最佳实践,希望对你有所帮助。记住,日志是排查问题、监控系统、分析用户行为的利器,一定要用好它!
如果你还有其他关于 NestJS 日志的问题,或者想了解更多关于 ELK Stack、Graylog 的配置细节,欢迎留言讨论,咱们一起进步!