日志

日志规范

文件命名:appName_logType_logName.log

日志文件的保存时间衡量因素:

  1. 重要程度
  2. 文件大小
  3. 磁盘空间

日志级别

日志类别

日志实现

使用类似 Canal 的中间件监听数据变化 写日志

日志文件

LoginUtil.setCurrentUser("cxk")

配置不同的logger

private final Logger businessLog = LoggerFactory.getLogger("businessLog");
template = "用户 %s 做了 %s";
log.error(String.format(template, "cxk", "打篮球"))

AOP方法注解

// bizNo 业务唯一ID 方便进行日志搜索
// 通过使用自定义函数配合SpEL来分离业务逻辑与日志记录
@LogRecord(content = "修改了订单的配送员:从“{queryOldUser{#request.deliveryOrderNo()}}”, 修改到“{deveryUser{#request.userId}}”",
        bizNo="#request.deliveryOrderNo")
public void modifyAddress(updateDeliveryRequest request){
    // 更新派送信息 电话,收件人、地址
    doUpdate(request);
}

AOP日志设计

日志使用

预先判断日志级别

避免使用字符串的形式连接打印日志

log.debug("user " + id + "create new order " + orderId) // bad

if (log.enableDebug){ // good
  log.debug(...)
}
log.debug('user {} create new order {}', id, orderId) // good

主要是防止无谓的字符串连接消耗系统资源

避免无效日志打印

对于debug log 等低级别的日志 一定要控制好输出量 避免磁盘空间被快速吞掉,

因为 String 字符串拼接会使用 StringBuilder 的 append () 方式,有一定的性能损耗。使用占位符可以有效提高性能

打印大文本日志非常影响性能,很多大文本对排查问题帮助不大,打印该信息的意义不大,因此尽量避免打印该内容或只截取一部分关键信息

区别对待错误日志

不能将所有错误一股脑归类为ERROR级别, ERROR 日志专门输出到一个 error.log 文件。调试时通过 tail -f error.log 随时监控出现的错误日志

ERROR级别就代表是需要人工介入处理的级别

日志记录的内容

  1. 一定要输出异常堆栈
  2. 输出对象实例时 要确保对象重写了 toString 方法

日志的追踪

对于没有链路追踪基础设施的单体应用,为了在大量的日志中找到自己所需的日志。就需要能以某种情况准确唯一标识日志,如

log.info("业务上下文-事件xxx xxxx")

同时为了追踪执行流或者数据流,通常都需要在某条日志之前或之后追踪日志,此时可以使用日志纪录里的线程名称来明确整条执行链路,如下面的bussiness-task1/bussiness-task2...

2022年1月21日20:30:21 INFO --- [-] [main] wang.ismy.xxx 日志内容1
2022年1月21日20:30:21 INFO --- [-] [bussiness-task1] wang.ismy.xxx 日志内容2
2022年1月21日20:30:21 INFO --- [-] [bussiness-task2] wang.ismy.xxx 日志内容3
...

日志的性能

单体在流量比较高的情况下,没有做好日志级别的控制,可能会狂打日志,这些大量的日志若没有使用专门的日志收集工具收集,大量打印不仅会消耗性能,同时大量的日志也会快速消耗磁盘空间,所以在做好日志级别控制的同时,也可以采取随机打印日志的方式

if (random(0, 1000) == 3) {
  log.info("xx")
}

使用这种方式的目的在于观察代码路径是否覆盖,并且前提一定得是流量特别高的场景,否则出现问题就没有日志可进行排查

错误的日志形式

日志框架

日志框架