midway日志體系


日常普通使用

首先我們學會 Midway 的日常日志使用方法。

import { Get } from '@midwayjs/decorator';
import { Inject, Controller, Provide } from '@midwayjs/decorator';
 
@Provide()
@Controller()
export class HelloController {
@Inject()
logger;
 
@Inject()
ctx;
 
@Get('/')
async ctx() {
this.logger.info('hello world');
this.ctx.body = 'hello world';
}
}

訪問后,我們能在兩個地方看到日志輸出:

  • console 欄看到輸出。
  • 日志目錄的 midway-app.log 文件中。

輸出結果:

2021-07-22 14:50:59,388 INFO 7739 [-/::ffff:127.0.0.1/-/0ms GET /api/get_user] hello world

以上是用戶在項目開發的基本使用。如果有更多高階用法,請繼續閱讀接下來的章節。

簡介

Midway 為不同場景提供了一套統一的日志接入方式。通過 @midwayjs/logger  包導出的方法,可以方便的接入不同場景的日志系統。

Midway 的日志系統基於社區的 winston,是現在社區非常受歡迎的日志庫。

默認日志對象

默認情況下,Midway 已經將日志庫埋入到整個框架中,在框架啟動時已經能夠自動輸出信息到控制台,以及輸出到文件。

Midway 日志的默認邏輯為:

  • 將日志輸出到控制台和寫入文件
  • 按日期每天切割,以及按大小切割
  • 將錯誤( .error() 輸出的內容)統一輸出到一個固定的錯誤文件

Midway 默認在框架提供了三種不同的日志,對應三種不同的行為。

框架,組件層面的日志,我們叫他 coreLogger  默認會輸出控制台日志和文本日志 midway-core.log ,並且默認會將錯誤日志發送到 common-error.log 。  
業務層面的日志,我們叫他 appLogger  默認會輸出控制台日志和文本日志 midway-app.log ,並且默認會將錯誤日志發送到 common-error.log 。  
請求鏈路的日志,我們叫它上下文日志對象(ContextLogger) 默認使用 appLogger 進行輸出,除了會將錯誤日志發送到 common-error.log  之外,還增加了上下文信息。 修改日志輸出的標記(Label),不同的框架有不同的請求標記,比如 HTTP 下就會輸出路由信息。

日志路徑和文件

默認情況下,Midway 會在本地開發和服務器部署時輸出日志到日志根目錄

  • 本地的日志根目錄為 ${app.appDir}/logs/項目名 目錄下
  • 服務器的日志根目錄為用戶目錄 ${process.env.HOME}/logs/項目名 (Linux/Mac)以及 ${process.env.USERPROFILE}/logs/項目名 (Windows)下,例如 /home/admin/logs/example-app

Midway 會在日志根目錄創建一些默認的文件。

  • midway-core.log 框架、組件打印信息的日志,對應 coreLogger 。
  • midway-app.log 應用打印信息的日志,對應 appLogger
  • common-error.log 所有錯誤的日志(所有 Midway 創建出來的日志,都會將錯誤重復打印一份到該文件中)
在 EggJS 下,為了兼容以前的日志,依舊處理將日志打印在 `midway-web.log` 下。

使用日志對象

一般來說,框架開發者需要獲取到 coreLogger ,記錄框架,組件層面的日志。而業務開發者,需要獲取到 appLogger  來記錄業務日志。在業務和請求相關的流程中,需要拿到上下文日志對象,方便追蹤請求。

使用裝飾器獲取日志對象

在任意類中,我們可以通過裝飾器來獲取日志對象。下面是一個通過裝飾器獲取各種默認日志對象的方式。

  • 1、獲取 coreLogger
import { Provide, Logger } from '@midwayjs/decorator';
import { ILogger } from '@midwayjs/logger';
 
@Provide()
export class UserService {
@Logger()
coreLogger: ILogger; // 獲取 coreLogger
 
@Logger('coreLogger')
anotherLogger: ILogger; // 這里和依賴注入的規則相同,依舊獲取的是 coreLogger
 
async getUser() {
// this.coreLogger === this.anotherLogger
this.coreLogger.warn('warn message');
}
}
  • 2、獲取 appLogger

