参考文档
开发环境介绍
- 主机操作系统:Ubuntu16.04 LTS 64-bit
- 目标平台:BoxV3 全志H8
- 交叉工具链:arm-linux-gnueabihf,gcc4.9.2
- node.js版本:6.10.0
- log4js版本:3.0.5
- 开发工具:vs code
日志介绍
日志的意义
在真实的项目中,开发只是整个投入的一小部分。应用或系统真正上线运转起来时,问题有可能会接踵而至。所谓智者千虑,必有一疏。无论多么周密的代码编写,一些未知问题总是可能在某个不确定的时候出现。这种情况下,与其遇见bug修复它,不如建立健全的排查和跟踪机制,而日志就是实现这种机制的关键。在健全的系统中,完善的日志记录最能够还原问题现场。通过记录日志来定位问题时一种成本较小的方式。这种非结构化、轻量的记录方式容易实现,也容易扩展。
日志的功能需求
记录信息
从日志的意义可以看出,日志的主要作用是帮助工作人员排查和跟踪系统异常的位置和原因,以便工作人员能够尽快解决bug和恢复系统正常运行。因此,首要功能便是需要将系统异常信息记录下来,方便工作人员阅读,目前有两种存储日志的方案,即日志文件和数据库(本文主要讲述第一种方案);另外,光是记录系统异常的反馈信息还是不够的,因为一旦程序开始运行,便会积累大量的日志信息,这些信息并不能帮助工作人员快速的还原现场,为了增加日志的可读性,还需要对规范日志信息个格式。
等级划分
日志通常有完善的分级,分级不仅可以帮助工作人员根据异常等级快速检索到亟待解决的问题,也可以帮助工作人员对问题解决顺序进行排序。而且,日志也并非越全越好,日志越全,意味着cpu占用率越高和磁盘空间占用越大。在调试过程中,工作人员可能会想要完整的日志信息来帮助他确定是否在程序的某些未知存在潜在危险,而在发布的版本中,工作人员可能只想要写错误及错误以上级别的日志信息。因此,日志等级划分是非常有必要的,可以在配置中添加想要写的日志级别,那么低于这一级别的日志信息将不会输出。
文件分割
程序运行起来会产生大量的日志文件,尤其是大型服务器,其日志系统不光会记录大量的信息,而且还会长时间运行。日志文件过于庞大的话,不利于工作人员去检索定位异常信息,因此,为了方便工作人员快速定位异常信息,应该对日志文件进行文件分割,这也顺应了高并发的理念。对日志文件进行分割,主要有两种方案,其一为按照文件大小进行分割,其二为按照日期进行分割。
文件压缩
程序运行环境下分配给日志记录的磁盘空间是有限的,为了尽可能多的保存日志信息,对日志文件进行压缩是必要的。
文件更新
一旦程序运行起来,日志信息是不断被记录的,但是磁盘空间是有限的,因此,日志信息并不能一直记录下去,为了既能不断记录新的日志信息,又不使得磁盘空间超限,移除部分最早记录的日志信息是必要的。目前比较主流的方案是日志文件的最大空间和日志文件最多数量,当写满日志文件后,新的日志信息在写入的同时,会删除最旧的日志信息。
log4js日志插件简介
log4js是一款强大nodejs的日志管理工具,可以将日志以各种形式输出到各种渠道。
- stdout或stderr彩色控制台记录
- File Appender,可根据文件大小或日期配置日志滚动
- Connect/Express日志程序
- 可配置日志信息输出格式
- 不同日志类别分级
安装log4js日志插件
npm install log4js
log4js使用说明
添加配置文件
在工程内添加log4js.json配置文件,然后根据需要填写配置文件:
{
"appenders": { // 配置appenders
"file": { // 添加对象名
"type": "fileSync", // 配置对象类型
"filename": "log/file.log", // 配置日志文件存储路径
"maxLogSize": 10485760, // 配置单个文件最大容量
"numBackups": 5, // 配置日志文件最多存在个数
"compress": true, // 配置日志文件是否压缩
"encoding": "utf-8", // 配置编码格式
"mode": "0o0640", // 默认配置
"layout": { // 配置输出格式
"type": "pattern",
"pattern": "[%d] [%p] [%c] | %m"
}
},
"dateFile": { // 配置对象名
"type": "dateFile", // 配置对象类型
"filename": "log/datefile.log", // 配置文件存储类型
"pattern": "yyyy-MM-dd-hh-mm-ss", // 配置输出格式
"compress": true // 配置日志文件是否压缩
},
"console": {
"type": "console"
}
},
"categories": { // 配置日志输出等级
"file": { "appenders": ["file"], "level": "error" },
"default": { "appenders": ["file", "dateFile", "console"], "level": "trace" }
}
}
创建日志对象
const log4js = require('log4js'); // 导入日志插件
log4js.configure('log4js.json'); // 导入配置文件
const logger = log4js.getLogger('file'); // 使用file类型的日志实例
定义日志输出格式
level | 格式 |
---|---|
fatal | 时间戳 + 日志类型 + 日志对象 + 当前操作 + 错误信息描述 + 建议 |
error | 时间戳 + 日志类型 + 日志对象 + 当前操作 + 错误信息描述 + 建议 |
warn | 时间戳 + 日志类型 + 日志对象 + 当前操作 + 警告信息描述 + 建议 |
info | 时间戳 + 日志类型 + 日志对象 + 当前操作 + 信息描述 |
debug | 时间戳 + 日志类型 + 日志对象 + 当前操作 + 调试信息描述 |
trace | 时间戳 + 日志类型 + 日志对象 + 当前操作 + 跟踪信息描述 |
实现日志格式化输出
修改信息等级输出格式
在工程中找到log4js插件包中layouts.js文件,在文件内找到函数logLevel(loggingEvent),将起替换为如下代码:
function logLevel(loggingEvent) {
var logLevelFormat = loggingEvent.level.levelStr;
for (var i = logLevelFormat.length; i < 5; i++) {
logLevelFormat += ' ';
}
return logLevelFormat;
}
添加日志工具包
下载log.js工具包到utils文件目录下,或创建log.js文件,其源码如下:
var assert = require('assert');
function ToDBC(txtstring) {
var tmp = "";
if (txtstring == undefined) {
return ;
}
for (var i = 0; i < txtstring.length; i++) {
if (txtstring.charCodeAt(i) == 32) {
tmp = tmp + String.fromCharCode(12288);
continue ;
}
if (txtstring.charCodeAt(i) < 127) {
tmp = tmp + String.fromCharCode(txtstring.charCodeAt(i) + 65248);
continue ;
}
tmp = tmp + txtstring[i];
}
return tmp;
}
module.exports.writeLogMessage = writeLogMessage = function(handle, descriptionMessage, suggest) {
handle = ToDBC(handle);
descriptionMessage = ToDBC(descriptionMessage);
suggest = ToDBC(suggest);
var tempMessage = '';
if (handle == undefined || descriptionMessage == undefined) {
assert(1, 2, '请添加操作和操作的详细描述信息');
}
if (suggest == undefined) {
tempMessage = '操作:' + handle + '-' + '消息:' + descriptionMessage;
} else {
tempMessage = '操作:' + handle + '-' + '消息:' + descriptionMessage + '-' + '建议' + suggest;
}
var message = tempMessage.split('-');
if (message[0].length > 10) {
assert.equal(1, 2, '日志书写错误:' + message[0] + ':超过10个字符,请精简描述');
return ;
}
if (message[1].length > 30) {
assert.equal(1, 2, '日志书写错误:' + message[1] + ':超过30个字符,请精简描述');
return ;
}
if(message[0].length < 10){
var handleFillLength = 10 - message[0].length;
for (var i = 0; i < handleFillLength; i++) {
message[0] += " ";
}
}
if(message[1].length < 30){
var descriptionFillLength = 30 - message[1].length;
for (var i = 0; i < descriptionFillLength; i++) {
message[1] += " ";
}
}
var formatMessage = "";
for (var i = 0; i < message.length; i++) {
if (i == message.length - 1) {
formatMessage += message[i];
} else {
formatMessage += message[i] + ' | ';
}
}
return formatMessage;
}
在需要写日志的文件下导入。
var log = require('./utils/log');
生成日志文件
logger.fatal(log.writeLogMessage('server', '这个操作有问题这个操作有问题', '更正操作'));
logger.error(log.writeLogMessage('操作', '这个操作有问题这个操作有问题', '更正操作'));
logger.warn(log.writeLogMessage('操作', '这个操作有问题这个', '更正操作'));
logger.info(log.writeLogMessage('server', '这个操作有问题这个操作有问题'));
logger.debug(log.writeLogMessage('server', '这个操作有问题这个操作有问题'));
logger.trace(log.writeLogMessage('操作', '这个操作有问题这个操作有问题'));