Clean Code, Clean Log

最近提的 PR 都有关于 Log 的 comment,不能忍,以下内容总结整理自明佳的 Comment 和网络资料,只是为了以后提 PR 之前过来扫一眼,尽量避免 Log 上的疏忽,不一定适用于所有人。

在程序中的适当位置打 Log 的重要性就不用多说了,很多人应该都体会过线上有 Bug 却由于没有打 log 而不好 troubleshooting 的经历。
T^T

相关文档(康桑哈密达)

SLF4J VS Log4J

有很多关于打 Log 的第三方库,也没有多研究过,接触过的就是 SLF4JLog4J 了,不过墙裂建议用 SLF4J,使用占位符 {} 真的比加号拼接字符串可读性提高N倍啊!!!

感受一下<( ̄︶ ̄)>

1
2
3
4
// SLF4J, good
log.error("Invest loan failed, loan Id: {}, error: {}", loanId, errorMsg);
// Log4J, bad
log.error("Invest loan failed, loan Id:" + loanId + " error: " + errorMsg);

当然,SLF4J 还有其他的优点,比如不用字符串拼接,节省了创建 String 对象所耗费的资源之类的。不过我最看重的就是可读性高了。
喵喵呜

Logging Level

  • ERROR - 记录一些比较严重的错误,比如一些严重异常,数据库链接不可用等等
  • WARN - 记录一些系统可以容忍的异常,或者是一些警示信息。比如:”Current data unavailable, using cached values”。
  • INFO - 记录一些比较重要的操作,能反映程序运行状态的。比如:”[Who] booked ticket from [Where] to [Where]”
  • DEBUG - 一些帮助调试的信息
  • TRACE - 嗯,这个级别俺也没用过。

Pay attention

  • Log 信息首字母大写
    这点完全是为了看上去舒服,至于到底需不需要大写,见仁见智吧~,不过我还是要注意一下,要大写。

    1
    2
    3
    4
    // good
    log.error("Invest loan failed, loan Id: {}, error: {}", loanId, errorMsg);
    // bad
    log.error("invest loan failed, loan Id: {}, error: {}", loanId, errorMsg);
  • 避免 Log 中的 NullPointerException
    如果像下面这样记 Log,要注意确保 loan 不会为null, 不然打 Log 时抛个 NPE,想想就蛋疼。

    1
    log.info("Invest loan : {}", loan.getId());
  • Log 的信息简洁有用
    Log 的内容一定要是有用的,能反映出程序的运行状态,能帮助定位错误。

    1
    2
    3
    4
    // good
    log.info("Invest loan with id:{}", loanId);
    // bad
    log.info("Invest loan");
  • 记录某些方法的入参和出参
    记录方法的入参和出参,也可以帮助我们定位问题。特别是调用提供接口给其他系统调用的时候,记录入参可以帮助分辨到底是谁的锅🌚。

    1
    2
    3
    4
    5
    6
    public String printDocument(Document doc, Mode mode) {
    log.debug("Entering printDocument(doc={}, mode={})", doc, mode);
    String id = //Lengthy printing operation
    log.debug("Leaving printDocument(): {}", id);
    return id;
    }
  • 合适的记录异常
    大家都知道要在记录程序运行中抛出的异常,但有的时候方式可能是不对的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    try{
    throw new NullPointerException("Just for test");
    } catch (Exception e){
    log.error(e); //A
    log.error(e, e); //B
    log.error("" + e); //C
    log.error(e.toString()); //D
    log.error(e.getMessage()); //E
    log.error(null, e); //F
    log.error("", e); //G
    log.error("{}", e); //H
    log.error("{}", e.getMessage()); //I
    log.error("Error reading configuration file: " + e); //J
    log.error("Error reading configuration file: " + e.getMessage()); //K
    log.error("Error reading configuration file", e); //L
    }

    在上面 12 种打印异常的方式中,只有 G 和 L是正确的。A 和 B 在使用 SLF4J 时会编译不通过, 其他的几种要么不会打印异常堆栈,要么会打印出不正确的信息。比如,E 的方式只会打印”Just for test”的信息,而不会打印异常类型和异常堆栈。在 SLF4J 中,第一个参数是文本信息,简单描述一下异常;第二个参数要传异常本身,而不是e.getMessage()e.toString(),这样才能打印出异常堆栈,方便定位问题。

希望可以消灭和 Log 有关的 Comment。
biu biu biu~

欢迎指正错误,欢迎一起讨论~(≧▽≦)/~。

分享到 评论