為了使用更簡單,我們將 appLogger  的 key 變為了最為普通的 logger 。

import { Provide, Logger } from '@midwayjs/decorator';
import { ILogger } from '@midwayjs/logger';
 
@Provide()
export class UserService {
@Logger()
logger: ILogger; // 獲取 appLogger
 
async getUser() {
this.logger.info('hello user');
this.coreLogger.warn('warn message');
}
}
  • 3、獲取上下文日志對象(Context Logger)

上下文日志是在每個請求實例中動態創建的日志對象,因此它和請求作用域綁定,即和請求實例綁定。

Midway 默認會將上下文日志對象掛載到上下文(ctx)上,即 ctx.logger 。

import { Provide, Logger } from '@midwayjs/decorator';
import { ILogger } from '@midwayjs/logger';
 
@Provide()
export class UserService {
@Inject()
logger: ILogger; // 獲取上下文日志
 
async getUser() {
this.logger.info('hello user');
}
}
和全局的日志不同,上下文日志對象,默認會放在請求作用域的依賴注入容器中,它的 key 為 logger,所以可以使用 `Inject` 裝飾器注入它。

使用 API 接口獲取日志對象

有時候,我們不在 Class 的場景下,我們可以從 app  上的方法來獲取這些默認的日志對象。

import { Provide, Logger } from '@midwayjs/decorator';
import { ILogger } from '@midwayjs/logger';
 
@Provide()
export class UserService {
@App()
app: IMidwayApplication;
 
@Inject()
ctx;
 
async getUser() {
this.app.getLogger('logger').info('hello user'); // 獲取 appLogger
this.app.getLogger('coreLogger').warn('warn message'); // 獲取 coreLogger
// this.ctx.logger 獲取請求上下文日志
}
}

使用全局 API 獲取

當上述兩種方法都無法獲取的時候,比如在靜態方法中,我們可以從全局的 日志容器 中獲取日志對象。

import { loggers } from '@midwayjs/logger';
 
// 獲取 coreLogger
loggers.getLogger('coreLogger');
 
// 獲取 appLogger
loggers.getLogger('logger');

更多的信息,可以查看 日志容器 的介紹。

輸出方法和格式

Midway 的日志對象繼承與 winston 的日志對象,一般情況下,只提供 error() , warn() , info() , debug  四種方法。

示例如下。

logger.debug('debug info');
logger.info('啟動耗時 %d ms', Date.now() - start);
logger.warn('warning!');
logger.error(new Error('my error'));

默認的輸出行為

在大部分的普通類型下,日志庫都能工作的很好。

比如:

logger.info('hello world'); // 輸出字符串
logger.info(123); // 輸出數字
logger.info(['b', 'c']); // 輸出數組
logger.info(new Set([2, 3, 4])); // 輸出 Set
logger.info(
new Map([
['key1', 'value1'],
['key2', 'value2'],
]),
); // 輸出 Map
Midway 針對 winston 無法輸出的 `Array` , `Set` , `Map` 類型,做了特殊定制,使其也能夠正常的輸出。

不過需要注意的是,日志對象在一般情況下,只能傳入一個參數,它的第二個參數有其他作用。

logger.info('plain error message', 321); // 會忽略 321

錯誤輸出

針對錯誤對象,Midway 也對 winston 做了定制,使其能夠方便的和普通文本結合到一起輸出。

// 輸出錯誤對象
logger.error(new Error('error instance'));
 
// 輸出自定義的錯誤對象
const error = new Error('named error instance');
error.name = 'NamedError';
logger.error(error);
 
// 文本在前,加上 error 實例
logger.info('text before error', new Error('error instance after text'));
注意,錯誤對象只能放在最后,且有且只有一個,其后面的所有參數都會被忽略。

格式化內容

基於 util.format  的格式化方式。

logger.info('%s %d', 'aaa', 222);

常用的有

  • %s  字符串占位
  • %d  數字占位
  • %j json 占位

更多的占位和詳細信息,請參考 node.js 的 util.format 方法。

