Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

【日志】koa-log4

安装配置

  • 安装

    npm i -S koa-log4
    
  • 根目录下新建文件夹logs

    mkdir logs
    
  • 根目录下新建配置文件log4js-conf.json

    {
        "appenders": {
            "log": {
                "type": "dateFile",
                "layout": { "type": "colored" },
                "filename": "logs/app.log",
                "pattern": "yyyy-MM-dd",
                "compress": false,
                "daysToKeep": 30
            },
            "body_log": {
                "type": "dateFile",
                "layout": { "type": "colored" },
                "filename": "logs/body_app.log",
                "pattern": "yyyy-MM-dd",
                "compress": false,
                "daysToKeep": 30
            },
            "access_log": {
                "type": "dateFile",
                "layout": { "type": "colored" },
                "filename": "logs/access_app.log",
                "pattern": "yyyy-MM-dd",
                "compress": false,
                "daysToKeep": 30
            },
            "out": {
                "type": "stdout"
            }
        },
        "categories": {
            "default": {
                "appenders": ["out"],
                "level": "trace"
            },
            "log": {
                "appenders": ["log", "out"],
                "level": "trace"
            },
            "body_log": {
                "appenders": ["body_log"],
                "level": "trace"
            },
            "access_log": {
                "appenders": ["access_log", "out"],
                "level": "info"
            }
        }
    }
    
    • log用于记录每个路由helperDB的执行
    • body_log用于记录post请求体,单独存储,不输出到终端
    • access_log用于记录全局的URL访问情况和响应时间
  • 如果想要自定义输出内容,可以修改layout选项,用pattern类型指定

    "layout": { "type": "pattern", "pattern": "[%d{ISO8601}] [%p] %c - %m%n" },
    
    • 但这样又会丢掉颜色提示...不好用
    • 这种专用于下一节给logstash输送时定制数据格式,并用grok按相应格式解析后索引

使用方法

  • 安装ip模块

    npm i -S ip
    
  • lib/utils下新建api_log.js文件,内容如下

    import ip from "ip";
    import log4js from "koa-log4";
    log4js.configure('log4js-conf.json');
    
    export class Log {
        constructor(name) {
            this.name = name;
            this.log = log4js.getLogger('log');
        }
    
        format(explaination, user_uuid, obj) {
            let str = `==[${this.name}]${explaination}==[${user_uuid ? user_uuid : ''}]`;
            if (obj) {
                Object.keys(obj).forEach((key) => {
                    str += `[${key}]${obj[key]}`;
                })
            }
            return str;
        }
    
        debug(explaination, user_uuid, obj) {
            if (typeof explaination !== 'string') return;
            this.log.debug(this.format(explaination, user_uuid, obj));
        }
    
        info(explaination, user_uuid, obj) {
            if (typeof explaination !== 'string') return;
            this.log.info(this.format(explaination, user_uuid, obj));
        }
    
        warn(explaination, user_uuid, obj) {
            if (typeof explaination !== 'string') return;
            this.log.warn(this.format(explaination, user_uuid, obj));
        }
    
        error(explaination, user_uuid, obj) {
            if (typeof explaination !== 'string') return;
            this.log.error(this.format(explaination, user_uuid, obj));
        }
    }
    
    const formatAccess = (ctx, costTime) => {
        const user = ctx.api_user ? ctx.api_user.user_uuid : "anonymous_user";
        return `${ctx.method} ${ctx.url} ${ctx.response.status} ${costTime}ms ${user} ${ip.address()}`;
    }
    
    export const accessLogger = (ctx, costTime) => log4js.getLogger('access_log').info(formatAccess(ctx, costTime))
    
    • 这里增加了ip的记录
    • 这里对于formatAccess不应该添加返回post的相关body记录,不然用户请求体容易被后面logstash的tcp传输泄密
  • 在全局注册accessLoggeruse代码

    import { log, accessLogger } from "../utils/api_log.js";
    
    log.debug("== set accessLogger");
    app.use(async (ctx, next) => {
        const start = new Date();
        await next();
        const ms = new Date() - start;
        accessLogger(ctx, ms);
    });
    
    //中间的其他代码
    
    app.on('error', (err) => {
        log.error(err);
    })
    
    • 这里监听app的error事件,专门捕获记录那些没有被程序处理而错误,便于及时发现并修复bug
  • 在router中使用log

    import { Log } from "../../../utils/api_log.js";
    const log = new Log("index");
    
    log.info("get index", null, { traceId: ctx.traceId });
    
  • 在 db 中使用log

    import { Log } from "../utils/api_log.js";
    const log = new Log("db_user");
    
    log.info("getUserByMobile");
    
  • 在代码中人为抛出错误(但这样貌似就不会被accessLogger记录?)

    ctx.throw()
    
    • 默认抛出500,返回客户端显示Internal Server Error
    ctx.throw(404)
    
    • 可指定返回404,返回客户端显示Not Found
    ctx.throw(500, "网页暂时无法访问")
    
    • 可指定500并写提示信息,会返回给客户端显示

pm2集群开启会使日志记录出现错误!!!

  • 问题产生的原因

    • Log4js 在 Cluster 模式下,worker 将日志发送至 master,master 实现日志写入文件
    • 但在 PM2 Cluster 模式下,所有进程皆为 worker
  • 官方提供的解决方案

    • 运行命令安装 pm2 install pm2-intercom

      • 安装会卡死在idealTree:pm2-intercom: sill idealTree buildDeps
      • 办法: 设置代理export https_proxy=http://127.0.0.1:8889 http_proxy=http://127.0.0.1:8889 all_proxy=socks5://127.0.0.1:1080
      • 安装完成后,使用pm2 ls会列出Module表格,要确保里面有online状态pm2-intercom
        • 否则就要卸载,然后重启,重新安装
    • 找到配置文件,名称如下

      log4js.configure('log4js-conf.json');
      
    • 需要在log4js-conf.json文件中添加

      "pm2": true,
      "pm2InstanceVar": "INSTANCE_ID",
      
      • appenders同级
      • 用于选举出一个负责写文件的主进程
    • 原理

      • 各自进程的日志首先发送到 pm2-intercom
      • 由 pm2-intercom 分发到全部进程
      • 但只有 log4js isMaster 才会写文件,主进程压力也会大
  • 另一种方案: (不推荐)

    • 设置log4js的disableClustering 选项为 true
      • 这样所有进程都将作为 master 进而拥有写文件的权限
      • 缺点:每个进程写自己的日志,日志分散不好查阅
  • 也可以考虑直接放弃pm2(好像不行)

    • 自己用 node 原生的 cluster模块实现“压榨CPU多核”
      import { cpus } from "os";
      import cluster from "cluster";
      import Koa from 'koa';
      const app = new Koa();
      if(cluster.isPrimary){
          for (let i = 0; i < cpus().length; i++) cluster.fork();
          console.log('master', process.pid);
      }else {
          app.listen(3000);
          console.log('worker', process.pid);
      }
      
      • node16抛弃了isMaster,换成isPrimary,造成[ERROR] log4js - Unable to parse log:错误