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