UNPKG

wy-log

Version:

An express middleware to record logs for Weiyi Application

251 lines (239 loc) 11 kB
/* options: { level: 日志级别,可选'all','info','error', file: 分割时,需要输入文件夹名, split: 是否分割,可选false,'day','month', ignoreList: [] 访问忽略名单,可以在路由后写入/*代表该路由下的所有子路由 routerList: [] 路由白名单,名单中没有出现的访问路径会被当作404处理 } */ const fs = require('fs') const moment = require('moment') const schedule = require('node-schedule') const defaultOptions = { level: 'all', file: './log', split: false, ignoreList: [], routerList: [] } const writeLogStream = (file,text) => { let logStream logStream = fs.createWriteStream(`${file}`, {flags: 'a'}) logStream.write(text) logStream.end() } const log = options => { //参数错误提示 options = Object.assign({}, defaultOptions, options) let {level, file, split, ignoreList, routerList} = options return (req, res, next) => { let stime = process.hrtime(), // 运行开始时间 allLog, infoLog, errorLog, statusReg = /^4\d{2}|5\d{2}$/, isNext = false // 获取ignoreList参数里带有*的值,以匹配子路由 const getIgnoreParentList = () => { return new Promise((resolve, reject) => { let ignoreParentList = [] ignoreList && ignoreList.forEach((value, index) => { const routerReg = /^\/(.*?)\/\*$/ if(routerReg.test(value)) { ignoreParentList.push('/' + value.split('/')[1]) } if(index === (ignoreList.length - 1)) { resolve(ignoreParentList) } }) }) } // 核心方法,最后会返回一个JSON对象,包含路由对象,日志级别,错误信息等 const getAccessJson = url => { let json = { url } // ignore, level, url const RouterPipe = new Promise((resolve, reject) => { // 判断当前访问路由是不是忽略列表中的一个,如果是就跳入下个中间件 if(ignoreList.length) { let routerIgnoreParentReg, regStr = '' getIgnoreParentList().then(list => { if(list.length) { list.map(value => { regStr += '^\\' + value + '\\/.*?$|' }) regStr = regStr.substring(0,regStr.length - 1) } routerIgnoreParentReg = new RegExp(regStr) if(routerIgnoreParentReg.test(url) || ignoreList.includes(url)) { isNext = true json.ignore = true } if(level === 'all') { if(statusReg.test(res.statusCode) || !routerList.includes(req.url)) { json.level = 'error' } else { json.level = 'info' } } else { json.level = level } console.log(json) resolve(json) }) } }) return RouterPipe } allLog = () => { if(statusReg.test(res.statusCode) || !routerList.includes(req.url)) { errorLog() } else { infoLog() } } infoLog = () => { if(!split) { !fs.existsSync(file) && fs.writeFile(file,'', err => { if(err) { console.error(err) } }) logText = `[access-log] --> url: ${req.url} ip: ${req.header('x-forwarded-for') || req.header('x-real-ip') || req.connection.remoteAddress || req.ip}; code: ${req.status}; ua: ${JSON.stringify(req.headers)}; <-- [/all] \n\n` writeLogStream(file, logText) } else { let logFile !fs.existsSync(file) && fs.mkdirSync(file) if(split === 'day') { logFile = `${file}/access.log` // 每天凌晨过30秒的时候拷贝 access.log --> access-YYYYMMDD.log 日志文件并重新生成一个access.log文件 const rule = '30 0 0 * * *' var j = schedule.scheduleJob(rule, function(){ if (fs.existsSync(logFile)) { // 拷贝 let readable, writable // 创建读取流 readable = fs.createReadStream(logFile); // 创建写入流 writable = fs.createWriteStream(`${file}/access${moment().add(-1, 'days').format('YYYYMMDD')}.log`) // 通过管道来传输流 readable.pipe(writable) let copyError = false readable.on('error', (err) => { console.log(err) copyError = true }) readable.on('close', () => { // 流写完之后再执行回调清空处理 if (!copyError) { // 清空 let removeStream = fs.createWriteStream(`${file}/access.log`, {flags: 'w'}) removeStream.write('') removeStream.end() } }); } }) } let etime = process.hrtime(), // 运行结束时间 duration = (etime[1] - stime[1]) * 1e-3, dataJson = { url: req.url, ip: req.header('x-forwarded-for') || req.header('x-real-ip') || req.connection.remoteAddress || req.ip, ua: JSON.stringify(req.header('user-agent')), code: res.statusCode, time: duration } logText = `[access-log] date: ${moment().format('YYYY MM DD hh:mm:ss')} ###start###${JSON.stringify(dataJson)}###end### [/access-log]\n\n` writeLogStream(logFile, logText) } } errorLog = () => { //访问错误 if(routerList && !routerList.length) { console.error('必须要传入至少一个路由地址') isNext = true return } let errMessage if(statusReg.test(res.statusCode) || !routerList.includes(req.url)) { !routerList.includes(req.url) && (res.statusCode = 404) switch (res.statusCode) { case 404: errMessage = '未找到页面' break case 500: errMessage = '内部服务器错误' break case 502: errMessage = 'Web服务器用作网关或代理服务器时收到了无效响应' break case 403: errMessage = '访问被禁止' break } if(!split) { !fs.existsSync(file) && fs.writeFile(file,'', err => { if(err) { console.log(err) } }) logText = `[error-log] --> date: ${moment().format('YYYY MM DD hh:mm:ss')} url: ${req.url}; code: ${res.statusCode}; errMessage: ${errMessage}; <-- [/error-log] \n\n` writeLogStream(file, logText) } else { let logFile !fs.existsSync(file) && fs.mkdirSync(file) if(split === 'day') { logFile = `${file}/error.log` // 每天凌晨过30秒的时候拷贝 error.log --> error-YYYYMMDD.log 日志文件并重新生成一个error.log文件 const rule = '30 0 0 * * *' var j = schedule.scheduleJob(rule, function(){ if (fs.existsSync(logFile)) { // 拷贝 let readable, writable // 创建读取流 readable = fs.createReadStream(logFile); // 创建写入流 writable = fs.createWriteStream(`${file}/error${moment().add(-1, 'days').format('YYYYMMDD')}.log`) // 通过管道来传输流 readable.pipe(writable) let copyError = false readable.on('error', (err) => { console.log(err) copyError = true }) readable.on('close', () => { // 流写完之后再执行回调清空处理 if (!copyError) { // 清空 let removeStream = fs.createWriteStream(`${file}/error.log`, {flags: 'w'}) removeStream.write('') removeStream.end() } }); } }) } logText = `[error-log] --> date: ${moment().format('YYYY MM DD hh:mm:ss')} url: ${req.url}; code: ${res.statusCode}; errMessage: ${errMessage}; <-- [/error-log] \n\n` writeLogStream(logFile, logText) } } } !isNext && getAccessJson(req.url).then(json => { if(json.ignore) { console.log('the router is ignored') return } switch(json.level) { case 'info': infoLog() break case 'error': errorLog() break } }) next() } } exports = module.exports = log