輸出自定義對象或者復雜類型

基於性能考慮,Midway(winston)大部分時間只會輸出基本類型,所以當輸出的參數為高級對象時,需要用戶手動轉換為需要打印的字符串

如下示例,將不會得到希望的結果。

const obj = { a: 1 };
logger.info(obj); // 默認情況下,輸出 [object Object]

需要手動輸出希望打印的內容。

const obj = {a: 1};
logger.info(JSON.stringify(obj)); // 可以輸出格式化文本
logger.info(a.1); // 直接輸出屬性值
logger.info('%j', a); // 直接占位符輸出整個 json

純輸出內容

特殊場景下,我們需要單純的輸出內容,不希望輸出時間戳,label 等和格式相關的信息。這種需求我們可以使用 write 方法。

write 方法是個非常底層的方法,並且不管什么級別的日志,它都會寫入到文件中。

雖然 write 方法在每個 logger 上都有,但是我們只在 IMidwayLogger 定義中提供它,我們希望你能明確的知道自己希望調用它。

(logger as IMidwayLogger).write('hello world'); // 文件中只會有 hello world

日志定義

默認的情況,用戶應該使用最簡單的 ILogger  定義。

import { Provide, Logger } from '@midwayjs/decorator';
import { ILogger } from '@midwayjs/logger';
 
@Provide()
export class UserService {
@Inject()
logger: ILogger; // 獲取上下文日志
 
async getUser() {
this.logger.info('hello user');
}
}

ILogger  定義只提供最簡單的 debug , info , warn  以及 error  方法。

在某些場景下,我們需要更為復雜的定義,比如修改日志屬性或者動態調節,這個時候需要使用更為復雜的 IMidwayLogger  定義。

import { Provide, Logger } from '@midwayjs/decorator';
import { IMidwayLogger } from '@midwayjs/logger';
 
@Provide()
export class UserService {
@Inject()
logger: IMidwayLogger; // 獲取上下文日志
 
async getUser() {
this.logger.disableConsole(); // 禁止控制台輸出
this.logger.info('hello user'); // 這句話在控制台看不到
this.logger.enableConsole(); // 開啟控制台輸出
this.logger.info('hello user'); // 這句話在控制台可以看到
}
}

IMidwayLogger  現有完整定義如下,下面文檔介紹的方法,都在其中。

export interface IMidwayLogger extends ILogger {
disableConsole();
enableConsole();
disableFile();
enableFile();
disableError();
enableError();
isEnableFile(): boolean;
isEnableConsole(): boolean;
isEnableError(): boolean;
updateLevel(level: LoggerLevel): void;
updateFileLevel(level: LoggerLevel): void;
updateConsoleLevel(level: LoggerLevel): void;
updateDefaultLabel(defaultLabel: string): void;
updateDefaultMeta(defaultMeta: object): void;
updateTransformableInfo(customInfoHandler: LoggerCustomInfoHandler): void;
getDefaultLabel(): string;
getDefaultMeta(): Record<string, unknown>;
write(...args): boolean;
add(transport: any): any;
remove(transport: any): any;
close(): any;
}

日志等級

winston 的日志等級分為下面幾類,日志等級依次降低(數字越大,等級越低):

const levels = {
all: 0,
error: 1,
warn: 2,
info: 3,
verbose: 4,
debug: 5,
silly: 6,
};

在 Midway 中,為了簡化,一般情況下,我們只會使用 error , warn , info , debug 這四種等級。

日志等級表示當前可輸出日志的最低等級。比如當你的日志 level 設置為 warn  時,僅 warn  以及更高的 error  等級的日志能被輸出。

框架的默認等級

在 Midway 中,有着自己的默認日志等級。

  • 在開發環境下(local,test,unittest),日志等級統一為 info 。
  • 在服務器環境(除開發環境外),為減少日志數量,日志等級統一為 warn 。

動態調整等級

在開發調試時,我們往往有動態調整等級的訴求。在 Midway 的日志下,我們可以使用方法動態的調整日志等級。

logger.updateLevel('debug'); // 動態調整等級為 debug

