NestJS 日志进阶:Winston 集成 ELK、Graylog 最佳实践
兄弟们,今天咱们聊聊 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 的配置细节,欢迎留言讨论,咱们一起进步!