VonaJS:winston日志系统,如何为?

摘要:VonaJS 基于winston提供了强大而灵活的日志系统。可以基于分级控制写入日志文件的内容。可以按指定的规则对日志文件进行轮换。
VonaJS 基于winston提供了强大而灵活的日志系统 特性 多Client: 每个 Client 有独立的配置 多Child: 可以为不同的场景创建 Child 日志 Rotate: 按指定的规则对日志文件进行轮换 日志分级: 可以基于分级控制写入日志文件的内容 日志目录 针对不同的运行环境默认使用不同的日志目录: 测试环境/开发环境: {project path}/.app/logs 生产环境: {home}/vona/{project name}/logs 可以在 App Config 或者.env 文件中修改配置 1. App Config src/backend/config/config/config.ts // server config.server = { loggerDir: '/new/path', }; 2. .env env/.env # server SERVER_LOGGERDIR = /new/path App Config配置 可以在 App Config 中进行日志配置: src/backend/config/config/config.ts // logger config.logger = { rotate: {}, base: {}, clients: {}, }; 名称 说明 rotate 日志Rotate base 基础配置,为所有Client提供通用的基础配置 clients 配置多个Client。系统提供了内置的default Client,实现开箱即用的日志能力 Rotate 系统提供了默认的轮换配置,并且处于开启状态。可以在 App Config 或者.env 文件中修改配置 1. App Config src/backend/config/config/config.ts // logger config.logger = { rotate: { enable: true, options(filename: string) { return { filename: `${filename}-%DATE%.log`, datePattern: 'YYYY-MM-DD', maxSize: '20m', maxFiles: '7d', }; }, }, }; 名称 说明 enable 是否启用Rotate options 通过函数返回Rotate配置 options.filename 文件名模版 options.datePattern 日期模式 options.maxSize 每个文件的最大尺寸 options.maxFiles 只保留几天之内的文件 2. .env env/.env # logger LOGGER_ROTATE_ENABLE = true LOGGER_ROTATE_FILENAME = '{{filename}}-%DATE%.log' LOGGER_ROTATE_DATEPATTERN = YYYY-MM-DD LOGGER_ROTATE_MAXSIZE = 20m LOGGER_ROTATE_MAXFILES = 7d 添加新Client 下面通过添加新 Client 解释 Client 的配置 1. 添加类型定义 采用接口合并机制添加新 Client 的类型定义,比如order,用于输出独立的与订单相关的日志 在 VSCode 编辑器中,输入代码片段recordloggerclient,自动生成代码骨架: declare module 'vona' { export interface ILoggerClientRecord { : never; } } 调整代码,然后添加order declare module 'vona' { export interface ILoggerClientRecord { + order: never; } } 2. 增加Client配置 src/backend/config/config/config.ts // logger config.logger = { clients: { order(this: VonaApplication, clientInfo) { const transports = [ this.bean.logger.makeTransportFile(clientInfo, 'order'), this.bean.logger.makeTransportConsole(clientInfo), ].filter(item => !!item); return { transports }; }, }, }; order: 通过函数返回 Client 配置。该配置将与系统提供的默认配置进行合并 因此,在一般情况下,只需构造所需的transports即可 clientInfo: 包含环境信息,用于构造transport makeTransportFile: 用于构造文件通道,需要提供日志文件名order 由于文件名模版是${filename}-%DATE%.log,那么实际生成的文件名是order-2025-11-14.log makeTransportConsole: 用于构造控制台通道 filter: 新建的通道实例有可能为 undefined,因此需要进行 filter 获取Logger Client实例 获取 Logger Client 实例有两种方式: 方式1 class ControllerStudent extends BeanBase { async test() { // logger: default const loggerDefault = this.bean.logger.default; // logger: order const loggerOrder = this.bean.logger.get('order'); } } 方式2 class ControllerStudent extends BeanBase { async test() { // logger: default const loggerDefault = this.$logger; // logger: order const loggerOrder = this.$loggerClient('order'); } } 方式2不仅代码更加简洁,而且还会自动在日志中带上当前 Bean Class 的beanFullName,便于排查问题 举例: const loggerOrder = this.bean.logger.get('order'); loggerOrder.info('test'); // logger: default this.$logger.info('test'); // logger: order this.$loggerClient('order').info('test'); 图中输出了 beanFullName: [demo-student.controller.student] 获取Logger Child实例 对于同一个 Logger Client,可以生成多个 Child 实例,不同的 Child 对应不同的场景 比如,生成 Child pay,从而在日志中可以明确提示pay信息 // child of logger-default this.$loggerChild('pay').info('$50'); // child of logger-order this.$loggerChild('pay', 'order').info('$50'); 图中输出了 child name: [pay] 添加类型定义 同样,需要提供pay的类型定义,从而支持类型提示 在 VSCode 编辑器中,输入代码片段recordloggerchild,自动生成代码骨架: declare module 'vona' { export interface ILoggerChildRecord { : never; } } 调整代码,然后添加pay declare module 'vona' { export interface ILoggerChildRecord { + pay: never; } } 日志消息 1. 一般用法 this.$logger.info('test'); 2. 字符串插值 基于util.format实现字符串插值 this.$logger.info('%s has %d apples', 'Tom', 3); 3. 延迟消息 由于存在日志分级,有些分级的消息并不会写入文件。那么,如果构造消息内容太大,则会浪费系统性能 举例 const obj = { data: 'more info' }; this.$logger.debug(JSON.stringify(obj)); 如果当前分级是info,那么debug分级的日志就不会写入文件。那么,JSON.stringify就会空耗系统性能 解决方案 可以提供回调函数,当需要写入文件时才会执行回调函数,从而生成实际的消息 const obj = { data: 'more info' }; this.$logger.debug(() => { return JSON.stringify(obj); }); 4. 多级Child 可以从当前 Logger 实例创建多级 Child Logger,从而向日志消息中传入更多 metadata this.$logger.child({ requestId: '451' }).child({ extra: 'some info' }).info('test'); this.$logger.child({ requestId: '578', extra: 'some info' }).info('test'); 如下图所示:requestId/extra NPM分级 可以基于分级控制写入日志文件的消息内容 VonaJS 采用 NPM 分级规则,参见: RFC5424 const levels = { error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6 }; 输出方法 与分级对应的是一组输出方法 this.$logger.error('test'); this.$logger.warn('test'); this.$logger.info('test'); this.$logger.http('test'); this.$logger.verbose('test'); this.$logger.debug('test'); this.$logger.silly('test'); 默认分级 VonaJS 的默认分级是info,从而可以控制只有<=info的分级日志才写入文件 1. makeTransportFile 我们在新建order Client,可以通过makeTransportFile方法实现此策略: 只有<=info的分级日志才写入文件 // logger config.logger = { clients: { order(this: VonaApplication, clientInfo) { const transports = [ + this.bean.logger.makeTransportFile(clientInfo, 'order'), this.bean.logger.makeTransportConsole(clientInfo), ].filter(item => !!item); return { transports }; }, }, }; 2. makeTransportFile: 独立文件 如果需要强制某个分级的日志写入独立的文件,可以再增加一个文件通道。比如,将debug分级的日志写入文件order-debug中 // logger config.logger = { clients: { order(this: VonaApplication, clientInfo) { const transports = [ + this.bean.logger.makeTransportFile(clientInfo, 'order-debug', 'debug'), this.bean.logger.makeTransportFile(clientInfo, 'order'), this.bean.logger.makeTransportConsole(clientInfo), ].filter(item => !!item); return { transports }; }, }, }; 3. makeTransportConsole 对于控制台通道,有一个特殊约定:凡是silly分级的日志,都会输出到控制台。因此,通过makeTransportConsole方法实现此策略 // logger config.logger = { clients: { order(this: VonaApplication, clientInfo) { const transports = [ this.bean.logger.makeTransportFile(clientInfo, 'order'), + this.bean.logger.makeTransportConsole(clientInfo), ].filter(item => !!item); return { transports }; }, }, }; 默认分级配置 可以通过.env 文件修改默认的分级配置 由于可以配置多个 Clients,因此,Clients 可以单独配置自己的默认分级 1. Client: default LOGGER_CLIENT_DEFAULT = 支持如下值:(empty)/true/false/{level} 比如,希望<=debug分级的日志写入文件,那么,配置如下: LOGGER_CLIENT_DEFAULT = debug 也可以直接在控制台设置环境变量: LOGGER_CLIENT_DEFAULT=debug npm run dev 2. Client: order 对于新增的 Client order,也可以设置默认分级: LOGGER_CLIENT_ORDER = verbose 获取当前分级 在系统运行中可以获取当前分级: class ControllerStudent { async test() { // logger: default const levelDefault = this.bean.logger.getLevel(); // logger: order const levelOrder = this.bean.logger.getLevel('order'); } } 动态修改分级 在系统运行中可以动态修改分级,从而在不停机、不重启的情况下,随时控制基于分级的写入策略 当调用setLevel方法时,系统会自动广播至所有 Workers,从而修改每个工作进程中的当前分级 class ControllerStudent { async test() { // level: info let levelDefault = this.bean.logger.getLevel(); assert.equal(levelDefault, 'info'); this.$logger.debug('1: this line will not output'); // level: debug this.bean.logger.setLevel('debug'); levelDefault = this.bean.logger.getLevel(); assert.equal(levelDefault, 'debug'); this.$logger.debug('2: this line will output'); // disable this.bean.logger.setLevel(false); levelDefault = this.bean.logger.getLevel(); assert.equal(levelDefault, false); this.$logger.info('3: this line will not output'); this.$logger.debug('4: this line will not output'); // enable this.bean.logger.setLevel(true); } } 资源 Github:https://github.com/vonajs/vona 文档:https://vona.js.org