• 谈谈日志规范


    一、背景

    编码过程中日志的重要性不言而喻,开发时通过打日志可以帮助调试代码,生产环境可以通过日志记录异常信息,排查问题等。一个好的日志规范同样重要,随意的打日志可能造成一些重要的异常堆栈信息丢失,甚至会占用大量的磁盘IO造成程序性能问题。

    二、日志技术选型

    建议统一使用slf4j
    因为它是使用门面模式的日志框架,便于我们后期随时切换日志实现。避免在代码中直接使用log4j或java logging 等实现类。
    实现方式统一使用Logback框架,具体原因可以参考这篇文章《logback最佳实践》。

    对象声明
    建议使用private static final。声明为private可防止logger对象被其他类非法使用。声明为static可防止重复new出logger对象,还可以防止logger被序列化,造成安全风险。声明为final是因为在类的生命周期内无需变更logger。

    private static final Logger logger = LoggerFactory.getLogger(MailService.class);
    

    如果觉得麻烦的话,也可以直接在类上打上lombok的 @Slf4j注解,它会帮我们自动生成代码。

    三、日志级别的选择

    ERROR
    影响到程序正常运行、当前请求正常运行的异常情况:

    1. 打开配置文件失败
    2. 所有第三方对接的异常(包括第三方返回错误码)
    3. 所有影响功能使用的异常,包括:SQLException和除了业务异常之外的所有异常(RuntimeException和Exception)

    WARN
    不应该出现但是不影响程序、当前请求正常运行的异常情况:

    1. 有容错机制的时候出现的错误情况
    2. 找不到配置文件,但是系统能自动创建配置文件
    3. 即将接近临界值的时候,比如缓存池占用达到警告线
    4. 业务异常的记录

    INFO
    记录系统关键信息:

    1. Service方法中对于系统/业务状态的变更
    2. 主要逻辑中的分步骤,if/else等
    3. 客户端请求参数(REST/WS)
    4. 调用第三方接口时的调用参数和调用结果

    DEBUG
    可以将各类详细信息记录到DEBUG里,起到调试的作用,包括参数信息,调试细节信息,返回值信息等。
    注意:生产环境需要关闭DEBUG信息

    TRACE
    更详细的跟踪信息,这个基本用不到。

    述日志级别从高到低排列,是开发中最常用的五种。生产系统一般只打印INFO 级别以上的日志,对于 DEBUG 级别的日志,只在测试环境中打印。打印错误日志时,需要区分是业务异常(如:用户名不能为空)还是系统异常(如:调用 会员核心异常),业务异常使用 warn 级别记录,系统异常使用 error 记录

    四、正确姿势

    4.1 语言

    最好在打印日志时输出英文,防止中文不支持而打印出乱码的情况。

    4.2 必须使用参数化信息的方式

    logger.debug("Processing trade with id:[{}] and symbol : [{}] ", id, symbol);
    

    4.3 对于debug日志,必须判断是否为debug级别后,才进行使用。

    如果不加判断,即使当前日志级别是INFO,字符串拼接操作依然会进行。

    if (logger.isDebugEnabled()) {
       logger.debug("Processing trade with id: " +id + " symbol: " + symbol);
    }
    

    如果打印的实参不含计算,则没必要加这个判断,具体可参考《关于打印debug日志是否加判断日志级别的分析》。

    4.4 禁止使用字符串拼接

    使用字符串拼接会产生很多String对象,占用空间,影响性能,而且使用字符串拼接的可读性和可维护性都比较差,建议使用占位符。

    // 错误
    logger.debug("Processing trade with id: " + id + " symbol: " + symbol);
    
    // 正确
    logger.debug("Processing trade with id:[{}] and symbol : [{}] ", id, symbol);
    

    4.5 建议使用[]进行参数变量隔离

    这样的格式写法,可读性更好,对于排查问题更有帮助。

    4.6 循环体内不要打印 INFO 级别日志

        for (Person person : personList){
                log.info("the person is [{}]", JSON.toJSONString(person));
            }
    

    4.7 if..else判断

    对于else 是非正常的情况,需要根据情况选择打印warn 或 error 日志。对于只有 if 没有 else 的地方,如果 else 的路径是不可能的,应当加上 else 语句,并打印 error 日志。

        if (true){
                // do something
            }else {
                log.error("It is impossible...");
            }
    

    同时对于if…else 或者 switch这样的分支,要在分支的首行打印日志,用来确定进入了哪个分支。

    4.8 不打印无意义日志

    不记录对于排查故障毫无意义的日志信息,日志信息一定要带有业务信息。

    //错误
    log.error("Consume message failed!");
    //正确
    log.error("Consume message failed,msgId={}",id);
    

    4.9 打印日志的代码任何情况下都不允许失败

    一定要确保不会因为Log语句的问题而抛出异常造成中断。如下,如果request为null,就会抛空指针异常。

    log.error("execute failed,id:[{}]",request.getId());
    

    4.10 远程调用接口建议打印日志

    比如使用Fegin调用库存服务的接口,需要记录方法入参和返回值,对于排查问题会有很大帮助。

    log.info("prepare to batch query rfid,businessId:[{}],productIds:[{}]",businessId,productIds);
    
    List<InvProductRfid> rfids = invProductClient.batchQueryRfidByProductId(businessId, productIds);
    log.info("batch query rfid result:[{}]", JSON.toJSONString(rfids));
    

    4.11 异常的处理

    catch中的异常记录必须打印堆栈信息,不要用e.printStackTrace()。

    try {
                // do something
            }catch (Exception e){
                // 正确
                log.error("something wrong",e);
                // 错误,丢失了堆栈信息
                log.error("something wrong,errorMsg:[{}]",e.getMessage());
                // 错误
                e.printStackTrace();
            }
    

    如果进行了抛出异常操作,请不要记录error日志,由最终处理方进行处理。

    反例(不要这么做):

    try{
        ....
    }catch(Exception ex){
      String errorMessage=String.format("Error while reading information of user [%s]",userName);
      logger.error(errorMessage,ex);
      throw new UserServiceException(errorMessage,ex);
    }
    

    4.12参数校验错误打印WARN日志

  • 相关阅读:
    关于字节对齐以及内存占用
    关于HandlerThread的分析
    关于栈和队列的相关操作
    自定义控件(View的绘制流程源码解析)
    关于采用github.io搭建个人博客
    算法题解
    关于Android中ArrayMap/SparseArray比HashMap性能好的深入研究
    ADB server didn't ACK * failed to start daemon *
    Handler 、 Looper 、Message
    KMP字符串模式匹配详解(转)
  • 原文地址:https://www.cnblogs.com/2YSP/p/11965461.html
Copyright © 2020-2023  润新知