nestjs-日志

202 阅读4分钟

前言

实际项目中,线上应用的问题比较难以定位,就算大公司老司机也无可避免,因此日志系统就显得格外重要,通过好的日志我们能够更好的分析线上应用出现问题的原因,一般分析通过线上控制台 + log文件来解决问题

对于一些比较难分析甚至可能需要时间的操作,我们更倾向于使用保存本地日志文件的方式来查找问题,后面将介绍 winston,让我们更方便使用该日志库

nestjs日志(个人感觉一般吧哈)

winston日志(符合企业级使用,nestjs中也有推荐)

下面忽略 nestjs 自带的日志(需要自己看,没啥东西),介绍 winston 日志的使用,并简单介绍请求中间件 + 日志定位问题

如果本文档不满足基础需要,可以点到 winston 中有更详细介绍,这里也只是一个普通的日志使用

加入 winston 日志

yarn add nest-winston winston winston-daily-rotate-file

其有模块化的方式使用,但个人感觉作为同意的日志模块使用很不方便,当然有需要可以这么使用

还有转化 nestjs/common logger 的方式使用,为了使用方便我们只介绍转化Logger这种方式,这样可以全局都使用 nestjs/common logger中的 Logger,毕竟log这东西一看就应该是基础模块,一个个导入还不如直接将其视为 nest/common 的一部分哈,我也不是那么死板

设置 winston Logger

//创建logger
const createLogger = () => {
    //直接转化nestjs的log,使用时需要保存log,使用 Nestjs 的 Logger.log、warn、error,测试debug仍然使用 console.log
    return WinstonModule.createLogger({
        instance: winston.createLogger({
            transports: [
                new winston.transports.Console({
                    level: 'info',
                    format: winston.format.combine(
                        winston.format.timestamp(),
                        utilities.format.nestLike(),
                    ),
                }),
                //后面的按级别添加,提示级别越低包含信息越多
                //error日志存储到/logs/warn-日期.log文件中
                new winston.transports.DailyRotateFile({
                    level: 'error', //错误级别
                    dirname: 'logs',
                    filename: 'error-%DATE%.log',
                    datePattern: 'YYYY-MM-DD-HH',
                    zippedArchive: true,
                    maxSize: '10m',
                    maxFiles: '14d',
                    format: winston.format.combine(
                        winston.format.timestamp(),
                        winston.format.simple(),
                    ),
                }),
                //级别稍低,warn、error日志存储到/logs/warn-日期.log文件中
                new winston.transports.DailyRotateFile({
                    level: 'warn', //包含error,设置这个可以不设置error
                    dirname: 'logs',
                    filename: 'warn-%DATE%.log',
                    datePattern: 'YYYY-MM-DD-HH',
                    zippedArchive: true,
                    maxSize: '10m',
                    maxFiles: '14d',
                    format: winston.format.combine(
                        winston.format.timestamp(),
                        winston.format.simple(),
                    ),
                }),
                //级别较低log、warning、error都会存在,日志存储到/logs/info-日期.log文件中
                new winston.transports.DailyRotateFile({
                    level: 'info',
                    dirname: 'logs',
                    filename: 'info-%DATE%.log',
                    datePattern: 'YYYY-MM-DD-HH',
                    zippedArchive: true,
                    maxSize: '10m',
                    maxFiles: '14d',
                    format: winston.format.combine(
                        winston.format.timestamp(),
                        winston.format.simple(),
                    ),
                }),
            ],
        }),
    });
};

//使用logger
app.useLogger(createLogger());

需要记录本地日志的log,使用 Logger 代替 console.log,记得导入的是 nestjs/common 的 Logger

//记得导入 nestjs/common 的 Logger
Logger.error(123)
Logger.warn(456)
Logger.log(789) 

文件保存到根目录的 logs 的子文件夹中,根据设置的日期格式分配到不同文件中,点开一个.文件可以看出默认缓存时间是14天

image.png

通过中间件+Logger记录请求时间

当我们线上可能出现性能问题,但是打控制台有时候又不方便,我们可以通过中间件 + Logger 的方式记录请求时间,当然这里没有

//设置中间件
@Injectable()
export class AppMiddleware implements NestMiddleware {
    use(req: Request, res: any, next: NextFunction) {
        //假设我们要获取一个请求的开始或者结束时间
        //如果平时我们不需要,发现问题需要,我们也可以另起一个单例,直接使用一个接口设置标识,这里根据标识更新调整是否监听即可
        const startTime = new Date();
		const startString = startTime.toISOString()
		Logger.log(`start # url:${req.url}, startTime:${startString}}`);
        res.on('finish', () => {
            Logger.log(`finish # url:${req.url}, duration:${Date.now() - startTime.getTime()} ms, startTime:${startString}}`);
        });
        next();
    }
}

//应用到全局,也可以应用到部分有问题模块
export class AppModule implements NestModule {
    configure(consumer: MiddlewareConsumer) {
        consumer
            .apply(UserMiddleware)
            .forRoutes(UserController, ArticleController);
    }
}

//改进一下,设置一个最大请求时间,超过时间的才打log
use(req: Request, res: any, next: NextFunction) {
    const maxRequestTime = 500 //假设设置的最大请求时间是500ms
    const startTime = new Date();
    const startString = startTime.toISOString()
    //如果开始日志不打的话,如果碰到死循环、递归的也没办法看到日志了,那种情况这里最好也打印
    // Logger.log(`start # url:${req.url}, startTime:${startString}}`);
    res.on('finish', () => {
        const interval = Date.now() - startTime.getTime()
        if (interval >= maxRequestTime) {
            Logger.log(`finish # url:${req.url}, duration:${Date.now() - startTime.getTime()} ms, startTime:${startString}}`);
        }
    });
    next();
}

设置typeorm数据库监听

这个相对于上面用的稍微少点,当确定了接口性能,想看看具体是哪个数据库操作出现性能问题时,又或者需要定位一些错误的数据库操作问题,则可以通过设置参数查看数据库的一些sql语句等

// logging: true,
// logging: "all", //logging类型
logger: 'file', //login保存到文件,不是在控制台查看
//开了这个不开logging类型,查询时间超过设定ms的会保存,必要可以设置环境变量重启的方式调整参数
maxQueryExecutionTime: 500,

文件保存项目根目录的 ormlogs.log 文件中

image.png