也可以單獨調整文本和控制台輸出的等級。

// 動態調整文件的日志等級
logger.updateFileLevel('warn');
// 動態調整控制台輸出日志等級
logger.updateConsoleLevel('error');

日志輸出管道(Transport)

Midway 的日志對象基於 Winston 日志,默認包含三個日志管道。

  • ConsoleTransport  用於向控制台輸出日志
  • FileTransport  用於向文件寫入日志
  • ErrorTransport  用於將 Error 級別輸出到特定的錯誤日志

我們可以通過方法動態更新這三個管道,控制輸出。

logger.enableFile();
logger.disableFile();
 
logger.enableConsole();
logger.disableConsole();
 
logger.enableError();
logger.disableError();

同時,還提供了判斷的 API。

logger.isEnableConsole();
logger.isEnableFile();
logger.isEnableError();

日志切割(輪轉)

默認行為下,同一個日志對象會生成兩個文件

以 midway-core.log 為例,應用啟動時會生成一個帶當日時間戳 midway-core.YYYY-MM-DD 格式的文件,以及一個不帶時間戳的 midway-core.log 的軟鏈文件。

為方便配置日志采集和查看,該軟鏈文件永遠指向最新的日志文件。

當凌晨 00:00 時,會生成一個以當天日志結尾 midway-core.log.YYYY-MM-DD 的形式的新文件。

同時,當單個日志文件超過 200M 時,也會自動切割,產生新的日志文件。

日志標簽(label)

日志標簽(label)指的是日志輸出時,帶有 [xx]  的部分。

默認的日志標簽

默認情況下,Midway 對 coreLogger  的標簽做了特殊處理,使用 coreLogger  輸出的日志,會自帶當前的框架信息。

比如:

2021-01-22 12:34:24,354 INFO 34458 [midway:gRPC] Find 1 class has gRPC provider decorator
2021-01-22 12:34:24,372 INFO 34458 [midway:gRPC] Proto helloworld.Greeter found and add to gRPC server
2021-01-22 12:34:24,381 INFO 34458 [midway:gRPC] Server port = 6565 start success
2021-01-22 12:34:24,416 INFO 34458 [midway:gRPC] Server shutdown success

Midway 沒有對 appLogger  做特殊處理,即輸出的日志不帶標簽。

Midway 對 contextLogger  做了特殊處理,默認的標簽會關聯上下文信息。

比如在 Web 下,會輸出 ip,method,path 等:

2021-01-20 15:13:25,408 INFO 66376 [-/127.0.0.1/-/5ms GET /] xxxx

修改日志標簽

有兩個地方可以修改日志標簽。

1、初始化時

const logger = this.app.createLogger('custom', {
// 創建了一個日志等級為 level,只輸出到終端的日志
level: 'info',
defaultLabel: 'main label',
});
 
logger.info('hello world');
 
// output => 2021-01-20 15:13:25,408 INFO 66376 [main label] hello world

2、動態調整

const logger = this.app.createLogger('custom', {
// 創建了一個日志等級為 level,只輸出到終端的日志
level: 'info',
});
 
// 可以傳遞一個字符串
logger.info('hello world', { label: 'UserService' });
 
// output => 2021-01-20 15:13:25,408 INFO 66376 [UserService] hello world
 
// 也可以傳遞數組,會使用:進行組合
logger.info('hello world', { label: ['a', 'b'] });
 
// output => 2021-01-20 15:13:25,408 INFO 66376 [a:b] hello world
注意,動態調整標簽,不會影響默認的標簽,即下一次如果不帶 {label: xxx},依舊會輸出默認標簽。

自定義日志

如果用戶不滿足於默認的日志對象,也可以自行創建。

創建日志有兩種方法:

  • 1、從 app/framework 創建
  • 2、從日志庫 @midwayjs/logger  創建

不管哪一種,都是代理自日志容器的 createLogger  方法。

日志容器

日志容器是來存放日志對象以及管理日志對象,你可以理解為一個 Map。key 為日志對象的名稱,value 為日志對象本身。

默認情況下,引入 @midwayjs/logger 庫時,會在全局創建一個日志容器。

