WEBKT

NestJS 日志进阶:Winston 集成 ELK、Graylog 最佳实践

274 0 0 0

兄弟们,今天咱们聊聊 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-host12201 要替换成你自己的 Graylog 地址和端口。
  • staticMeta 可以添加一些静态的元数据,方便你在 Graylog 里过滤和搜索。

配置完成后,你的 NestJS 应用的日志就会被发送到 Graylog。

最佳实践

  1. 日志级别控制: 根据环境(开发、测试、生产)设置不同的日志级别。开发环境可以设置成 debug,方便调试;生产环境设置成 infowarn,避免过多日志影响性能。

  2. 结构化日志: 尽量使用 JSON 格式输出日志,方便日志聚合系统解析和处理。添加必要的字段,比如时间戳、请求 ID、用户 ID 等,方便后续分析。

  3. 异常处理: 捕获未处理的异常,记录详细的错误信息和堆栈跟踪,方便排查问题。

        //logger.service.ts
          exceptionHandlers: [
            new winston.transports.File({ filename: 'exceptions.log' })
          ],
          rejectionHandlers: [
            new winston.transports.File({ filename: 'rejections.log' })
          ]
    
  4. 日志轮转: 对于输出到文件的日志,配置日志轮转,避免单个日志文件过大。可以用 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'
        })
    
  5. 异步日志: 对于高并发场景,可以考虑使用异步日志,避免日志写入阻塞主线程。Winston 的 transports 默认是同步的,但你可以通过一些方法(比如使用 async 库)实现异步写入。

  6. 日志脱敏: 对于包含敏感信息(比如密码、密钥)的日志,进行脱敏处理,避免泄露。

  7. 统一日志格式: 为了方便日志的后续处理和分析, 建议在整个应用中, 甚至整个组织内使用统一的日志格式. 可以在winston.format.combine中定义通用的日志格式.

  8. 全局日志实例: 在logger.service.ts中创建的winston实例, 可以在整个应用中共享. 避免在每个模块中都创建新的winston实例, 造成资源浪费.

  9. Context信息: 在调用logger的各个方法时, 传入context参数, 可以帮助你更好地追踪日志的来源. 例如, 可以传入ControllerService的类名.

总结

好了,兄弟们,今天咱们把 NestJS 的日志处理给捋了一遍,从 Winston 的基本用法,到集成 ELK Stack 和 Graylog,再到一些最佳实践,希望对你有所帮助。记住,日志是排查问题、监控系统、分析用户行为的利器,一定要用好它!

如果你还有其他关于 NestJS 日志的问题,或者想了解更多关于 ELK Stack、Graylog 的配置细节,欢迎留言讨论,咱们一起进步!

全栈老王 NestJSWinston日志管理

评论点评