nestjs学习之日志系统
日志定位什么问题
- 什么时候发生的
- 发生了什么
- 错误是什么
日志分类(等级)
- log: 通用日志,一般在控制台输出。
- warn:警告日志,例如调用一些过时方法。
- error: 错误日志,例如连接数据库异常等等。方便我们定位问题,给出友好提示。
- debug: 调试日志,例如加载数据日志。方便我们开发。
- verbose: 详细日志,所有的操作与详细信息。
日志库中都会集成上述日志登记方法供我们调用,来处理日志输出。
日志记录位置
- 控制台日志。方便监看(开发调试使用)。
- 文件日志。方便回溯与追踪(24小时滚动)。
- 数据库日志。敏感操作、敏感数据记录。
了解这些后,那么将带领你熟悉一下nestjs内置Logger,之后学习第三方日志winston, pino
,并且在nestjs中进行集成配置。让我们可以有更强大的日志系统。
nestjs日志系统
nestjs内置了日志系统。我们可以通过Logger
去实例化对象进行一些不同等级的日志输出。在初始化时可以传入模块名称,让输出的日志更直观。
private logger = new Logger('用户模块');
constructor(
private userService: UserService,
private config: ConfigService,
) {
this.logger.log('userController init.....');
}
它是只是将日志输出到控制台进行调试使用的。具体内容可以看这里
第三方日志
pino
官方也记录了结合nestjs使用的文档,直接安装nestjs-pino
库就行。
通过依赖注入的方式进行操作。
import { Logger } from 'nestjs-pino';
constructor(
private userService: UserService,
private config: ConfigService,
private logger: Logger,
) {
this.logger.log('userController init.....');
}
@Module({
// 在module中导入的依赖就会被自动实例化。
imports: [
TypeOrmModule.forFeature([User, Logs]),
LoggerModule.forRoot({
// pinoHttp: {
// transport: {
// target: 'pino-pretty',
// options: {
// colorize: true,
// },
// },
// },
}),
],
controllers: [UserController],
providers: [UserService],
})
他会在每次请求自动输出请求日志,非常详细。但是很不美观,没有格式化。
由于pino打印的日志很丑,我们可以安装pino-pretty
来美化打印日志。
import { LoggerModule } from 'nestjs-pino';
@Module({
// 在module中导入的依赖就会被自动实例化。
imports: [
TypeOrmModule.forFeature([User, Logs]),
LoggerModule.forRoot({
pinoHttp: {
transport: {
target: 'pino-pretty',
options: {
colorize: true,
},
},
},
}),
],
controllers: [UserController],
providers: [UserService],
})
然后我们还可以使用
pino-roll
去在生产环境进行日志记录,并保存在指定文件中。
import { LoggerModule } from 'nestjs-pino';
import { join } from 'path';
@Module({
// 在module中导入的依赖就会被自动实例化。
imports: [
TypeOrmModule.forFeature([User, Logs]),
LoggerModule.forRoot({
pinoHttp: {
transport:
process.env.NODE_ENV === 'development'
? {
target: 'pino-pretty',
options: {
colorize: true,
},
}
: {
target: 'pino-roll',
options: {
file: join(__dirname, '../../logs/logs.txt'),
// 这个表示当前文件保存日志的周期,超过当前周期,保存在下一个文件
frequency: 'daily',
// 这个表示当前文件保存的大小,如果超过则保存在下一个文件中
size: '10M',
mkdir: true,
},
},
},
}),
],
controllers: [UserController],
providers: [UserService],
})
winston
我们在nestjs中使用需要配合nest-winston
库进行使用。 他就是实现了nestjs提供的LoggerService接口。然后在nestjs初始化的时候传递给logger参数即可。
// main.ts
import { createLogger } from 'winston';
import * as winston from 'winston';
import {
utilities as nestWinstonModuleUtilities,
WinstonModule,
} from 'nest-winston';
import 'winston-daily-rotate-file';
import { join } from 'path';
async function bootstrap() {
const loggerInstance = createLogger({
transports: [
// 控制台输出格式化
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
nestWinstonModuleUtilities.format.nestLike(),
),
})
],
});
const app = await NestFactory.create(AppModule, {
logger: WinstonModule.createLogger({
instance: loggerInstance,
}),
});
await app.listen(8888);
}
并且需要在module中进行全局导出。
// app.module.ts
@Global()
@Module({
// 导入模块
// ConfigModule.forRoot 加载环境变量
imports: [
UserModule,
],
// 注册控制器
controllers: [AppController],
// 依赖注入,在控制器中自动实例化该服务
providers: [AppService, Logger],
// 导出供其他模块使用
exports: [Logger],
})
通过依赖注入方式使用即可。
constructor(
private readonly appService: AppService,
private logger: Logger,
) {
this.logger.error('出现错误app');
}
我们也可以像pino一样设置滚动日志,将生产环境的日志记录保存在指定文件中。使用winston-daily-rotate-file
进行操作即可。
在实例化的时候传递transport即可。具体的配置详细看这里
const loggerInstance = createLogger({
transports: [
// 控制台输出格式化
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
nestWinstonModuleUtilities.format.nestLike(),
),
}),
// 滚动日子,输出到文件
new winston.transports.DailyRotateFile({
// 只记录warn级别之后的日志。warn, error
level: 'warn',
dirname: join(__dirname, '../logs'),
filename: 'application-%DATE%.log',
datePattern: 'YYYY-MM-DD-HH',
zippedArchive: true,
maxSize: '20m',
// 要保留的最大日志数。
maxFiles: '14d',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.simple(),
),
}),
// 通过level来区分不同的日志输出
new winston.transports.DailyRotateFile({
// 记录全部类型日志
level: 'info',
dirname: join(__dirname, '../logs'),
filename: 'info-%DATE%.log',
datePattern: 'YYYY-MM-DD-HH',
zippedArchive: true,
maxSize: '20m',
maxFiles: '14d',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.simple(),
),
}),
],
});
通过不同的日志等级(这里是根据我们调用的不同等级方法进行区分的)输出不同的文件中。
如果想要做系统化的项目,我们需要将逻辑单独抽离出一个module,不应该将代码放在main.ts中,具体操作看这里
大体步骤就是将逻辑代码提取到logs.module.ts
中
import { ConfigService } from '@nestjs/config';
import { Module } from '@nestjs/common';
import * as winston from 'winston';
import {
utilities as nestWinstonModuleUtilities,
WinstonModule,
WinstonModuleOptions,
} from 'nest-winston';
import 'winston-daily-rotate-file';
import { join } from 'path';
const createDailyRotateTransport = (level: string, filename: string) => {
return new winston.transports.DailyRotateFile({
// 如果是warn时,只记录warn级别之后的日志。warn, error
level,
dirname: join(__dirname, '../../logs'),
filename: `${filename}-%DATE%.log`,
datePattern: 'YYYY-MM-DD-HH',
zippedArchive: true,
maxSize: '20m',
// 要保留的最大日志数。
maxFiles: '14d',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.simple(),
),
});
};
@Module({
imports: [
WinstonModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
// 控制台输出格式化
const consoleTransport = new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
nestWinstonModuleUtilities.format.nestLike(),
),
});
return {
transports: [
consoleTransport,
...(configService.get('LOG_ON')
? [
// 使用函数创建transport,防止无论啥条件都创建logs文件
createDailyRotateTransport('info', 'info'),
createDailyRotateTransport('warn', 'warn'),
]
: []),
],
} as WinstonModuleOptions;
},
}),
],
})
export class LogsModule {}
在main.ts
中设置
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));
在模块中使用
constructor(
private userService: UserService,
private config: ConfigService,
// 该模块需要等到logger初始化完成之后在进行初始化。
@Inject(WINSTON_MODULE_NEST_PROVIDER)
private readonly logger: LoggerService,
) {
this.logger.error('user init error....');
}
到这里我们就可以看出来pino和winston的区别了。前者无需我们打印日志,他会很好的处理我们的任何日志。但是后者的日志输出需要我们进行手动操作。
全局异常过滤器
我们可以借助nestjs提供的过滤器功能来完成全局异常的拦截处理。来集成日志打印等功能。
我们需要实现ExceptionFilter
接口,实现catch方法,并在其内部获取响应请求对象,进行逻辑处理,并做出响应。我们还需要传入logger对象,将日志存入文件中,便于回溯。
import {
ExceptionFilter,
LoggerService,
HttpException,
Catch,
ArgumentsHost,
} from '@nestjs/common';
// 全局处理http异常,统一响应
// 指定只捕获http异常
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
constructor(private logger: LoggerService) {}
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
// 响应对象
const response = ctx.getResponse();
// const request = ctx.getRequest();
const status = exception.getStatus();
// 通过winston处理日志写入文件中
this.logger.error(exception.message, exception.stack);
// 响应错误请求
response.status(status).json({
code: status,
timestamp: new Date().toISOString(),
message: exception.message || exception.name,
});
}
}
往期年度总结
往期文章
专栏文章
转载自:https://juejin.cn/post/7313776114911805481