盒子
盒子
文章目录
  1. 基本使用
  2. 存储/输出机制(transports)
    1. 内置transports
    2. 自定义transports
  3. 日志文件根据时间切割(翻转)
  4. 日志脱敏
    1. 附上我自己写的脱敏
  5. 最后

2021年了,该会日志脱敏了吧(Node篇)

日志可以为我们提供关于系统行为的必要信息,便于定位线上代码问题,不至于抓瞎🤠,所以说日志是非常重要的,对于Node程序也是一样,日志工具也是多种多样,有log4jsbunyanwinston,今天就来讲讲在GitHub上面star最多的winston

安装不用说吧,就是下面的代码

npm install winston

基本使用

const logger = require('winston')
logger.info('is info') // 也可以写成后面这样 logger.log('info', 'is info')
logger.warn('is warn') // logger.log('warn', 'is warn')
logger.error('is error') // logger.log('error', 'is error')

就会把日志打印到控制台中

[winston] Attempt to write logs with no transports {"message":"is info","level":"info"}
[winston] Attempt to write logs with no transports {"message":"is warn","level":"warn"}
[winston] Attempt to write logs with no transports {"message":"is error","level":"error"}

存储/输出机制(transports)

内置transports

有时候我们希望在控制台接收日志,并把日志保存到文件中

单纯使用pm2out.log来记录日志的同学上述代码就可以实现,并且有level分类

使用docker等非pm2启动的同学,也很简单,比如保存到指定为的的server.log里面,只需要使用以下代码

const winston = require('winston')
const path = require('path')
const logger = winston.createLogger({
transports: [
new winston.transports.Console(),
new winston.transports.File({filename: path.resolve(__dirname, '../logs/server.log')})
]
})
logger.info('print to the console and the file')

以上是使用到winston内置存储/输出机制(transports):

  • Console:控制台传输
  • File:文件传输

除此之外内置的transports还有

  • Http:http传输
  • Stream:流传输

自定义transports

可以自己编写一个继承自 winston.Transport类,并实现类的log方法

const Transport = require('winston-transport');
const util = require('util');

module.exports = class YourCustomTransport extends Transport {
constructor(opts) {
super(opts);
}

log(info, callback) {
setImmediate(() => {
this.emit('logged', info);
});
callback();
}
};

日志文件根据时间切割(翻转)

安装日志翻转组件

npm install winston-daily-rotate-file

const dailyRotateFile = require('winston-daily-rotate-file');
const dateTransport = new dailyRotateFile({
filename: path.resolve(__dirname, './logs/server.log'),
maxSize: '50m',
createSymlink: true,
symlinkName: 'server.log'
})
const logger = winston.createLogger({
transports: [
dateTransport
]
});

上面的maxSize就是文件旋转后的最大大小,单位为k(kb)、m(mb)、g(gb)

会得到文件server.log.2021.01.28

当日志文件大小大于maxSize的时候,能得到

server.log.2021.01.28.1server.log.2021.01.28.2server.log.2021.01.29

createSymlink为真的时候,创建一个从指定名称(symlinkName)连接当前活动日志文件的符号连接

方便ELK扫描

日志脱敏

为了符合国家等保三级规定,必须对日志文件中存在的用户隐私信息进行加密,例如手机号,身份证等等

这时候可以用到winston的自定义格式化功能

即在createLogger的时候使用自定义format

const { createLogger, format } = require('winston');
const util = require('util')
// 自定义format
const formatLog = format.printf(info => {
// logger.info('is info')的info.message为is info
const msg = info.message;
// logger.info('my name is %s, my personal information is %s', 'xiaolin',{ name: 'xiaolin', phone: '15811111111' })
// info[SPLAT]就能取到'xiaolin'和{ name: 'xiaolin', phone: '15811111111' },方便字符串插值
if (typeof (msg) === 'string' && msg.includes('%s')) {// 兼容旧日志(格式)
const splat = info[SPLAT] || info.splat || [];
// 对对象进行脱敏
const splatInfo = splat.map(item => {
if (typeof item === 'object') {
// 在这里取到了phone的值,又知道这个是手机号码,就可以对其做脱敏,具体脱敏逻辑(encryptionLog)自己定义,合理利用正则表达式
return util.inspect(encryptionLog(item), false, null)
}
return item
})
info.message = util.format(msg, ...splatInfo);// 字符串插值
}
// info.timestamp 是使用了内置的格式化插件format.timestamp取到的值
const finalLog = `[${info.timestamp}] [${info.level}] ${info.message}`
return finalLog
})

exports.businessLog = createLogger({
format: format.combine(
format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
formatLog
)
});

附上我自己写的脱敏

下面是我自己简单写的脱敏规则,即对日志进来的对象进行脱敏

const encryRules = require('./encryRules').encryRules
/**
* 日志脱敏深拷贝
* PS: 在深拷贝的时候顺便脱敏,避免修改到原对象
*/
const encryptionLog = (splat, keyName) => {
if (splat === null) return splat;
if (splat instanceof Date) return new Date(splat);
if (splat instanceof RegExp) return new RegExp(splat);
if (typeof splat !== "object") {
if (encryRules.rules.hasOwnProperty(keyName)) {
// 命中规则,进行正则脱敏
return encryRules.rules[keyName](splat)
}
return splat
};
let cloneSplat = new splat.constructor();
for (let key in splat) {
if (splat.hasOwnProperty(key)) {
// 递归拷贝
cloneSplat[key] = encryptionLog(splat[key], key);
}
}
return cloneSplat;
}

加密规则(encryRules.js)

const regularEncrypt = (str, ruleName) => {
if (str != null && str != undefined) {
switch (ruleName) {
case 'phone':// phone
return String(str).replace(/(\d{3})\d{4}(\d{4})/g, '$1****$2')
case 'email':// 邮箱
return str.replace(/([^@]*)/, word => {
word.slice(0, 3) + word.slice(3).replace(/.{1}/g, '*')
})
}
}
return str
}

const encryptPhone = (str) => regularEncrypt(str, 'phone')// 手机号脱敏
const encryptEmail = (str) => regularEncrypt(str, 'email')// 邮箱脱敏

// 规则对象
const rules = {
phone: encryptPhone,
email: encryptEmail
}

exports.encryRules = {
rules
}

最后

上述的功能对于一般场景来说就已经完全足够了🤪,对于winston的其他功能,这里就不一一叙述了,具体可以到Github上面查看相关文档,如果有什么更好的方法,欢迎评论区交流哈哈😜。

最后最后,希望觉得文章还行的靓仔靓女们可以给我点个赞,mua ~

img

赞赏
扫一扫,支持Kobayashi
  • 微信扫一扫
  • 支付宝扫一扫