NestJS博客实战04-导入Log日志模块
接着上一章,这章主要说明怎么写自定义Log日志模块。官网Logger说明
Nest有自己的内置Logger
,可以用来显示程序中捕获的异常。这个Logger
方法是有由@nestjs/common
这个包提供的.您可以完全控制包含以下内容的日志系统的行为:
- 完全禁用日志记录
- 指定日志的详细级别(例如,显示errors、warnings、debug信息等)
- 覆盖默认Logger中的时间戳(例如,使用ISO8601标准作为日期格式)
- 完全覆盖默认Logger
- 通过扩展来自定义默认Logger
- 利用依赖项注入来简化应用程序的组成和测试
您能够利用内置的Logger创建自定义的实现,来为程序打印程序级别的时间和消息。
对于更高级的日志记录功能,您可以使用任何Node.js日志记录包,如Winston,来实现一个完全自定义的生产级日志记录系统。
基本的自定义
为了禁用日志,首先要设置logger
为false
,并且在NestFactory.create()
的时候传入这个可选参数。
const app = await NestFactory.create(AppModule, {
logger: false,
});
await app.listen(3000);
想选择特定的日志级别,可以为logger
设置字符串数组
const app = await NestFactory.create(AppModule, {
logger: ['error', 'warn'],
});
await app.listen(3000);
可以选择的级别有:'log'
, 'error'
, 'warn'
, 'debug'
, 和 'verbose'
.
要禁用默认记录器消息中的颜色,请将NO_COLOR环境变量设置为一些非空字符串。
自定义实现
通过将logger属性的值设置为满足LoggerService
接口的对象,您可以提供Nest用于系统Logger
的自定义记录器实现。例如,您可以告诉Nest使用内置的全局JavaScript控制台对象(实现LoggerService
接口),如下所示:
const app = await NestFactory.create(AppModule, {
logger: console,
});
await app.listen(3000);
想实现自己的Logger
,只要实现LoggerService
这个接口就行了
import { LoggerService } from '@nestjs/common';
export class MyLogger implements LoggerService {
/**
* 写log'等级日志.
*/
log(message: any, ...optionalParams: any[]) {}
/**
* 写'error'等级日志.
*/
error(message: any, ...optionalParams: any[]) {}
/**
* 写'warn'等级日志.
*/
warn(message: any, ...optionalParams: any[]) {}
/**
* 写'debug'等级日志.
*/
debug?(message: any, ...optionalParams: any[]) {}
/**
* 写'verbose'等级日志.
*/
verbose?(message: any, ...optionalParams: any[]) {}
}
然后您可以传入自己定义的MyLogger
const app = await NestFactory.create(AppModule, {
logger: new MyLogger(),
});
await app.listen(3000);
这种技术虽然简单,但没有为MyLogger
类使用依赖项注入。这可能会带来一些挑战,尤其是对测试而言,并限制MyLogger
的可重用性。有关更好的解决方案,请参阅下面的依赖注入
部分。
继承内置的Logger
您可以通过扩展内置的ConsoleLogger类并覆盖默认实现的选定行为来满足您的需求,而不是从头开始编写Logger。
import { ConsoleLogger } from '@nestjs/common';
export class MyLogger extends ConsoleLogger {
error(message: any, stack?: string, context?: string) {
// 在这里写逻辑
super.error(...arguments);
}
}
参照下面Logger为程序记录日志
,您可以为自己的模块添加继承内置的Logger
您可以告诉Nest使用扩展Logger进行系统日志记录,方法是通过应用程序选项对象的Logger
属性传递它的实例(如上面的自定义实现
部分所示),或者使用下面的依赖注入
部分所示的技术。如果这样做,您应该注意调用super
,如上面的示例代码所示,将特定的日志方法调用委托给父类(内置),这样Nest就可以依赖于它所期望的内置功能。
依赖注入
对于更高级的日志记录功能,您需要利用依赖项注入。例如,您可能想注入上一章讲的ConfigService
。为了能够注入自定义Logger,需要创建一个类并实现LoggerService
然后再使用的模块里面注册provider
。
举例:
- 定义
MyLogger
类,它可以是继承内置的ConsoleLogger
或者完全的重写它。 - 创建
LoggerModule
,提供MyLogger
import { Module } from '@nestjs/common';
import { MyLogger } from './my-logger.service';
@Module({
providers: [MyLogger],
exports: [MyLogger],
})
export class LoggerModule {}
有了这个构造,您现在可以提供自定义Logger供任何其他模块使用。因为MyLogger
类是模块的一部分,所以它可以使用依赖项注入(例如,注入ConfigService
)。还有一种技术需要提供这个自定义记录器,供Nest用于系统日志记录(例如,用于引导和错误处理)。
因为应用程序实例化(NestFactory.create()
)发生在任何模块的上下文之外,所以它不参与初始化的正常依赖注入阶段。因此,我们必须确保至少有一个应用程序模块导入LoggerModule
,以触发Nest实例化MyLogger
类的单个实例。
然后,我们可以指示Nest使用以下构造的MyLogge
r的相同单例实例:
const app = await NestFactory.create(AppModule, {
bufferLogs: true,
});
app.useLogger(app.get(MyLogger));
await app.listen(3000);
在这里,我们在NestApplication
实例上使用get()
方法来检索MyLogger
对象的singleton实例。这种技术本质上是一种“注入”记录器实例供Nest使用的方法。app.get()
调用检索MyLogger
的singleton实例,并取决于该实例首先被注入到另一个模块中,如上所述。
用Logger为程序记录日志
我们可以将上面的几种技术结合起来,在Nest系统日志和我们自己的应用程序事件/消息日志中提供一致的行为和格式。
一个好的做法是在我们的每个服务中从@nestjs/common
实例化Logger
类。我们可以在Logger
构造函数中提供服务名称作为context
上下文参数,如下所示:
import { Logger, Injectable } from '@nestjs/common';
@Injectable()
class MyService {
private readonly logger = new Logger(MyService.name);
doSomething() {
this.logger.log('Doing something...');
}
}
在默认的Logger实现中,上下文打印在方括号中,如下面示例中的NestFactory:
[Nest] 19096 - 12/08/2019, 7:12:59 AM [NestFactory] Starting Nest application...
如果我们通过app.useLogger()
提供自定义Logger,Nest实际上会在内部使用它。这意味着我们的代码仍然与实现无关,而我们可以通过调用app.useLogger()
来轻松地用默认Logger代替自定义Logger。
这样,如果我们按照上一节中的步骤调用app.useLogger(app.get(MyLogger))
,那么下面从MyService调用this.logger.log()
将导致从MyLogger
实例调用方法log
。
这应该适用于大多数情况。
注入自定义 logger
首先,使用如下代码扩展内置Logger。我们提供scope
选项作为ConsoleLogger
类的配置元数据,指定一个临时作用域,以确保在每个功能模块中都有一个唯一的MyLogger实例。在本例中,我们不扩展单独的ConsoleLogger
方法(如log()
、warn()
等),尽管您可以选择这样做。
import { Injectable, Scope, ConsoleLogger } from '@nestjs/common';
@Injectable({ scope: Scope.TRANSIENT })
export class MyLogger extends ConsoleLogger {
customLog() {
this.log('Please feed the cat!');
}
}
接下来,创建LoggerModule
import { Module } from '@nestjs/common';
import { MyLogger } from './my-logger.service';
@Module({
providers: [MyLogger],
exports: [MyLogger],
})
export class LoggerModule {}
接下来,将LoggerModule
导入到您的功能模块中。由于我们扩展了默认Logger
,因此可以方便地使用setContext
方法。因此,我们可以开始使用上下文感知的自定义记录器,如下所示:
import { Injectable } from '@nestjs/common';
import { MyLogger } from './my-logger.service';
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
constructor(private myLogger: MyLogger) {
//由于临时作用域,CatsService有自己唯一的MyLogger实例,因此在此处设置上下文不会影响其他服务中的其他实例
this.myLogger.setContext('CatsService');
}
findAll(): Cat[] {
// 您可以调用所有默认方法
this.myLogger.warn('About to return cats!');
// 以及您的自定义方法
this.myLogger.customLog();
return this.cats;
}
}
最后,指示Nest在main.ts
文件中使用自定义Logger的实例,如下所示。当然,在这个例子中,我们实际上还没有自定义Logger行为(通过扩展logger
方法,如log()
、warn()
等),所以实际上不需要这个步骤。但是,如果您将自定义逻辑添加到这些方法中,并希望Nest使用相同的实现,那么就需要这样做。
const app = await NestFactory.create(AppModule, {
bufferLogs: true,
});
app.useLogger(new MyLogger());
await app.listen(3000);
使用扩展Logger
内置的Logger只能在命令行显示。这在开发环境中是非常好用,但是在生产环境中,我们可能希望有更强大功能。比如把日志信息写入文件。winston可以满足这些需求。
1. 导入winston
所需要的依赖包
pnpm i winston nest-winston winston-daily-rotate-file --save
2. 新建一个Logger模块
nest g resource common/logs-config --no-spec
# 选择 REST API
# CRUD 选择不要 n
# 最后把生成出来的logs-config.controller.ts文件删除
3. 写module模块
import { Module } from '@nestjs/common';
import { WinstonModule, WinstonModuleOptions, utilities } from 'nest-winston';
import { ConfigService } from '@nestjs/config';
import * as winston from 'winston';
import { Console } from 'winston/lib/winston/transports';
import * as DailyRotateFile from 'winston-daily-rotate-file';
import { LogsConfigService } from './logs-config.service';
import { ConfigEnum } from '../enum/config.enum';
function createDailyRotateTrasnport(level: string, filename: string) {
return new DailyRotateFile({
level,
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 config = configService.get(ConfigEnum.LOG_CONFIG);
const timestamp = config.TIMESTAMP;
const conbine = [];
if (timestamp) {
conbine.push(winston.format.timestamp());
}
conbine.push(utilities.format.nestLike());
const consoleTransports = new Console({
level: config.LOG_LEVEL || 'info',
format: winston.format.combine(...conbine),
});
return {
transports: [
consoleTransports,
...(config.LOG_ON
? [
createDailyRotateTrasnport('info', 'application'),
createDailyRotateTrasnport('warn', 'error'),
]
: []),
],
} as WinstonModuleOptions;
},
}),
],
providers: [LogsConfigService],
})
export class LogsConfigModule {}
4. 运行确认
配置文件.dev.yaml
如下
LOG_CONFIG:
TIMESTAMP: true
LOG_LEVEL: 'info'
LOG_ON: true
启动程序:
npm run start:dev
访问接口,确认是否生成logs文件
本章代码
转载自:https://juejin.cn/post/7218835791955066935