import { loggers } from '@midwayjs/logger';
 
console.log(loggers); // 當前全局默認的日志容器

所有通過框架以及自定義創建的日志對象,都會存儲其中。

每次使用 @Logger 裝飾器以及 app.getLogger() 獲取日志的行為,本質上都是從默認的日志容器或獲取同名的日志對象。

import { Provide, Logger } from '@midwayjs/decorator';
import { ILogger } from '@midwayjs/logger';
 
@Provide()
export class UserService {
@App()
app: IMidwayApplication;
 
@Logger()
logger; // 即 loggers.getLogger('logger')
 
async getUser() {
// 即 loggers.getLogger('coreLogger')
this.app.getLogger('coreLogger').warn('warn message');
}
}

除了 getLogger  之外,還有其他一些方法,這些最基礎的方法,可以以最原始的方式來獲取、修改日志對象。

import { loggers, ILogger } from '@midwayjs/logger';
 
const customLogger = loggers.createLogger('customLogger', {
// ...
});
 
customLogger.info('hello world');
 
loggers.getLogger('customLogger'); // 從容器獲取一個日志
loggers.addLogger('anotherLogger', customLogger); // 添加一個新的日志
loggers.removeLogger('customLogger'); // 移除一個日志
loggers.close(); // 關閉並移除所有日志

這種方法是一般用於和框架無關的場景,需要傳遞相對完整的參數,比如日志文件的路徑等。

同時, @midway/logger  也提供兩個簡化的方法,用於快速創建日志。

import { createLogger, createConsoleLogger } from '@midwayjs/logger';
 
// 一個只有控制台輸出的日志,並添加到默認的日志容器中
const consoleLogger = createConsoleLogger('customConsoleLogger');
 
// 一個只寫文本的日志,並添加到默認的日志容器中(不會將錯誤轉到其他日志,也不會輸出控制台)
const onlyFileLogger = createFileLogger('customOnlyFileConsoleLogger', {
dir: logsDir,
fileLogName: 'test-logger.log',
});
 
// 文本日志,並添加到默認的日志容器中
const fileLogger = createLogger('customFileLogger', {
level: 'warn',
dir: __dirname,
});

注意,如果創建同名的日志,日志容器會自動判斷重名,跳過創建,並返回原有日志對象。

const customLogger1 = loggers.createLogger('customLogger', {
// ...
});
 
const customLogger2 = loggers.createLogger('customLogger', {
// ...
});
 
// customLogger1 === customLogger2
這個特性很有用,使得在不同場景下,能夠讓業務使用到同一個日志對象。

從當前框架、App 創建日志

在大多數請下,用戶會使用這種方式創建日志。

Midway 在 app  上增加了 createLogger  方法,以方便用戶快速基於框架默認的日志配置創建自己的日志實例。

比如在入口的 configuration.ts  中,我們可以創建出自己的日志。

export class AutoConfiguration {
@App()
app: IMidwayApplication;
 
async onReady() {
this.app.createLogger('custom1'); // 創建一個全功能的自定義日志
 
this.app.createLogger('custom2', {
// 創建了一個日志等級為 level,只輸出到終端的日志
level: 'error',
disableFile: true,
disableError: true,
});
 
this.app.createLogger('custom3', {
fileLevel: 'warn', // 只修改文件日志等級
disableConsole: true, // 禁止終端輸出
});
}
}

這樣創建出日志會自動綁定到框架中,並且使用框架默認的路徑創建日志,后期可以直接根據日志名獲取使用。

import { ILogger } from '@midwayjs/logger';
 
export class UserService {
@Logger('custom1')
custom1Logger: ILogger;
 
@Logger('custom2')
custom2Logger: ILogger;
 
@Logger('custom3')
custom3Logger: ILogger;
}
所有創建的日志,在全局日志容器 `loggers` 中都能獲取到。

創建日志,等價於在全局日志容器中調用 loggers.createLogger()  方法。

創建日志選項

createLogger  方法的所有參數如下,用戶可以自行調整。

