上节回顾

  • 联表查询
  • 审批增删改查逻辑

工作内容

  • 配置日志系统
  • 测试日志系统
  • 安全策略

准备工作

  • npm i -S koa-loa4 // 先切换到/server目录下
  • npm i -S xss // 先切换到/server目录下

日志系统

技术选型

  • koa-logger

    • 只能设置统一的日志
  • koa-morgan

    • 能根据skip分类,但,可操作性太差
  • koa-log4js

    • 可以定制化
    • 周期性存储

koa-log4js规范对象

  • 参考文档
  • log4默认是禁用的,不会打印日志:log4js.configure()
  • log4通过配置项开启日志功能:log4js.configure({...})
// configure规范对象
{

  // 自定义日志等级/修改内部已定义的日志等级
  // 日志等级:OFF > MARK > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL
  levels: {
    ...
  },
  {
    // 定义日志输出类型 (参考文档:https://github.com/log4js-node/log4js-node/blob/master/docs/appenders.md)
    appenders: {
      error: {
        // type是必选属性,其它属性配置依赖于type属性值
        type: 'dateFile', // (dateFile参考文档:https://github.com/log4js-node/log4js-node/blob/master/docs/dateFile.md)
        filename: path.resolve(__dirname, 'logs', 'error', 'filename'), // 定义日志存储位置,与最终指向的目录/文件同级:filename推荐不要加后缀名
        pattern: '.yyyy-MM-dd.log', // 日志周期,这里加上文件后缀
        alwaysIncludePattern: true, // alwaysIncludePattern为true时,才会实现以pattern规则(如:yyyy-MM-dd)新建文件一天一存
        //keepFileExt: true // 没有像官网说的那样生效,所以,通过设置pattern值带后缀文件名来替代
      }
    },
    // 预自定义日志类型:log4js.getLoggeer([category])获取该类型的日志实例
    categories: {
      // 必须定义一个默认类型,当没有匹配的类型时,一律按默认类型处理
      default: {

      },
      errorLog: {
        appenders: ['error'], // 指定日志输出类型
        // 指定可用的最小日志等级
        level: 'error', //可以使用OFF > MARK > FATAL > ERROR等级,不可以使用WARN > INFO > DEBUG > TRACE > ALL等级
        enableCallStack: true // 是否打印所属文件名 & 行号
      }
    }
  }
}

服务端配置

代码部分,注意转义

// 新建文件:/server/config/log.js
const path =  require('path');

const { checkDirExist } =  require('../utils/dir');

  

// 日志根目录

const baseLogDir = path.resolve(\_\_dirname, '../logs');

// 错误日志

const errorDir =  'error';

const errorFileName =  'error';

const errorDirPath = path.resolve(baseLogDir, errorDir);

const errorLogPath = path.resolve(errorDirPath, errorFileName);

// 访问日志

const accessDir =  'access';

const accessFileName =  'access';

const accessDirPath = path.resolve(baseLogDir, accessDir);

const accessLogPath = path.resolve(accessDirPath, accessFileName);

  

// 响应日志

const responseDir =  'response';

const responseFileName =  'response';

const responseDirPath = path.resolve(baseLogDir, responseDir);

const responseLogPath = path.resolve(responseDirPath, responseFileName);

  

\[errorDirPath, accessDirPath, responseDirPath\].forEach(p  \=> {

checkDirExist(p);

})

module.exports = {

// 定义日志输出类型https://github.com/log4js-node/log4js-node/blob/master/docs/appenders.md

appenders: {

console: {

type:  'stdout'

},

error: {

type:  'dateFile', //https://github.com/log4js-node/log4js-node/blob/master/docs/dateFile.md

filename: errorLogPath, // 定义生成文件的路径。

daysToKeep:  7, // 保存7天日志,大于7天的,删除;

pattern:  '.yyyy-MM-dd.log',

alwaysIncludePattern: true, // 只有该属性设置为true,才会以pattern追加重命名filename

// keepFileExt: true //不起作用

},

access: {

type:  'dateFile',

filename: accessLogPath,

daysToKeep:  7, // 保存7天日志,大于7天的,删除;

pattern:  '.yyyy-MM-dd.log',

alwaysIncludePattern: true, // 只有该属性设置为true,才会以pattern追加重命名filename

// keepFileExt: true //不起作用

},

response: {

type:  'dateFile',

filename: responseLogPath,

daysToKeep:  7, // 保存7天日志,大于7天的,删除;

pattern:  '\-yyyy-MM-dd.log',

alwaysIncludePattern: true, // 只有该属性设置为true,才会以pattern追加重命名filename

// keepFileExt: true //不起作用

}

},

// 定义Logger对象类型,用于log4js.getLogger(\[category\])

categories: {

default: {

appenders: \['console'\], level:  'all'

},

errorLogger: {

appenders: \['error'\], level:  'error'

},

accessLogger: {

appenders: \['access'\], level:  'info'

},

responseLogger: {

appenders: \['response'\], level:  'info'

}

}

};

