Nest.js集成Winston后部分日志无法推送至Loki的问题求助
你的定时任务日志只在控制台显示、无法推送到Loki的核心原因是:定时任务中使用的是Nest默认的ConsoleLogger,而非你配置的Winston Logger实例。下面是具体的问题分析和解决方案:
问题根源
当你通过NestFactory.create传入logger: WinstonModule.createLogger({ instance: winstonInstance })时,仅替换了Nest应用的全局Logger,但:
- 手动
new Logger(...)创建的实例默认会使用Nest自带的ConsoleLogger,不会自动关联到你的Winston实例 - 直接注入
Logger类时,默认提供的也是ConsoleLogger实例,而非Winston的
而你的HTTP拦截器日志能正常推送到Loki,大概率是因为拦截器是全局绑定的(比如通过APP_INTERCEPTOR),执行上下文自动关联了应用的全局Winston Logger;但定时任务是独立的提供者,默认使用的是ConsoleLogger,所以日志不会经过LokiTransport。
解决方案
方法1:依赖注入LoggerService(推荐)
这种方式能确保你使用的是配置好的Winston Logger实例:
在AppModule中注册Winston为全局
LoggerService
如果你还没使用WinstonModule.forRoot,可以用它自动注册全局Logger:import { Module } from '@nestjs/common'; import { WinstonModule } from 'nest-winston'; import { loggerConfig } from './logger.config'; import { TemCleanerJob } from './tem-cleaner.job'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { LoggerInterceptor } from './logger.interceptor'; @Module({ imports: [WinstonModule.forRoot(loggerConfig)], // 自动将Winston实例注册为LoggerService providers: [ TemCleanerJob, { provide: APP_INTERCEPTOR, useClass: LoggerInterceptor, }, ], }) export class AppModule {}如果你坚持手动创建Winston实例,也可以手动绑定:
import { Module, LoggerService } from '@nestjs/common'; import { createLogger } from 'winston'; import { loggerConfig } from './logger.config'; const winstonInstance = createLogger(loggerConfig); @Module({ providers: [ { provide: LoggerService, useValue: winstonInstance, }, TemCleanerJob, ], }) export class AppModule {}在定时任务中注入
LoggerService
替换手动创建Logger的方式,直接注入LoggerService:import { Injectable, LoggerService } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; @Injectable() export class TemCleanerJob { private readonly tempDir = './uploads/temp'; private readonly maxAgeHours = 12; constructor( private readonly logger: LoggerService, // 注入Winston实例 private readonly s3Service: S3Service, ) {} @Cron(CronExpression.EVERY_MINUTE) idk() { this.logger.log('log template'); this.logger.warn('warn log template'); this.logger.error('error log template'); throw new Error('asdfasd'); } }
方法2:手动关联Winston实例到Logger类
如果你需要保留Logger类的上下文名称功能,可以在构造时传入全局Winston实例:
import { Inject, Injectable, Logger, LoggerService } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; @Injectable() export class TemCleanerJob { private readonly logger: Logger; private readonly tempDir = './uploads/temp'; private readonly maxAgeHours = 12; constructor( @Inject(LoggerService) private readonly loggerService: LoggerService, private readonly s3Service: S3Service, ) { // 创建Logger时指定使用Winston实例 this.logger = new Logger(TemCleanerJob.name, { logger: loggerService }); } @Cron(CronExpression.EVERY_MINUTE) idk() { this.logger.log('log template'); this.logger.warn('warn log template'); this.logger.error('error log template'); throw new Error('asdfasd'); } }
验证
完成配置后,运行定时任务,检查Grafana Loki是否能收到日志。此时所有日志都会经过Winston的两个Transport(Console和Loki),同时符合Loki要求的JSON格式。
内容的提问来源于stack exchange,提问作者Jorge Montejo