export interface LoggerOptions {
dir?: string;
fileLogName?: string;
errorLogName?: string;
label?: string;
disableConsole?: boolean;
disableFile?: boolean;
disableError?: boolean;
consoleLevel?: LoggerLevel;
fileLevel?: LoggerLevel;
fileMaxSize?: string;
fileMaxFiles?: string;
fileDatePattern?: string;
errMaxSize?: string;
errMaxFiles?: string;
errDatePattern?: string;
disableFileSymlink?: boolean;
disableErrorSymlink?: boolean;
printFormat?: (info) => string;
format?: logform.format;
eol?: string;
}

參數名 | 參數類型 | 默認值 | 描述 | | ------------------------------ | ---------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------ | ----- | --- | ------------ | | dir | string | window: process.env.USERPROFILE  | | Linux/mac: process.env.HOME  | 文本日志的根目錄,默認為當前的用戶根目錄 | | level | debug | info | warn | error | | 全局日志等級 | | fileLogName | string | midway-core.log | 文本日志寫入的文件名 | | errorLogName | string | common-error.log | 錯誤日志寫入的文件名 | | defaultLabel | string | undefined | 輸出的默認標簽,[] 中的值 | | disableConsole | boolean | false | 禁止控制台輸出 | | disableFile | boolean | false | 禁止文本日志輸出 | | disableError | boolean | false | 禁止錯誤日志輸出 | | disableFileSymlink | boolean | false | 禁止生成軟鏈,默認情況下,會生成帶有時間戳的文件加上一個沒有時間戳的軟鏈文件。 | | disableErrorSymlink | boolean | false | 禁止生成軟鏈,默認情況下,會生成帶有時間戳的文件加上一個沒有時間戳的軟鏈文件。 | | consoleLevel | string | silly | 最低的控制台日志可見等級,可覆蓋全局的日志等級 | | fileLevel | string | silly | 最低的文本日志可見等級,可覆蓋全局的日志等級 | | fileMaxSize | string | 200m | 日志切割的最大尺寸,默認 200m  | | fileMaxFiles | string | 31d(31 天) | 最多保留的文件時間,默認  31d  | | fileDatePattern | string | YYYY-MM-DD | 文件后綴時間戳格式 | | errMaxSize | string | 200m | 日志切割的最大尺寸,默認 200m  | | errMaxFiles | string | 31d(31 天) | 最多保留的文件時間,默認  31d | | errDatePattern | string | YYYY-MM-DD | 錯誤日志文件后綴時間戳格式 | | printFormat | (info: any) => string; | midway 默認顯示格式 | 默認的日志輸出顯示格式,傳入一個回調函數進行覆蓋。 | | format | logform.Format | midway 默認 format | 默認的 winston format 格式。 | | eol | string | os.EOL | 默認是操作系統的換行符 |

修改顯示格式(Display)

顯示格式指的是日志輸出時單行文本的字符串結構。Miidway 對 Winston 的日志做了定制,提供了一些默認對象。

顯示格式是一個返回字符串結構的方法,參數為 Winston 的 info 對象

默認情況下,我們的顯示格式為:

(info) => {
return `${info.timestamp} ${info.LEVEL} ${info.pid} ${info.labelText}${info.message}`;
};

輸出如下:

2020-12-30 07:50:10,453 ERROR 3847 [customLabel] Error: another test error
at Object.<anonymous> (/home/runner/work/midway/midway/packages/logger/test/index.test.ts:224:18)

info 對象的默認屬性如下:

屬性名 描述 示例
timestamp 時間戳,默認為 'YYYY-MM-DD HH:mm:ss,SSS 格式。 2020-12-30 07:50:10,453
level 小寫的日志等級 info
LEVEL 大寫的日志等級 INFO
pid 當前進程 pid 3847
labelText 標簽的聚合文本 [a:b:c]
message 普通消息 + 錯誤消息 + 錯誤堆棧的組合 1、普通文本,如 123456 , hello world  2、錯誤文本(錯誤名+堆棧)Error: another test error at Object. (/home/runner/work/midway/midway/packages/logger/test/index.test.ts:224:18) 3、普通文本+錯誤文本 hello world Error: another test error at Object. (/home/runner/work/midway/midway/packages/logger/test/index.test.ts:224:18)
stack 錯誤堆棧 Error: another test error at Object. (/home/runner/work/midway/midway/packages/logger/test/index.test.ts:224:18)
originError 原始錯誤對象 錯誤實例本身
originArgs 原始的用戶入參 [ 'a', 'b', 'c' ]

