## 日志
`Nest` 附帶一個默認的內部日志記錄器實現,它在實例化過程中以及在一些不同的情況下使用,比如發生異常等等(例如系統記錄)。這由 `@nestjs/common` 包中的 `Logger` 類實現。你可以全面控制如下的日志系統的行為:
- 完全禁用日志
- 指定日志系統詳細水平(例如,展示錯誤,警告,調試信息等)
- 覆蓋默認日志記錄器的時間戳(例如使用 ISO8601 標準作為日期格式)
- 完全覆蓋默認日志記錄器
- 通過擴展自定義默認日志記錄器
- 使用依賴注入來簡化編寫和測試你的應用
你也可以使用內置日志記錄器,或者創建你自己的應用來記錄你自己應用水平的事件和消息。
更多高級的日志功能,可以使用任何 `Node.js` 日志包,比如[Winston](https://github.com/winstonjs/winston),來生成一個完全自定義的生產環境水平的日志系統。
### 基礎自定義
要禁用日志,在(可選的)Nest 應用選項對象中向 `NestFactory.create()` 傳遞第二個參數設置 `logger` 屬性為 `false` 。
```typescript
const app = await NestFactory.create(ApplicationModule, {
logger: false,
});
await app.listen(3000);
```
你也可以只啟用特定日志級別,設置一個字符串形式的 `logger` 屬性數組以確定要顯示的日志水平,如下:
```typescript
const app = await NestFactory.create(ApplicationModule, {
logger: ['error', 'warn'],
});
await app.listen(3000);
```
數組中的字符串可以是以下字符串的任意組合: `log` , `error` , `warn` , `debug` 和 `verbose` 。
>你可以通過設置 `NO_COLOR` 環境變量為非空字符串來禁用默認日志信息的顏色
### 自定義應用
你可以提供一個自定義日志記錄器應用,并由 Nest 作為系統記錄使用,這需要設置`logger` 屬性到一個滿足 `LoggerService` 接口的對象。例如,你可以告訴 Nest 使用內置的全局 JavaScript `console` 對象(其實現了 `LoggerService` 接口),如下:
```typescript
const app = await NestFactory.create(ApplicationModule, {
logger: console,
});
await app.listen(3000);
```
應用你的自定義記錄器很簡單。只要簡單實現以下 `LoggerService` 接口中的每個方法就可以:
```typescript
import { LoggerService } from '@nestjs/common';
export class MyLogger implements LoggerService {
/**
* Write a 'log' level log.
*/
log(message: any, ...optionalParams: any[]) {}
/**
* Write an 'error' level log.
*/
error(message: any, ...optionalParams: any[]) {}
/**
* Write a 'warn' level log.
*/
warn(message: any, ...optionalParams: any[]) {}
/**
* Write a 'debug' level log.
*/
debug?(message: any, ...optionalParams: any[]) {}
/**
* Write a 'verbose' level log.
*/
verbose?(message: any, ...optionalParams: any[]) {}
}
```
你可以通過 `logger` 屬性為 Nest 應用的選項對象提供一個 `MyLogger` 實例:
```typescript
const app = await NestFactory.create(ApplicationModule, {
logger: new MyLogger(),
});
await app.listen(3000);
```
這個技術雖然很簡單,但是沒有為 `MyLogger` 類應用依賴注入。這會帶來一些挑戰,尤其在測試方面,同時也限制了 `MyLogger` 的重用性。更好的解決方案參見如下的[依賴注入](https://docs.nestjs.com/techniques/logger#dependency-injection)部分。
### 擴展內置的日志類
很多實例操作需要創建自己的日志。你不必完全重新發明輪子。只需繼承內置 `ConsoleLogger` 類以部分覆蓋默認實現,并使用 `super` 將調用委托給父類。
```typescript
import { ConsoleLogger } from '@nestjs/common';
export class MyLogger extends ConsoleLogger {
error(message: any, stack?: string, context?: string) {
// add your tailored logic here
super.error.apply(this, arguments);
}
}
```
你可以按如下[使用應用記錄器來記錄](https://docs.nestjs.com/techniques/logger#using-the-logger-for-application-logging)部分所述,從你的特征模塊中使用擴展記錄器。
你可以把你的擴展日志記錄器的實例傳遞到應用選項對象的 `logger` 屬性來讓 Nest 使用你的日志記錄器記錄系統日志(如[自定義應用](https://docs.nestjs.com/techniques/logger#custom-logger-implementation)所述),也可以按照如下的[依賴注入](https://docs.nestjs.com/techniques/logger#dependency-injection)部分。如果你這樣做,你在調用 `super` 時要小心,如上述代碼示例,要委托一個特定的日志方法,調用其父(內置)類,以便 Nest 可以依賴需要的內置特征。
### 依賴注入
你可能需要利用依賴注入的優勢來使用高級的日志記錄功能。例如,你可能想把 `ConfigService` 注入到你的記錄器中來對它自定義,然后把自定義記錄器注入到其他控制器和/或提供者中。要為你的自定義記錄器啟用依賴注入,創建一個實現 `LoggerService` 的類并將其作為提供者注冊在某些模塊中,例如,你可以:
1. 定義一個 `MyLogger` 類來繼承內置的 `ConsoleLogger` 或者完全覆蓋它,如前節所述。注意一定要實現 `LoggerService` 接口。
2. 創建一個 `LoggerModule` 如下所示,從該模塊中提供 `MyLogger` 。
```typescript
import { Module } from '@nestjs/common';
import { MyLogger } from './my-logger.service.ts';
@Module({
providers: [MyLogger],
exports: [MyLogger],
})
export class LoggerModule {}
```
通過這個結構,你現在可以提供你的自定義記錄器供其他任何模塊使用。因為你的 `MyLogger` 類是模塊的一部分,它也可以使用依賴注入(例如,注入一個 `ConfigService` )。提供自定義記錄器供使用還需要一個技術,即 Nest 的系統記錄(例如,供 `bootstrapping` 和 `error handling` )。
由于應用實例化( `NestFactory.create()` )在任何模塊上下文之外發生,它不能參與初始化時正常的依賴注入階段。因此我們必須保證至少一個應用模塊導入了 `LoggerModule` 來觸發 Nest ,從而生成一個我們的 `MyLogger` 類的單例。
我們可以在之后按照下列知道來告訴 Nest 使用同一個 `MyLogger` 實例。
```typescript
const app = await NestFactory.create(ApplicationModule, {
bufferLogs: true,
});
app.useLogger(app.get(MyLogger));
await app.listen(3000);
```
>在上面的例子中,我們把 `bufferLogs` 設置為 `true` 以確保所有的日志都會被放入緩沖區直到一個自定義的日志記錄器被接入(在上面的例子中是 `MyLogger` )并且應用初始化成功或者失敗。如果初始化失敗,Nest 會回退到原始的 `ConsoleLogger` 以打印出錯誤信息。你也可以將 `autoFlushLogs` 設置為 `false` (默認為 `true` )來手動刷新日志緩沖區(使用 `Logger#flush()` 方法)。
在這里我們在 `NestApplication` 實例中用了 `get()` 方法以獲取 `MyLogger` 對象的單例。這個技術在根本上是個“注入”一個日志記錄器的實例供 Nest 使用的方法。 `app.get()` 調用獲取 `MyLogger` 單例,并且像之前所述的那樣依賴于第一個注入到其他模塊的實例。
你也可以在你的特征類中注入這個 `MyLogger` 提供者,從而保證 Nest 系統記錄和應用記錄行為一致。參考[為應用記錄使用記錄器](https://docs.nestjs.com/techniques/logger#using-the-logger-for-application-logging)和[注入一個自定義日志記錄器](https://docs.nestjs.com/techniques/logger#injecting-a-custom-logger)章節以獲取更多信息。
### 為應用記錄使用記錄器
我們可以組合上述幾種技術來提供一致性的行為和格式化以保證我們的應用事件/消息記錄和 Nest 系統記錄一致。
一個很好的實踐是在每個提供者內實例化 `@nestjs/common` 內的 `Logger` 類。我們可以將提供者的名字當作 `context` 參數傳入 `Logger` 的構造函數,就像這樣:
```typescript
import { Logger, Injectable } from '@nestjs/common';
@Injectable()
class MyService {
private readonly logger = new Logger(MyService.name);
doSomething() {
this.logger.log('Doing something...');
}
}
```
在默認的日志記錄器實現中, `context` 是包裹在方括號中被打印出來,就像下面例子中的 `NestFactory` :
```
[Nest] 19096 - 12/08/2019, 7:12:59 AM [NestFactory] Starting Nest application...
```
如果我們通過 `app.useLogger()` 提供一個自定義日志記錄器,那么它會在 Nest 內部被使用。這就意味著我們的代碼可以保持與實現無關,因為我們可以簡單地調用 `app.useLogger()` 用默認日志記錄器來代替自定義的那個。
如果我們跟著前面章節一步步做下來并且調用了 `app.useLogger(app.get(MyLogger))` ,那么接下來在 `MyService` 中對 `this.logger.log()` 的調用會造成對 `MyLogger` 實例中方法 `log` 的調用。
這個應該在大多數情況下都適用。但是你如果想要更深入的自定義(比如增加或者調用自定義方法),請看下一章節。
### 注入自定義日志記錄器
通過像下面一樣的擴展內置的日志記錄器來開始這一章節。我們傳入 `scope` 選項來配置 `ConsoleLogger` 的元數據,通過指定[瞬態作用域](https://docs.nestjs.com/fundamentals/injection-scopes)來保證在每個模塊內都有獨一無二的 `MyLogger` 的實例。在下面的例子中,我們沒有擴展每個單獨的 `ConsoleLogger` 方法(比如 `log()` , `warn()` 之類),盡管你可能會選擇去這么做。
```typescript
import { Injectable, Scope, ConsoleLogger } from '@nestjs/common';
@Injectable({ scope: Scope.TRANSIENT })
export class MyLogger extends ConsoleLogger {
customLog() {
this.log('Please feed the cat!');
}
}
```
接下來,我們采用如下結構創建一個 `LoggerModule` 。
```typescript
import { Module } from '@nestjs/common';
import { MyLogger } from './my-logger.service';
@Module({
providers: [MyLogger],
exports: [MyLogger],
})
export class LoggerModule {}
```
然后,在你的模塊中導入 `LoggerModule` 。因為我們繼承了默認的 `Logger` ,所以我們可以很方便地調用 `setContext` 方法。之后我們就可以像下面一樣開始使用這個包含了上下文的日志記錄器了。
```typescript
@Injectable()
export class CatsService {
private readonly cats: Cat[] = [];
constructor(private myLogger: MyLogger) {
// 因為瞬態作用域的原因, CatService 有屬于自己的獨一無二的 MyLogger 實例,
// 所以在這里設置上下文不會影響到其他提供者的實例
this.myLogger.setContext('CatsService');
}
findAll(): Cat[] {
// 你可以調用所有的默認方法
this.myLogger.warn('About to return cats!');
// 當然還有你的自定義方法
this.myLogger.customLog();
return this.cats;
}
}
```
最后,和下面一樣在你的 `main.ts` 文件中讓 Nest 使用自定義日志記錄器的實例。當然這只是個例子,我們還沒有真正自定義日志記錄器的行為(通過擴展像 `log()` , `warn()` 這些 `Logger` 的方法),所以這一步并不一定需要。但是如果你給這些方法增加了自定義的邏輯而且你想讓 Nest 去使用這個實現,那么你還是會需要這一步。
```typescript
const app = await NestFactory.create(ApplicationModule, {
bufferLogs: true,
});
app.useLogger(new MyLogger());
await app.listen(3000);
```
>除了把 `bufferLogs` 設置為`true`,你也可以聲明 `logger: false` 來臨時禁用日志記錄器。需要注意的是如果你給 `NestFactory.create` 提供了 `logger: false` ,在你調用 `useLogger` 以前沒有東西會被記錄進日志,所以你可能會錯過一些重要的初始化錯誤。如果你不在意一些初始化信息會使用默認日志記錄器來記錄,那你可以直接設置 `logger: false` 。
### 使用外部記錄器
生產環境應用通常包括特定的記錄需求,包括高級過濾器,格式化和中心化記錄。Nest 的內置記錄器用于監控 Nest 系統狀態,在開發時也可以為你的特征模塊提供實用的基礎的文本格式的記錄,但生產環境可能更傾向于使用類似[Winston](https://github.com/winstonjs/winston)的模塊,這是一個標準的 Node.js 應用,你可以在 Nest 中體驗到類似模塊的優勢。
- 介紹
- 概述
- 第一步
- 控制器
- 提供者
- 模塊
- 中間件
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 自定義裝飾器
- 基礎知識
- 自定義提供者
- 異步提供者
- 動態模塊
- 注入作用域
- 循環依賴
- 模塊參考
- 懶加載模塊
- 應用上下文
- 生命周期事件
- 跨平臺
- 測試
- 技術
- 數據庫
- Mongo
- 配置
- 驗證
- 緩存
- 序列化
- 版本控制
- 定時任務
- 隊列
- 日志
- Cookies
- 事件
- 壓縮
- 文件上傳
- 流式處理文件
- HTTP模塊
- Session(會話)
- MVC
- 性能(Fastify)
- 服務器端事件發送
- 安全
- 認證(Authentication)
- 授權(Authorization)
- 加密和散列
- Helmet
- CORS(跨域請求)
- CSRF保護
- 限速
- GraphQL
- 快速開始
- 解析器(resolvers)
- 變更(Mutations)
- 訂閱(Subscriptions)
- 標量(Scalars)
- 指令(directives)
- 接口(Interfaces)
- 聯合類型
- 枚舉(Enums)
- 字段中間件
- 映射類型
- 插件
- 復雜性
- 擴展
- CLI插件
- 生成SDL
- 其他功能
- 聯合服務
- 遷移指南
- Websocket
- 網關
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 適配器
- 微服務
- 概述
- Redis
- MQTT
- NATS
- RabbitMQ
- Kafka
- gRPC
- 自定義傳輸器
- 異常過濾器
- 管道
- 守衛
- 攔截器
- 獨立應用
- Cli
- 概述
- 工作空間
- 庫
- 用法
- 腳本
- Openapi
- 介紹
- 類型和參數
- 操作
- 安全
- 映射類型
- 裝飾器
- CLI插件
- 其他特性
- 遷移指南
- 秘籍
- CRUD 生成器
- 熱重載
- MikroORM
- TypeORM
- Mongoose
- 序列化
- 路由模塊
- Swagger
- 健康檢查
- CQRS
- 文檔
- Prisma
- 靜態服務
- Nest Commander
- 問答
- Serverless
- HTTP 適配器
- 全局路由前綴
- 混合應用
- HTTPS 和多服務器
- 請求生命周期
- 常見錯誤
- 實例
- 遷移指南
- 發現
- 誰在使用Nest?