服务端日志工具库

代码部分,注意转义

// 新建文件:/server/utils/log.js
const log4js =  require('koa-log4');

const config =  require('../config/log');

  

//以规范对象开启日志功能

log4js.configure(config);

// 获取日志对象实例

const errorLogger = log4js.getLogger('errorLogger');

const accessLogger = log4js.getLogger('accessLogger');

const responseLogger = log4js.getLogger('responseLogger');

const consoleLogger = log4js.getLogger();

  

const logUtil = {};

// 封装错误日志

logUtil.logError  \=  function  (ctx, error, resTime) {

if (ctx && error) {

errorLogger.error(formatError(ctx, error, resTime));

}

};

  

// 封装请求日志

logUtil.logAccess  \=  function  (ctx, resTime) {

if (ctx) {

accessLogger.info(formatAccessLog(ctx, resTime));

}

};

// 封装响应日志

logUtil.logResponse  \=  function  (ctx, resTime) {

if (ctx) {

responseLogger.info(formatRes(ctx, resTime));

}

};

  

logUtil.logInfo  \=  function  (info) {

if (info) {

consoleLogger.info(formatInfo(info));

}

};

  

const  formatInfo  \=  function  (info) {

let logText =  '';

// 响应日志开始

logText +=  '\\n' +  '\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* console log start \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*' +  '\\n';

  

// 响应内容

logText +=  'console detail: ' +  '\\n' +  JSON.stringify(info) +  '\\n';

  

// 响应日志结束

logText +=  '\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* console log end \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*' +  '\\n';

  

return logText;

};

  

// 格式化响应日志

const  formatRes  \=  function  (ctx, resTime) {

let logText =  '';

// 响应日志开始

logText +=  '\\n' +  '\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* response log start \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*' +  '\\n';

  

// 添加请求日志

logText +=  formatAccessLog(ctx, resTime);

  

// 响应状态码

logText +=  '\\n' +  'response status: ' + ctx.status +  '\\n';

  

// 响应内容

logText +=  'response body: ' +  '\\n' +  JSON.stringify(ctx.body) +  '\\n';

  

// 响应日志结束

logText +=  '\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* response log end \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*' +  '\\n';

  

return logText;

};

  

// 格式化错误日志

const  formatError  \=  function  (ctx, err, resTime) {

let logText =  '';

  

// 错误信息开始

logText +=  '\\n' +  '\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* error log start \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*' +  '\\n';

  

// 添加请求日志

logText +=  formatAccessLog(ctx, resTime);

  

// 错误名称

logText +=  '\\n' +  'err name: ' + err.name +  '\\n';

// 错误信息

logText +=  'err message: ' + err.message +  '\\n';

// 错误详情

logText +=  'err stack: ' + err.stack +  '\\n';

  

// 错误信息结束

logText +=  '\*\*\*\*\*\*\*\*\*\*\*\*\*\*\* error log end \*\*\*\*\*\*\*\*\*\*\*\*\*\*\*' +  '\\n';

  

return logText;

};

  

// 格式化请求日志

const  formatAccessLog  \=  function  (ctx, resTime) {

const { method, originalUrl, ip, query, params, body } = ctx.request;

let logText =  '';

// 客户端ip

logText +=  'request client ip: ' + ip +  '\\n';

// 客户端

logText +=  'request userAgent: ' + ctx.header\['user-agent'\] +  '\\n';

// 访问协议

logText +=  'request protocol: ' + ctx.protocol +  '\\n';

// 访问方法

logText +=  'request method: ' + method +  '\\n';

// 请求原始地址

logText +=  'request originalUrl: ' + originalUrl +  '\\n';

// 请求参数

logText += params  ?  'request params: ' +  JSON.stringify(params) +  '\\n'  :  '';

logText += query  ?  'request query: ' +  JSON.stringify(query) +  '\\n'  :  '';

logText += body  ?  'request body: ' +  JSON.stringify(body) +  '\\n'  :  '';

// 服务器响应时间

logText +=  'response time: ' + resTime +  '\\n';

  

return logText;

};

  

module.exports = logUtil;

服务端使用日志服务

// 更新文件:在需要使用的地方或统一拦截的地方修改
// 如:/server/app.js
...
const logUtil =  require('./utils/log');
...
// 错误处理
app.use(function(ctx, next){
  return  next().catch((err)  => {
  logUtil.logError(ctx, err)
...

测试结果

image.png

安全系统

安全系统很简单,直接在需要进行xss攻击处理的地方,进行一下改进即可

var xss = require("xss");
var html = xss('<script>alert("xss");</script>');

xss包会根据预先定义好的规则,转义标签,过滤调具有攻击性的属性。

参考文档

log4js-node
xss


米花儿团儿
1.3k 声望75 粉丝

引用和评论

0 条评论