示例,創建一個自定義格式的 Logger。

export class AutoConfiguration {
@App()
app: IMidwayApplication;
 
async onReady() {
this.app.createLogger('custom1', {
printFormat: (info) => {
return `${info.timestamp} ${info.level} ${info.message}`;
},
});
 
this.app.getLogger('custom1').info('hello world');
}
}

這樣該日志的輸出效果則為:

2020-12-30 07:50:10,453 info hello world

動態修改默認顯示內容(TransformableInfoInfo)

在某些場景下,我們無法在初始化修改日志對象 ,如果希望可以修改輸出內容,也可以使用動態修改 info 對象的值來達到類似的效果。

logger.updateTransformableInfo((info) => {
info.timestamp = '123';
return info;
});

在原有輸出的時間字段的位置則會變成

123,408 INFO 66376 [-/127.0.0.1/-/5ms GET /] xxxx
注意,該方法只能修改屬性值,但是不能修改輸出結構。

完全自定義格式(Format)

一般來說修改展示的效果已經足夠,在 winston 中,還有另一種完全自定義輸出的方式,修改 logform。通過修改 logform,基本上可以達到任意的效果。

你可以使用如下的 winston 自帶的 format。

Formats

以及,midway 為 winston 定制的幾個 format。

1、displayCommonMessage

displayCommonMessage 用於對常用輸入的規則化處理,做了以下一些事情

  • 1、對數組,Set,Map 的輸出處理
  • 2、Error 的堆棧拼裝,以及增加原始的 error 對象
  • 3、增加 pid
  • 4、增加大寫的 level

它的 options 如下:

屬性名 類型 描述
defaultMeta object 默認輸出的元信息,對象 key/value 結構
uppercaseLevel boolean 是否開啟大寫,默認 true

2、displayLabels

按照一定的分隔符聚合標簽(labels),它的 options 如下:

屬性名 類型 描述
defaultLabels string[] 標簽信息數組
labelSplit string 標簽分隔符,默認為 :

示例:

import { format, displayCommonMessage, displayLabels } from '@midwayjs/logger';
 
export class AutoConfiguration {
@App()
app: IMidwayApplication;
 
async onReady() {
this.app.createLogger('custom1', {
format: format.combine(
displayCommonMessage({
uppercaseLevel: true,
defaultMeta: {
group: 'defaultGroup',
},
}),
displayLabels({
defaultLabels: this.labels,
}),
format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss,SSS',
}),
format.splat(),
format.printf(
(info) =>
`${info.timestamp} ${info.LEVEL} ${info.pid} ${info.labelText}- ${info.group} ${info.message}`,
),
),
});
 
this.app.getLogger('custom1').info('hello world');
}
}

清理全局日志容器

midway 提供了一個方法用於一次性清理所有的日志對象。

import { clearAllLoggers } from '@midwayjs/logger';
 
clearAllLoggers(); // 從默認日志容器中清理所有的日志對象
loggers.getLogger('coreLogger'); // undefined

配置框架日志

Midway 在框架中提供了默認日志,如果需要修改默認日志的行為,可以在初始化框架時修改,傳入不同的日志對象。

覆蓋框架日志

框架的初始化入口一般為下面的代碼,在其中創建日志實例,替換即可。

const { Bootstrap } = require('@midwayjs/bootstrap');
import { Framework } from '@midwayjs/koa';
import { createLogger, createConsoleLogger } from '@midwayjs/logger';
 
// 一個只有控制台輸出的日志
const consoleLogger = createConsoleLogger('customConsoleLogger');
// 文本日志
const fileLogger = createLogger('customFileLogger', {
dir: __dirname,
});
 
