# 日志输出器
说明
本章中使用的宏变量如果没有特殊描述,则存储在Core.Macros
中。
# 输出模型
# 输出器配置
通常,我们实例化日志输出器时需要指定由运行环境、最小输出等级和功能参数构成的配置对象,即:{ env, level, params }
。
env
:日志输出器的运行环境,默认值为BASE_LOGGER_DEVELOPMENT_ENVIRONMENT
。说明
通常,运行环境将影响日志输出器的输出行为:
当运行环境为
BASE_LOGGER_DEVELOPMENT_ENVIRONMENT
时,日志输出器将日志统一输出至控制台。当运行环境非
BASE_LOGGER_DEVELOPMENT_ENVIRONMENT
时,日志输出器将日志输出至由其本身功能实现而指向的目标位置。
我们在自定义输出器时同样应该遵守在运行环境为
BASE_LOGGER_DEVELOPMENT_ENVIRONMENT
时输出日志到控制台的准则。level
:最小输出等级(支持使用输出等级的全称或别名),日志输出器仅输出权重大于等于此输出等级的日志。最小输出等级的默认值依赖于日志输出器的运行环境:
当运行环境为
BASE_LOGGER_DEVELOPMENT_ENVIRONMENT
时,默认值为'all'
。当运行环境非
BASE_LOGGER_DEVELOPMENT_ENVIRONMENT
时,默认值为'error'
。
params
:控制日志输出器的输出行为的功能参数,不同类型的日志输出器通常拥有不同的功能参数。提示
功能参数将在日志输出器实例化时被缓存在实例属性
params
中。因此,我们在自定义输出器时可以使用功能参数的缓存机制存储一些自定义参数,以使其在整个输出器生命周期的任意阶段中共享。
需要注意的是,为了避免对日志输出器的行为产生影响,我们在使用自定义参数时不要使用与已有功能参数相同的键名。
# 输出等级
我们可以使用输出等级名称和输出等级别名指定输出等级。
日志输出器默认支持6个输出等级:
日志类型 | 名称 | 别名 | 权重 |
---|---|---|---|
追踪日志 | 'trace' | 't' | 10000 |
调试日志 | 'debug' | 't' | 20000 |
信息日志 | 'infos' | 'i' 、'info' | 30000 |
警告日志 | 'warns' | 'w' 、'warn' | 40000 |
错误日志 | 'error' | 'e' 、'err' | 50000 |
灾难日志 | 'fatal' | 'f' | 60000 |
另外,日志输出器使用2个内部输出等级用于等级限定:
限定规则 | 等级名称 | 等级别名 | 等级权重 |
---|---|---|---|
保留所有日志 | 'all' | 'a' | 0 |
禁止所有日志 | 'off' | 'o' | 100000 |
日志输出器内部使用4个基准输出等级:
最小输出等级(通过配置对象指定)
默认日志等级(通过配置对象中的功能参数指定)
默认警告日志等级(通过配置对象中的功能参数指定)
默认错误日志等级(通过配置对象中的功能参数指定)
当基准输出等级未指定,或指定的值不在输出等级支持列表中时,将使用等级选举的结果。
# 等级选举
日志输出器将自动进行等级选举以在输出等级支持列表中选出基准输出等级的默认值。一旦某个基准输出等级无效,则应用选举结果。
注意
等级选举将产生绝对有效的默认日志等级、默认警告日志等级和默认错误日志等级,但是日志输出器不会应用全部的选举结果。
比如,当我们指定的默认日志等级无效,但默认警告日志等级和默认错误日志等级有效时,日志输出器仅使用选举结果中的默认日志等级。
另外,当最小输出等级无效时将使用选举结果中的默认日志等级。
输出等级选举规则为:
迭代输出等级支持列表,尝试在配置了
default
为true
的输出等级中选择权重最高项作为默认日志等级。若上述步骤未能产生默认日志等级(比如:输出等级支持列表中不存在
default
为true
的输出等级),则使用输出等级支持列表中权重居中项作为默认日志等级。若上述步骤产生了两个权重居中的输出等级,则使用权重较小项(两者中前者)作为默认日志等级。
选举比默认日志等级权重大一个梯度的输出等级(即输出等级支持列表按权重升序排列后位于默认日志等级后一个位置的输出等级)作为默认警告日志等级。
若上述步骤未能产生默认警告日志等级(比如:默认日志等级位于输出等级支持列表的最末位),则使用默认日志等级作为默认警告日志等级。
选举比默认日志等级权重大二个梯度的输出等级(即输出等级支持列表按权重升序排列后位于默认日志等级后二个位置的输出等级)作为默认错误日志等级。
若上述步骤未能产生默认错误日志等级(比如:默认日志等级位于输出等级支持列表的最末位或倒数第二位),则使用默认警告日志等级作为默认错误日志等级。
# 基础输出器
基础输出器抽象了日志输出器基本的生命周期管理和输出链路控制,是日志输出器的基类。
通过基础输出器输出的日志最终将打印至控制台。因此,基础输出器通常不用于生产环境的日志收集。
注意
我们实现的自定义输出器必须继承自基础输出器,否则在使用时将无法通过类型校验。
# 功能说明
输出的日志将被打印至控制台。
支持自启动。即:基础输出器实例化后自动启动,无需显式执行实例方法
start
即可执行输出动作。执行实例方法
start
、close
和log
时自动进行状态检测。执行实例方法
log
时:检测当前是否处于启动状态,若实例未启动则放弃此次日志输出。执行实例方法
start
时:检测当前是否处于启动状态,若实例已启动则不再执行启动逻辑。执行实例方法
close
时:检测当前是否处于关闭状态,若实例已关闭则不再执行关闭逻辑。
说明
通常,日志输出器仅提供实例方法
start
、close
和log
以被业务层调用。在业务层执行日志输出器提供的实例方法时默认进行状态检测,未通过状态检测或执行链路中产生异常时将抛出异常信息。
我们可以通过实例化日志输出器时指定功能参数以控制是否进行状态检测和捕获异常信息。
实例方法
log
支持多种调用方式以快捷输出日志。调用方式 说明 log(message)
根据日志内容智能选择输出等级,且根据实际调用栈获取调用方法名 log(level, message)
使用指定的输出等级输出日志,且根据实际调用栈获取调用方法名 log(level, funcName, message)
使用指定的输出等级和方法名输出日志 说明
使用
log(message)
方式输出日志时,日志输出器将根据日志内容message
自动选择输出等级:- 当
message
非Error
类型:使用默认日志等级输出日志。 - 当
message
为Error
类型:使用默认错误日志等级输出日志。
在日志输出器执行输出动作时,如果实际应用的输出等级权重高于或等于默认错误日志等级时,将在日志内容中附加调用栈:
- 当
message
非Error
类型时:使用执行实例方法log
时的实际调用栈。 - 当
message
为Error
类型时:使用Error
实例携带的调用栈。
我们可以通过在日志输出器实例化时配置
callStackOffset
以调整自动构建调用栈时的偏移基准。- 当
# 功能参数
#
template
设置此配置将指定日志输出器的输出模板,默认值为:
BASE_LOGGER_MESSAGE_DEFAULT_TEMPLATE
,即:'[${timestamp}] [${env} LOG] [${level}] - <${funcName}><${content}>\n'
。说明
宏变量
BASE_LOGGER_MESSAGE_DEFAULT_TEMPLATE
存储在Core.Messages
中。我们在指定输出模板时,可以使用
${}
引用日志的内容元素,默认支持:内容元素 说明 ${env}
当前运行环境 ${level}
日志输出等级 ${content}
日志内容(可能由日志消息和调用栈组成) ${funcName}
调用方法名 ${timestamp}
时间戳字符串 #
defaultLevel
设置此配置将指定日志输出器的默认日志等级,默认值为:
null
。我们可以使用输出等级的名称或别名作为默认日志等级。当指定此配置为
null
或指定了不在输出等级支持列表中的值时,将使用等级选举产生的默认日志等级。应用场景
在以下场景中,日志输出器将自动应用默认日志等级:
日志输出器配置的最小输出等级不在输出等级支持列表中时,将使用默认日志等级作为最小输出等级。
日志输出器使用
log(message)
方式输出日志,且日志内容message
不为Error
类型时,将使用默认日志等级输出日志。日志输出器使用
log(level[, funcName], message)
方式输出日志,且指定的输出等级不在输出等级支持列表中时,将使用默认日志等级输出日志。
让我们来看一个有关默认日志等级的🌰:
const Core = require('node-corejs'); // 创建基础输出器 const logger = new Core.BaseLogger({ level: 'x', // 此时最小输出等级无效,将应用默认日志等级error params: { defaultLevel: 'error' }, }); logger.log('i', '此条日志不会被输出'); // infos日志权重小于当前的最小输出等级error logger.log('这是一条红色的错误日志'); // 不指定输出等级时将使用默认等级error logger.log('x', '这是一条红色的错误日志'); // 输出等级无效时将使用默认等级error logger.log('x', '方法名', '这是一条红色的错误日志'); // 输出等级无效时将使用默认等级error
1
2
3
4
5
6
7
8
9
10
11
12#
defaultWarnsLevel
设置此配置将指定日志输出器的默认警告日志等级,默认值为:
null
。我们可以使用输出等级的名称或别名作为默认警告日志等级。当指定此配置为
null
或指定了不在输出等级支持列表中的值时,将使用等级选举产生的默认警告日志等级。#
defaultErrorLevel
设置此配置将指定日志输出器的默认错误日志等级,默认值为:
null
。我们可以使用输出等级的名称或别名作为默认错误日志等级。当指定此配置为
null
或指定了不在输出等级支持列表中的值时,将使用等级选举产生的默认错误日志等级。应用场景
在以下场景中,日志输出器将自动应用默认错误日志等级:
日志输出器使用
log(message)
方式输出日志,且日志内容message
为Error
类型时,将使用默认错误日志等级输出日志。日志输出器使用
log(level[, funcName], message)
方式输出日志,且指定的输出等级的权重高于或等于默认错误日志等级的权重时,将在日志内容中附加调用栈。
让我们来看一个有关默认错误日志等级的🌰:
const Core = require('node-corejs'); // 创建基础输出器 const logger = new Core.BaseLogger({ params: { defaultErrorLevel: 'i' } // 指定infos为默认错误等级 }); logger.log(new Error('这是一条绿色且包含调用栈的错误日志')); // Error类型的日志内容将以infos等级输出 logger.log('i', new Error('这是一条绿色且包含调用栈的错误日志')); // 指定infos及以上的输出等级时将在日志内容中添加调用栈 logger.log('d', new Error('这是一条蓝色且不包含调用栈的错误日志')); // debug等级的权重小于infos因此不在日志内容中添加调用栈
1
2
3
4
5
6
7
8
9
10#
callStackOffset
设置此配置将指定日志输出器构建实际调用栈时使用的偏移基准,默认值为:
BASE_LOGGER_DEFAULT_CALLSTACK_OFFSET
,即:0
,此时将取得实例方法log
之前的调用栈记录。说明
偏移基准仅作用于日志输出器自动构建实际调用栈的场景,不会影响
Error
类型日志内容的调用栈提取。
我们可以根据实际场景调整偏移基准以控制调用栈提取内容:
使用
Function
类型的偏移基准:日志输出器自动构建调用栈时将仅提取在偏移基准之上的调用栈记录。注意
日志输出器使用
Error.captureStackTrace
提取调用栈,如果偏移基准指定的Function
不在调用链路中则将取得空调用栈。使用
Number
类型的偏移基准:日志输出器首先提取实例方法log
之前的调用栈记录,接下来通过slice
应用偏移基准中指定的偏移量以取得调用栈。注意
因nodejs默认限制,日志输出器使用
Error.captureStackTrace
最大仅能提取实例方法log
之前的Error.stackTraceLimit
条调用栈记录。偏移基准将以此为基础进行偏移,使调用栈信息量降低。
我们可以使用日志输出器的某个原型方法作为偏移基准:
const Core = require('node-corejs'); // 创建基础输出器 const logger = new Core.BaseLogger({ params: { // 使用原型方法作为偏移基准 callStackOffset: Core.BaseLogger.prototype.buildCallSnapshot } }); logger.log('e', '非Error类型日志内容将应用偏移基准'); logger.log(new Error('Error类型日志内容不受偏移基准影响'));
1
2
3
4
5
6
7
8
9
10
11
12#
_checkStateInLog
设置此配置将指定日志输出器在执行实例方法
log
时是否检查输出器状态,默认值为:true
。通常,日志输出器的状态(开启/关闭)应与输出动作所需资源的创建与否具有相关性,因此引入了此配置以在执行输出动作时检查输出器状态。
提示
启用此配置后,如果日志输出器在执行输出动作时处于关闭状态,则抛出类型为
BASE_LOGGER_ERROR_TYPE_CANT_LOG
的异常信息且不再执行后续的实际输出链路。我们可以设置异常处理器
_errorHandler
以捕获和处理状态检查失败时抛出的异常。#
_checkStateInStart
设置此配置将指定日志输出器在执行实例方法
start
时是否检查输出器状态,默认值为:true
。警告
日志输出器默认开启此配置用于避免资源创建逻辑多次执行,请谨慎调整此配置。
提示
启用此配置后,如果日志输出器在执行启动动作时已处于启动状态,则抛出类型为
BASE_LOGGER_ERROR_TYPE_CANT_START
的异常信息且不再执行后续的实际启动逻辑。我们可以设置异常处理器
_errorHandler
以捕获和处理状态检查失败时抛出的异常。#
_checkStateInClose
设置此配置将指定日志输出器在执行实例方法
close
时是否检查输出器状态,默认值为:true
。警告
日志输出器默认开启此配置用于避免资源释放逻辑多次执行,请谨慎调整此配置。
提示
启用此配置后,如果日志输出器在执行关闭动作时已处于关闭状态,则抛出类型为
BASE_LOGGER_ERROR_TYPE_CANT_CLOSE
的异常信息且不再执行后续的实际关闭逻辑。我们可以设置异常处理器
_errorHandler
以捕获和处理状态检查失败时抛出的异常。#
_errorHandler
设置此配置将指定日志输出器的异常处理器。
说明
日志输出器将自动捕获关键动作中产生的异常并调用实例方法
onError
,默认将直接触发异常处理器。通常,我们在自定义输出器时应根据关键动作实际逻辑的同步或异步指定对应的实例方法为
Function
或AsyncFunction
,在其中使用throw
即可直接触发异常处理,日志输出器将自动根据产生异常的关键动作计算其类型。对于需要自定义类型或无法通过
throw
抛出的异常,我们可以直接调用实例方法onError
。异常处理器接收一个参数列表为
(errType, errDetail)
的Function
:errType
:异常的类型,为String
。日志输出器将根据产生异常的关键动作计算其类型:
异常类型 描述 产生原因 BASE_LOGGER_ERROR_TYPE_CANT_LOG
日志输出器无法执行输出动作 日志输出链路中产生了异常 BASE_LOGGER_ERROR_TYPE_CANT_INIT
日志输出器无法构建和实例化 输出器初始化时产生了异常 BASE_LOGGER_ERROR_TYPE_CANT_START
日志输出器无法启动 输出器启动时产生了异常 BASE_LOGGER_ERROR_TYPE_CANT_CLOSE
日志输出器无法关闭 输出器关闭时产生了异常 errDetail
:异常的细节信息,为Object
。通常,细节信息的结构中至少包含
{ error }
以指示造成异常的Error
对象。在日志输出器执行实例方法
start
、close
和log
未通过状态校验时将产生以下Error
:提示
Error描述文案的相关宏变量存储在
Core.Messages
中,我们可以在Error
产生前修改对应宏变量的值以实现对Error描述文案的变更。Error描述文案 描述 产生原因 BASE_LOGGER_MESSAGE_CANT_LOG
日志输出器无法输出日志 执行日志输出时无法通过状态检验 BASE_LOGGER_MESSAGE_CANT_START
日志输出器无法启动 执行启动动作时无法通过状态校验 BASE_LOGGER_MESSAGE_CANT_CLOSE
日志输出器无法关闭 执行关闭动作时无法通过状态校验
# 日期输出器
日期输出器提供了按时间周期归档日志的能力。
我们可以使用日期输出器将同一时间周期内产生的日志输出至单个日志文件或是按照体积自动拆分为日志文件组。
同时,我们还可以指定归档周期保留数量,日期输出器将滚动清理过期的日志文件。
提示
日期输出器继承自基础输出器,支持其全部功能参数。
另外,日期输出器在运行环境为BASE_LOGGER_DEVELOPMENT_ENVIRONMENT
时,将保持与基础输出器一致的输出行为,即:将日志打印至控制台。
此时,当输出器执行启动和关闭动作时,仅进行基本状态判断,不再触发对资源的计算和操作。
# 功能说明
将日志按照指定的归档周期归档至文件中。
支持日志文件按体积自动分割。
支持设置归档周期保留数量,以自动清理过时的日志文件。
支持设置日志文件的输出目录和输出文件名的构成规则。
提示
输出目录可能由归档源目录和归档前缀构成。
输出文件名结构为:<%归档前缀%>.<%归档日期%>.<%归档偏移%>.<%文件后缀%>。
构成元素 规则 归档前缀 实例化日期输出器时配置 filePrefix
和filePrefixAsFileName
附加至输出文件名中归档日期 一定出现在归档文件名中;日期格式将使用实例化日期输出器时指定的 dateFormat
归档偏移 实例化日期输出器时配置 maxSize
启动日志文件自动分割时会附加至输出文件名中文件后缀 实例化日期输出器时配置 keepFileExt
启用日志文件后缀时会附加至输出文件名中输出环境异常时自动降级以保证日志完整性。
说明
当日期输出器无法创建日志输出目录或输出文件时,运行环境自动降级至
BASE_LOGGER_DEVELOPMENT_ENVIRONMENT
将日志打印至控制台。
# 异常捕获
在日期输出器的异常处理器中可能接收到拓展的自定义异常类型(即:errType
):
异常类型 | 描述 | 产生原因 |
---|---|---|
BASE_LOGGER_ERROR_TYPE_CANT_CREATE_FILE | 无法创建日志文件 | 应用权限不足或操作系统异常(磁盘不足等) |
BASE_LOGGER_ERROR_TYPE_CANT_CREATE_FOLDER | 无法创建输出目录 | 应用权限不足或操作系统异常(磁盘不足等) |
同时,日期输出器拓展的自定义异常类型附带的细节信息(即:errDetail
)将更加详细,其结构为{ error, errorPath, errorDescription }
。
error
:导致异常的Error
实例。errorPath
:导致异常的日志输出目录/文件路径。errorDescription
:异常描述。
提示
异常描述文案的相关宏变量存储在Core.Messages
中, 我们同样可以通过修改对应宏变量的值以实现对异常描述文案的变更。
异常描述文案 | 产生时机 | 异常类型归属 |
---|---|---|
BASE_LOGGER_MESSAGE_CANT_CREATE_FILE | 日期输出器无法创建日志文件时 | BASE_LOGGER_ERROR_TYPE_CANT_CREATE_FILE |
BASE_LOGGER_MESSAGE_CANT_CREATE_FOLDER | 日期输出器无法创建输出目录时 | BASE_LOGGER_ERROR_TYPE_CANT_CREATE_FOLDER |
# 功能参数
#
sourcePath
设置此配置将指定日期输出器的归档源目录,默认值为
path.resolve(process.cwd(), `./logs/${process.pid}`)
。归档源目录是进行日志文件分割和清理的工作区,我们将在日志分割和日志清理中进行详细讨论。
提示
由于文件系统限制,我们在指定归档源目录时使用的
<:?*"|>
这7个非法字符将被自动替换为'_'
。在使用了
Cluster
架构的应用程序中,多进程抢夺相同的文件资源将导致日志归档异常。因此,我们应尽量使用进程标识符(比如:process.pid
)作为归档源目录的构成元素。当由归档源目录和归档前缀构成的日志输出目录在文件系统中不存在时,日期输出器将在实例化过程中尝试创建,创建失败将导致运行环境降级。
通常,应用程序中的日志输出器应使用相同的归档源目录以实现日志的集中存储:
const Core = require('node-corejs'); // 公用的日志归档源目录 const sourcePath = './testlogs'; // 创建第一个日期输出器 const logger1 = new Core.DateLogger({ env: 'prod', params: { filePrefix: 'DateLogger1', sourcePath } }); // 创建第二个日期输出器 const logger2 = new Core.DateLogger({ env: 'prod', params: { filePrefix: 'DateLogger2', sourcePath } });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#
filePrefix
设置此配置将指定日期输出器的归档前缀,默认值为
''
。归档前缀可以作为日志输出目录和输出文件名的组成部分。因此,由于文件系统限制,我们在指定归档前缀时使用的
<\.:?*"|/>
这10个非法字符将被自动替换为'_'
。#
filePrefixAsSourcePath
设置此配置将指定日期输出器是否在归档源目录中创建以归档前缀命名的目录用于存放日志文件,即在文件系统中使用归档前缀分类日志文件,默认值为
true
。#
filePrefixAsFileName
设置此配置将指定日期输出器是否在输出文件名中展示归档前缀,默认值为
true
。
让我们来看一个使用归档前缀构建输出目录和输出文件名的🌰:
const Core = require('node-corejs');
const env = 'prod';
const level = 'infos';
const sourcePath = './testlogs';
// 日志输出目录:./testlogs/DateLogger1
// 日志输出文件:[归档日期].log
const logger1 = new Core.DateLogger({
env,
level,
params: {
sourcePath,
filePrefix: 'DateLogger1',
filePrefixAsFileName: false,
filePrefixAsSourcePath: true,
}
});
// 日志输出目录:./testlogs/DateLogger2
// 日志输出文件:DateLogger2.[归档日期].log
const logger2 = new Core.DateLogger({
env,
level,
params: {
sourcePath,
filePrefix: 'DateLogger2',
filePrefixAsFileName: true,
filePrefixAsSourcePath: true,
}
});
// 日志输出目录:./testlogs
// 日志输出文件:DateLogger3.[归档日期].log
const logger3 = new Core.DateLogger({
env,
level,
params: {
sourcePath,
filePrefix: 'DateLogger3',
filePrefixAsFileName: true,
filePrefixAsSourcePath: false,
}
});
// 执行日志输出
logger1.log('一条🌰日志');
logger2.log('一条🌰日志');
logger3.log('一条🌰日志');
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#
dateFormat
设置此配置将指定日期输出器的归档周期和归档日期格式,默认值为
YYYY-MM-DD
。归档日期将根据此配置中指定的归档日期格式附加至输出文件名。因此,由于文件系统限制,我们在指定此配置时使用的
<\.:?*"|/>
这10个非法字符将被自动替换为'_'
。说明
日期输出器支持以月、日、时、分、秒(因年周期过长,故不支持)作为归档周期。
我们在指定归档日期格式时需要使用递进方式组合日期元素,即归档日期格式必须包含归档周期前置的所有日期元素。比如:
选择月作为归档周期
归档日期格式需要包含月及其前置的所有日期元素,即:年(
'YYYY'
)和月('MM'
)。此时,我们可以指定归档日期格式为:
'YYYY-MM'
。当选择分作为归档周期
归档日期格式需要包含分及其前置的所有日期元素,即:年(
'YYYY'
)、月('MM'
)、日('DD'
)、时('HH'
)和分('mm'
)。此时,我们可以指定归档日期格式为:
'YYYY-MM-DD_HH_mm'
。
日期元素及其表示方式从前至后依次为:
- 年:
'YYYY'
- 月:
'MM'
- 日:
'DD'
- 时:
'HH'
- 分:
'mm'
- 秒:
'ss'
接下来,我们尝试构建一个以秒为归档周期的日期输出器:
const Core = require('node-corejs'); // 创建输出器 const logger = new Core.DateLogger({ env: 'prod', level: 'infos', params: { sourcePath: './testlogs', dateFormat: 'YYYY-MM-DD_HH_mm_ss' // 指定日期格式和归档周期 } }); // 每150ms执行一次日志输出 setInterval(() => { logger.log('一条🌰日志') }, 150);
1
2
3
4
5
6
7
8
9
10
11
12
13
14#
keepDateNum
设置此配置将指定日期输出器的归档周期保留数量,当指定了
<=0
的值时将关闭自动清理,默认值为0
。提示
日期输出器将保留包含当前归档周期在内的最近
keepDateNum
个周期内生成的日志文件。比如:指定归档周期保留数量为
2
。指定归档日期格式为
'YYYY-MM'
。当前日期为2011年10月,且日志输出目录中有五个日志文件:
2011-01.log
2011-02.log
2011-05.log
2011-08.log
2011-10.log
清理动作完成后,将只保留
2011-10.log
、2011-08.log
两个日志文件。#
maxSize
设置此配置将指定日期输出器触发日志文件分割的最大体积,单位为
Byte
,当指定了<=0
的值时将关闭自动分割,默认值为0
。提示
启动自动分割后,输出文件名中将附加归档偏移。
#
keepFileExt
设置此配置将指定日期输出器是否在输出文件名中附加文件后缀
.log
,默认值为true
。
接下来,我们将在一个复杂的场景下使用日期输出器进行日志收集:
保留最近5个归档日期内的日志。
将每秒内产生的日志归档至同一日志文件,超出
1KB
时自动分割。日志文件按照归档前缀存储,且输出文件名中不展示归档前缀。
const Core = require('node-corejs');
// 构建输出器
const logger = new Core.DateLogger({
env: 'prod',
params: {
maxSize: 1024, // 最大日志文件体积为1K
keepDateNum: 5, // 保留最近5个周期
sourcePath: './testlogs', // 归档源目录
filePrefix: 'ComplexLogger', // 归档前缀
filePrefixAsFileName: false, // 输出文件名不附加归档前缀
filePrefixAsSourcePath: true, // 在归档源目录中创建归档前缀目录
dateFormat: 'YYYY-MM-DD_HH_mm_ss', // 以秒作为归档周期
},
});
// 每100ms执行一次日志输出
setInterval(() => logger.log(new Error(`一个🌰日志`)), 100);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 配置联动
在日期输出器的功能参数中,其中三个功能参数具有联动关系:
- 归档前缀:
filePrefix
- 使用归档前缀构建输出目录:
filePrefixAsSourcePath
- 使用归档前缀构建输出文件名:
filePrefixAsFileName
首先,我们先分析其联动逻辑关系:
- 当归档前缀无效时,则归档前缀无法参与输出目录和输出文件名的构建。
- 当归档前缀有效时,则归档前缀至少参与输出目录和输出文件名其中一项的构建,否则设置归档前缀没有意义。
说明
当归档前缀没有指定或是指定为''
时,认为无效。
因此,日期输出器将自动进行功能参数校正以满足联动关系:
当
filePrefix
无效时,将锁定filePrefixAsSourcePath
和filePrefixAsFileName
为false
。当
filePrefix
有效时,则filePrefixAsSourcePath
和filePrefixAsFileName
中必须至少有一项指定为true
。如果两项都指定为
false
,则锁定filePrefixAsSourcePath
为true
。
提示
日期输出器将在功能功能参数违反联动关系时,优先保证使用归档前缀构建输出目录而不是用于构建输出文件名。
# 日志分割
在自动分割日志文件的模式下,日期输出器将在执行日志输出动作时根据日志文件的体积情况自动定向目标输出文件。
提示
自动分割功能不会对已产生的日志文件进行分割。
接下来,我们将讨论日志分割的实现方案:
日期输出器执行启动动作时
在其归档源目录中提取相同归档前缀且相同归档周期内全部日志文件以进行日志分割的首次仲裁计算,将取得初始归档偏移和日志文件的体积。
提示
当日期输出器启用使用归档前缀构建日志输出目录时,将提升日志文件检索效率。
日志输出器执行输出动作时
计算输出动作造成的体积增量,并与分割阈值
maxSize
进行比较,以仲裁后续归档偏移。
注意
当多个启用了日志分割的日期输出器将日志输出至相同的日志输出目录,且拥有相同的归档前缀和归档周期时,将可能导致日志分割紊乱。
即:多个日期输出器的
sourcePath
、filePrefix
、filePrefixAsSourcePath
和dateFormat
指定了相同的值。
因此,我们在使用日期输出器时通常:
在单进程维度,每个日期输出器指定唯一的归档前缀。
在
Cluter
架构下,使用进程标识参与构建归档源目录,以隔离不同进程内日期输出器对应的日志输出目录。
# 日志清理
Corejs内置了用于定时进行文件清理的清理引擎,将统一管理挂载了清理规则的文件系统目录。
清理引擎将在单个文件系统目录挂载多个清理规则时自动仲裁触发周期,以避免过于频繁的磁盘读写。
日志清理同样使用分治设计,清理引擎将周期性选择日志输出目录中符合以下全部条件的日志文件按照归档周期保留数量进行删除:
- 归档日期早于当前日期
- 与当前日期输出器拥有相同的归档前缀
- 与当前日期输出器拥有相同的归档日期格式
提示
日期输出器将在执行启动动作时向清理引擎中添加清理规则:
未使用归档前缀构建日志输出目录时,日期输出器向归档源目录的规则池中添加清理规则。
使用了归档前缀构建日志输出目录时,日期输出器向归档源目录下归档前缀目录的规则池中添加清理规则。
因此,我们通常使用归档前缀构建日志输出目录,以提高清理仲裁逻辑的执行效率。
# 文件输出器
文件输出器提供了将日志输出至单一文件的能力。
通常,我们使用文件输出器对频次型业务产生的日志进行收集,将每次业务执行链路中的日志输出至同一文件。
提示
文件输出器继承自基础输出器,支持其全部功能参数。
另外,文件输出器在运行环境为BASE_LOGGER_DEVELOPMENT_ENVIRONMENT
时,将保持与基础输出器一致的输出行为,即:将日志打印至控制台。
此时,当输出器执行启动和关闭动作时,仅进行基本状态判断,不再触发对资源的计算和操作。
# 功能说明
文件输出器将其产生的所有日志输出至同一的文件。
支持自动/手动管理文件输出器的状态和资源。
支持设置日志文件的输出目录和输出文件名的构成规则。
提示
日志文件的输出目录和输出文件名由多个功能参数(归档源目录、归档前缀、归档周期等)共同决定。
通常,我们将相同业务产生的日志归档至相同的输出目录中,并使用不同的输出文件名以区分业务调用的次序。
支持设置归档周期和周期保留数量,以自动清理过时的输出文件。
输出环境异常时自动降级以保证日志完整性。
说明
当文件输出器无法创建日志输出目录或输出文件时,运行环境自动降级至
BASE_LOGGER_DEVELOPMENT_ENVIRONMENT
将日志打印至控制台。
# 异常捕获
在文件输出器的异常处理器中可能接收到拓展的自定义异常类型(即:errType
):
异常类型 | 描述 | 产生原因 |
---|---|---|
BASE_LOGGER_ERROR_TYPE_CANT_CREATE_FILE | 无法创建日志文件 | 应用权限不足或操作系统异常(磁盘不足等) |
BASE_LOGGER_ERROR_TYPE_CANT_CREATE_FOLDER | 无法创建输出目录 | 应用权限不足或操作系统异常(磁盘不足等) |
同时,文件输出器拓展的自定义异常类型附带的细节信息(即:errDetail
)将更加详细,其结构为{ error, errorPath, errorDescription }
。
error
:导致异常的Error
实例。errorPath
:导致异常的日志输出目录/文件路径。errorDescription
:异常描述。
提示
异常描述文案的相关宏变量存储在Core.Messages
中, 我们同样可以通过修改对应宏变量的值以实现对异常描述文案的变更。
异常描述文案 | 产生时机 | 异常类型归属 |
---|---|---|
BASE_LOGGER_MESSAGE_CANT_CREATE_FILE | 文件输出器无法创建日志文件时 | BASE_LOGGER_ERROR_TYPE_CANT_CREATE_FILE |
BASE_LOGGER_MESSAGE_CANT_CREATE_FOLDER | 文件输出器无法创建输出目录时 | BASE_LOGGER_ERROR_TYPE_CANT_CREATE_FOLDER |
此外,文件输出器在输出器启动和关闭动作时可能产生新的Error
。
提示
Error
信息文案的相关宏变量存储在Core.Messages
中, 我们同样可以通过修改对应宏变量的值以实现对Error
信息文案的变更。
Error 信息文案 | 产生时机 | Error 类型归属 |
---|---|---|
FILE_LOGGER_MESSAGE_CANT_START_IN_AUTO | 自动模式下的文件输出器执行了start() | BASE_LOGGER_ERROR_TYPE_CANT_START |
FILE_LOGGER_MESSAGE_CANT_CLOSE_IN_AUTO | 自动模式下的文件输出器执行了close() | BASE_LOGGER_ERROR_TYPE_CANT_CLOSE |
# 运行模式
文件输出器支持使用两种管理状态和资源的模式:
自动模式
文件输出器的默认运行模式。
此模式下,文件输出器将自动进行状态和资源管理:
- 执行日志输出动作前,如果文件输出器处于关闭状态,将自动执行启动动作,再执行日志输出。
- 执行日志输出动作后,如果文件输出器在等待阈值
timeout
内无日志输出动作,将自动执行关闭动作。
注意
在自动模式下,手动执行文件输出器的启动和关闭将产生异常,我们可以在异常处理器中对其进行捕获和处理。
因此,我们无需在业务层中显式调用使用了自动模式的文件输出器的实例方法
start()
和close()
,直接使用实例方法log()
输出日志即可。手动模式
此模式下,我们需要手动对文件输出器进行状态和资源管理:
- 执行实例方法
start()
:启动文件输出器,并创建文件句柄等必要资源。 - 执行实例方法
close()
:关闭文件输出器,并释放文件句柄等必要资源。
注意
在文件输出器处于关闭状态时执行输出动作,或重复启动/关闭时将产生异常,我们可以在异常处理器中对其进行捕获和处理。
- 执行实例方法
# 使用规则
每次业务执行时实例化文件输出器,调整相关功能参数以指定日志的输出目录和输出文件名:
- 对于同类型业务,实例化文件输出器时使用相同的归档源目录、归档前缀和归档周期及其附属配置,以将同类型业务产生的日志归档至相同的输出目录。
- 在输出文件名中附带次序标识或内置随机文件名,以在输出目录中区分每次业务执行链路。
# 输出目录
我们无法直接指定日志最终的输出目录,而是通过调整归档源目录、归档前缀和归档日期及其附属配置间接指定。
因此,日志的输出目录将由归档源目录、归档前缀和归档日期中的一项或几项组合而成。
注意
仅在文件输出器使用归档前缀预分类日志文件时,才能通过归档日期进行二次分类。
即:输出目录构成规则为path.resolve(<%归档源目录%>[, <%归档前缀%>[, <%归档日期%>]])
。
我们将在配置联动一节中详细讨论功能参数间的依赖关系。
# 输出文件名
我们也无法直接指定日志最终的输出文件名,同样是通过调整归档前缀、归档日期和归档文件名及其附属配置间接指定。
输出文件名的结构为:<%归档前缀%>.<%归档文件名%>.<%归档日期%>.<%归档类型%>.<%文件后缀%>。
构成元素 | 规则 |
---|---|
归档前缀 | 实例化文件输出器时,指定功能参数filePrefix 和filePrefixAsFileName 附加至输出文件名中 |
归档文件名 | 一定出现在输出文件名中;首先应用实例化文件输出器时的功能参数fileName ,当此配置无效时则使用长度为16 的随机字符串 |
归档日期 | 实例化文件输出器时,指定功能参数dateFormat 和dateAsFileName 附加至输出文件名中 |
归档类型 | 文件输出器将根据输出文件名构成元素的排列方式自动生成 |
文件后缀 | 实例化文件输出器时,指定功能参数keepFileExt 附加至输出文件名中 |
说明
归档类型由归档前缀、归档日期和归档文件名的排列方式决定:
当输出文件名由归档文件名和归档日期构成时,归档类型为
[FN]
。当输出文件名由归档前缀和归档文件名构成时,归档类型为
[FP]
。其他场景不附加归档类型至输出文件名。
需要注意的是,归档类型记录了输出文件名的元信息,对输出目录中已生成的日志文件进行重命名操作将导致元信息损坏。
# 功能参数
#
sourcePath
设置此配置将指定文件输出器的归档源目录,默认值为
path.resolve(process.cwd(), `./logs/${process.pid}`)
。归档源目录是进行日志文件清理的工作区,我们将在日志清理中进行详细讨论。
提示
由于文件系统限制,我们在指定归档源目录时使用的
<:?*"|>
这7个非法字符将被自动替换为'_'
。在使用了
Cluster
架构的应用程序中,多进程抢夺相同的文件资源将导致日志归档异常。因此,我们应尽量使用进程标识符(比如:process.pid
)作为归档源目录的构成元素。当由归档源目录、归档前缀和归档日期中的一项或几项构成的输出目录在文件系统中不存在时,文件输出器将在实例化过程中尝试创建,创建失败将导致运行环境降级。
通常,日志输出器应使用相同的归档源目录以实现日志的集中存储。
当然,文件输出器可以与日期输出器使用相同的归档源目录:
const Core = require('node-corejs'); // 日志归档源目录 const sourcePath = './testlogs'; // 创建日期输出器 const dateLogger = new Core.DateLogger({ env: 'prod', level: 'infos', params: { filePrefix: 'DateLogger', sourcePath } }); // 创建文件输出器 const fileLogger = new Core.FileLogger({ env: 'prod', level: 'infos', params: { filePrefix: 'FileLogger', sourcePath } }); // 执行日志输出 dateLogger.log('一条🌰日志'); fileLogger.log('一条🌰日志');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22#
auto
设置此配置将指定文件输出器是否使用自动模式作为运行模式,默认值为
true
。#
timeout
设置此配置将指定文件输出器在自动模式中自动释放资源的等待时间,单位为毫秒,默认值为
10000
。提示
在手动模式下,此配置将被锁定为
0
。在自动模式下指定了
<=0
的值时,将使用默认值10000
。
使用自动模式时,我们无需显式调用start()
和close()
即可执行日志输出动作。
在自动模式强行调用文件输出器的实例方法start()
或close()
将抛出异常,我们可以通过在实例化时配置异常处理器_errorHandler
进行捕获和处理:
const Core = require('node-corejs');
// 创建使用自动模式的文件输出器
const logger = new Core.FileLogger({
env: 'prod',
params: {
// 指定使用自动模式
auto: true,
timeout: 10000,
// 将捕获到的异常输出到控制台
_errorHandler(errType, errDetail) { console.log(errType, errDetail) }
}
});
// 强行调用start()和close()
logger.start();
logger.close();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
使用手动模式时,如果在执行日志输出动作前没有调用start()
启动文件输出器,同样会抛出异常:
const Core = require('node-corejs');
// 创建使用手动模式的文件输出器
const logger = new Core.FileLogger({
env: 'prod',
params: {
// 指定使用手动模式
auto: false,
// 将捕获到的异常输出到控制台
_errorHandler(errType, errDetail) { console.log(errType, errDetail) }
}
});
// 强行发起log()
logger.log('一条🌰日志');
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#
fileName
设置此配置将指定文件输出器的归档文件名,默认根据进程标识、当前时间戳和8位随机小写字母和数字生成16长度的字符串作为归档文件名。
归档文件名将作为输出文件名的组成部分。因此,由于文件系统限制,我们在指定此配置时使用的
<\.:?*"|/>
这10个非法字符将被自动替换为'_'
。
通常,归档文件名由业务自定义部分和随机字符串部分共同组成。
此场景下,我们可以在指定归档文件名时使用宏字符串
[%RANDOM_FILE_NAME%]
以引用文件输出器内置的随机字符串:const Core = require('node-corejs'); setInterval(() => { const logger = new Core.FileLogger({ env: 'prod', level: 'infos', params: { sourcePath: './testlogs', // 在fileName中使用宏字符串 fileName: 'LogFile_[%RANDOM_FILE_NAME%]' } }); // 日志输出文件名为LogFile_[%随机字符串%].log logger.log('一个🌰日志'); }, 500);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#
filePrefix
设置此配置将指定文件输出器的归档前缀,默认值为空字符串
''
。归档前缀可以作为输出目录和输出文件名的组成部分。因此,由于文件系统限制,我们在指定归档前缀时使用的
<\.:?*"|/>
这10个非法字符将被自动替换为'_'
。#
filePrefixAsSourcePath
设置此配置将指定文件输出器是否在归档源目录中创建以归档前缀命名的目录用于存放日志文件,即在文件系统中使用归档前缀分类日志文件,默认值为
true
。#
filePrefixAsFileName
设置此配置将指定文件输出器是否在输出文件名中展示归档前缀,默认值为
true
。#
dateFormat
设置此配置将指定文件输出器的归档周期和归档日期格式,默认值为
YYYY-MM-DD
。归档日期将根据此配置中指定的归档日期格式附加至输出文件名。因此,由于文件系统限制,我们在指定此配置时使用的
<\.:?*"|/>
这10个非法字符将被自动替换为'_'
。说明
文件输出器支持以月、日、时、分、秒(因年周期过长,故不支持)作为归档周期。
我们在指定归档日期格式时需要使用递进方式组合日期元素,即归档日期格式必须包含归档周期前置的所有日期元素。比如:
选择月作为归档周期
归档日期格式需要包含月及其前置的所有日期元素,即:年(
'YYYY'
)和月('MM'
)。此时,我们可以指定归档日期格式为:
'YYYY-MM'
。当选择分作为归档周期
归档日期格式需要包含分及其前置的所有日期元素,即:年(
'YYYY'
)、月('MM'
)、日('DD'
)、时('HH'
)和分('mm'
)。此时,我们可以指定归档日期格式为:
'YYYY-MM-DD_HH_mm'
。
日期元素及其表示方式从前至后依次为:
- 年:
'YYYY'
- 月:
'MM'
- 日:
'DD'
- 时:
'HH'
- 分:
'mm'
- 秒:
'ss'
#
dateAsSourcePath
设置此配置将指定文件输出器是否在归档源目录中以归档前缀命名的目录下创建以归档日期命名的目录用于存放日志文件,即在文件系统中使用归档前缀和归档日期分类日志文件,默认值在
filePrefix
或dateFormat
无效时为false
,其他情况时为true
。#
dateAsFileName
设置此配置将指定文件输出器是否在输出文件名中展示归档日期,默认值在
dateFormat
无效时为false
,其他情况时为true
。#
keepDateNum
设置此配置将指定文件输出器的归档周期保留数量,当指定的归档日期格式无效或指定了
<=0
的值时将关闭自动清理,默认值为0
。提示
文件输出器将保留包含当前归档周期在内的最近
keepDateNum
个周期内生成的日志文件。比如:指定归档周期保留数量为
2
。指定归档日期格式为
'YYYY-MM'
。当前日期为2011年10月,且输出目录中有五个日志文件:
2011-01.log
2011-02.log
2011-05.log
2011-08.log
2011-10.log
清理动作完成后,将只保留
2011-10.log
、2011-08.log
两个日志文件。#
keepFileExt
设置此配置将指定文件输出器是否在输出文件名中附加文件后缀
.log
,默认值为true
。
假设我们有一个间隔150ms
执行的频次型业务需要使用文件输出器进行日志收集:
仅保留最近5秒内的日志
将此业务每秒产生的日志存储至相同目录
输出文件名中展示业务执行次序和归档时间
const Core = require('node-corejs');
let count = 0;
// 每150ms创建文件输出器并输出日志以模拟每次业务执行
setInterval(() => {
count += 1;
const logger = new Core.FileLogger({
env: 'prod',
level: 'infos',
params: {
fileName: count, // 归档文件名
sourcePath: './testlogs', // 归档源目录
filePrefix: 'ComplexLogger', // 归档前缀
filePrefixAsSourcePath: true, // 使用归档前缀预分类日志
filePrefixAsFileName: false, // 输出文件名不附加归档前缀
dateFormat: 'YYYY-MM-DD_HH_mm_ss', // 归档日期格式
dateAsSourcePath: true, // 使用归档日期分类日志
dateAsFileName: true, // 输出文件名不附加归档日期
keepDateNum: 5 // 保留最近5个归档日期
}
});
// 每个文件输出器执行4次日志输出以模拟业务链路内产生日志
logger.log('i', `infos级别的🌰日志 -> ${count}`);
logger.log('w', `warns级别的🌰日志 -> ${count}`);
logger.log('e', `error级别的🌰日志 -> ${count}`);
logger.log('f', `fatal级别的🌰日志 -> ${count}`);
}, 150);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 配置联动
在文件输出器的功能参数中,有两组联动关系:
# 归档前缀联动
- 归档前缀:
filePrefix
- 使用归档前缀构建输出目录:
filePrefixAsSourcePath
- 使用归档前缀构建输出文件名:
filePrefixAsFileName
# 归档日期联动
- 归档日期格式:
dateFormat
- 使用归档日期构建输出目录:
dateAsSourcePath
- 使用归档日期构建输出文件名:
dateAsFileName
首先,我们分析归档前缀的联动逻辑关系:
- 当归档前缀无效时,则归档前缀无法参与输出目录和输出文件名的构建。
- 当归档前缀有效时,则归档前缀至少参与输出目录和输出文件名其中一项的构建,否则设置归档前缀没有意义。
接下来,我们分析归档日期的联动逻辑关系。归档日期与归档前缀的联动关系类似:
- 如果归档日期格式无效,则归档前缀无法参与输出目录和输出文件名的构建。
- 如果归档日期格式有效,则归档日期至少参与输出目录和输出文件名其中一项的构建,否则设置归档日期格式没有意义。
说明
当归档前缀、归档日期格式没有指定或指定为''
时,认为其无效。
另外,我们已经知道,使用归档前缀预分类日志文件时,才能通过归档日期对日志文件进行二次分类。即:只有在使用归档前缀构建输出目录时才允许使用归档日期构建输出目录。
因此:如果归档前缀不参与构建输出目录,则归档日期也无法参与构建输出目录。
文件输出器将自动进行功能参数校正以满足联动关系。
对于归档前缀的联动关系,文件输出器将确保:
当
filePrefix
无效时,将锁定filePrefixAsSourcePath
和filePrefixAsFileName
为false
。当
filePrefix
有效时,则filePrefixAsSourcePath
和filePrefixAsFileName
中必须至少有一项指定为true
。如果两项都指定为
false
,则锁定filePrefixAsSourcePath
为true
。
对于归档日期的联动关系,文件输出器将确保:
当
dateFormat
无效时,将锁定dateAsSourcePath
和dateAsFileName
为false
。当
dateFormat
有效时,则dateAsSourcePath
和dateAsFileName
中必须至少有一项指定为true
。如果两项都指定为
false
,则锁定dateAsSourcePath
为true
。
提示
文件输出器将在功能功能参数违反联动关系时,优先保证使用归档前缀或归档日期构建输出目录而不是用于构建输出文件名。
对于归档前缀和归档日期维度的高阶联动,文件输出器将确保:
当
filePrefix
无效时,将锁定dateAsSourcePath
为false
。当
filePrefix
有效,且filePrefixAsSourcePath
为false
时,将锁定dateAsSourcePath
为false
。当
dateFormat
有效,且dateAsSourcePath
因①或②而被锁定为false
时,则锁定dateAsFileName
为true
。
此策略将使文件输出器在保证归档前缀和归档日期的高阶联动性的基础上,使指定的归档日期格式发挥作用。
# 日志清理
提示
文件输出器的分治清理规则非常抽象,仅做了解即可。
文件输出器同样使用在实例化时向清理引擎注册清理策略的分治方式实现日志清理。
不同的是,文件输出器的实例化通常为高频行为,需要引入防止清理规则重复挂载的反制策略:
输出目录为归档源目录时:
将向归档源目录的规则池中挂载清理规则。
对于相同归档源目录的规则池,将拒绝归档前缀、归档日期格式、归档周期保留数量完全一致的文件输出器进行二次规则挂载。
此时,清理引擎将周期性选择归档源目录下与当前文件输出器拥有相同归档前缀且相同归档日期格式的日志文件按照归档周期保留数量进行清理仲裁。
输出目录非归档源目录时:
将向归档源目录下的归档前缀子目录的规则池中挂载清理规则。
对于相同的归档源目录下的归档前缀目录的规则池,将拒绝使用归档日期构建输出目录、归档日期格式、归档周期保留数量完全一致的文件输出器进行二次规则挂载。
此时,清理引擎将周期性选择归档源目录下的归档前缀子目录下与当前文件输出器拥有相同归档日期格式的日志文件按照归档周期保留数量进行清理仲裁。