const koaFramework = new Framework().configure({
port: 7001,
logger: consoleLogger, // or fileLogger
});
 
Bootstrap.load(koaFramework).run();

覆蓋請求鏈路日志的 Label

每個框架(Framework)可能會有默認的上下文日志輸出,ContextLogger 是基於 appLogger 來打日志的,會復用 appLogger 的所有信息,唯一不同的是,ContextLogger 會輸出特殊的 label。

比如 HTTP 下的默認輸出為:

2021-01-20 15:13:25,408 INFO 66376 [-/127.0.0.1/-/5ms GET /] xxxx

label 為 -/127.0.0.1/-/5ms GET /  這一部分。

我們可以通過重寫上下文日志類來修改上下文輸出信息(ContextLogger)的 label。

首先,你需要定義一個文件,繼承默認的 MidwayContextLogger  類,實現 formatContextLabel  來返回 label 內容。比如 HTTP 下:

// src/custom/logger.ts
 
import { MidwayContextLogger } from '@midwayjs/logger';
import { Context } from 'egg';
 
export class MidwayCustomContextLogger extends MidwayContextLogger<Context> {
formatContextLabel() {
const ctx = this.ctx;
return `${Date.now() - ctx.startTime}ms ${ctx.method}`;
}
}

Midway 為每個框架的 app 增加了一個 setContextLoggerClass 方法,用於覆蓋默認的 ctx.logger 輸出的 label。

你可以在啟動時進行覆蓋。

// configuration.ts
import { Configuration } from '@midwayjs/decorator';
import { ILifeCycle, IMidwayContainer } from '@midwayjs/core';
import { MidwayCustomContextLogger } from './custom/logger';
import { Application } from 'egg';
 
@Configuration()
export class ContainerConfiguration implements ILifeCycle {
@App()
app: Application;
 
async onReady(container: IMidwayContainer): Promise<void> {
this.app.setContextLoggerClass(MidwayCustomContextLogger);
}
}

則你在使用 ctx.logger  輸出時,會默認變成你 format 的樣子。

ctx.logger.info('hello world');
// 2021-01-28 11:10:19,334 INFO 9223 [2ms POST] hello world

@midwayjs/web(EggJS)下特殊情況

在 2021-01-28 之前的創建的項目,默認使用 egg-logger,之后創建的項目,將使用 @mdwayjs/logger。

兼容配置

由於 Egg 下原日志配置是非 API 形式,統一放在 config 文件中,在這一場景下,我們依舊支持大部分的參數,用於快速將應用遷移到新的日志體系。

以下配置只在 Egg 下生效。

config.logger

dir 日志根目錄
level 文本日志等級
consoleLevel 控制台日志等級
fileLogName 文本日志文件名
coreLogName core 日志文件名
agnetLogName agent 日志名
appLogName 應用日志名
disableConsoleAfterReady ready 之后禁止控制台輸出

config.customLogger

dir 日志根目錄
file 日志文件名
level 文本日志等級
consoleLevel 控制台日志等級

替換日志庫

默認情況下,腳手架生成的日志庫為 @midwayjs/logger ,並且默認關閉 egg 的日志切割能力(因為 midway 的日志庫自帶了),如果希望繼續使用 egg-logger ,可以通過配置改回。

// src/config.default.ts
export const midwayFeature = {
// true 代表 使用 midway logger
// false 或者為空代表使用 egg-logger
replaceEggLogger: false,
};

同時,由於 egg-logger 日志需要額外開啟切割能力,需要開啟切割插件。

// src/config/plugin.ts
import { EggPlugin } from 'egg';
export default {
logrotator: true, // 這行改成 true,或者刪掉
static: false,
} as EggPlugin;

調整默認 level

// config.local.ts
export const logger = {
level: 'INFO',
consoleLevel: 'WARN',
};

啟動輸出

開發時,框架的默認輸出都使用的是 coreLogger ,egg 默認的 coreLogger  的 consoleLevel  為 WARN ,如有查看的需求,可以覆蓋默認的 egg 配置。

// config.local.ts
export const logger = {
coreLogger: {
consoleLevel: 'INFO',
},
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM