Java基础学习总结(146)——开发人员日志实践规范

一、日志规范

日志作用:

1、日常排查问题(基本作用),快速定位问题的根源

2、分析日志,构建常见问题排查平台

3、报表输出(日活、周活)

4、追踪程序执行的过程,追踪数据的变化: 

    a)在系统启动或初始化时记录重要的系统初始化参数;

    b)记录系统运行过程中的所有的错误;

    c)记录系统运行过程中的所有的警告; 

    d)在持久化数据修改时记录修改前和修改后的值;

    e)记录系统各主要模块之间的请求和响应;

    f)记录用户操作的审计日志,甚至有的时候就是监管部门的要求。

5、 数据统计和性能分析 ,采集运行环境数据等,如提取有用数据,数据变现(比如:用户注册信息,手机号码,实名认证信息,LBS信息,用户画像信息)。

扫描二维码关注公众号,回复: 6793822 查看本文章

规范建议:

1、使用门面模式的日志框架SLF4J中的API

// SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

2、 对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。

    /**
     * 对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。说明:logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象,会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印
     */
    private void logFoo5(){
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Processing trade with id={}, symbol={}" + id + " and symbol: " + symbol); // 错误
            LOGGER.debug("Processing trade with id: {} and symbol : {} ", id, symbol); // 正确
        }
    }

3、 异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。正例:logger.error(各类参数或者对象 toString + "_" + e.getMessage(), e);

4、 谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。

说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?

5、 可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。注意日志输出的级别,error 级别只记录系统逻辑出错、异常等重要的错误信息。如非必要,请不要在此场景打出 error 级别。

6、 日志内容:尽量使用业务相关的描述。我们的程序是实现某种业务的,那么就最好能描述清楚这个时候走到了业务过程的哪一步。其次,避免在日志中输出一些敏感信息,例如用户名和密码。

7、 日志格式:对于每一条日志应含有的信息包括日期、时间、日志级别、代码位置、日志内容、错误码等信息。

8、日志中不出现计算或方法调用,防止在打印日志的时候报错。如LOGGER.info("i={}", 2/0);会报错(java.lang.ArithmeticException: / by zero)。

9、输出Exceptions的全部Throwable信息,因为logger.error(msg)和logger.error(msg,e.getMessage())这样的日志输出方法会丢失掉最重要的StackTrace信息。

    /**
     * 输出Exceptions的全部Throwable信息,因为logger.error(msg)和logger.error(msg,e.getMessage())
     * 这样的日志输出方法会丢失掉最重要的StackTrace信息。
     */
    private void logFoo1(){
        try {
            // do ...
        } catch (Exception e) {
            LOGGER.error("错误信息,e.getMessage() ==> {}", e.getMessage()); // 错误
            LOGGER.error("错误信息 ==> ", e); // 正确
        }
    }

10、不允许记录日志后又抛出异常,因为这样会多次记录日志,只允许记录一次日志

    /**
     *  不允许记录日志后又抛出异常,因为这样会多次记录日志,只允许记录一次日志
     */
    private void logFoo2() throws LogException{
        try {
            // do ...
        } catch (Exception e) {
            LOGGER.error("错误信息 ==> ", e);
            throw new LogException("错误信息 ==> ", e);
        }
    }

11、 不允许出现System print(包括System.out.println和System.error.println)

    /**
     * 不允许出现System print(包括System.out.println和System.error.println)语句
     */
    private void logFoo3(){
        try {
            // do ...
        } catch (Exception e) {
            LOGGER.error("错误信息 ==> ", e); // 正确
            System.out.println(e.getMessage()); // 错误
            System.err.println(e.getMessage()); // 错误
        }
    }

12、 不允许出现printStackTrace

    /**
     * 不允许出现printStackTrace
     */
    private void logFoo4(){
        try {
            // do ...
        } catch (Exception e) {
            LOGGER.error("错误信息 ==> ", e); // 正确
            e.printStackTrace(); // 错误
        }
    }

13、【推荐】尽量用英文来描述日志错误信息,如果日志中的错误信息用英文描述不清楚的话使用 中文描述即可,否则容易产生歧义。国际化团队或海外部署的服务器由于字符集问题,【强制】 使用全英文来注释和描述日志错误信息。

14、不打无用的、无意义、不完全的日志。如下反例:

if(message instanceof TextMessage){  
      //do sth
}else{     
    log.warn("Unknown message type");
}

15、一般不在循环里打印日志。

16日志打印的时候,假如需要处理列表、数组类的数据,最好是只输出对象的大小,或者某些关键字段,例如:编号(Commons Beanutils),如果将全部内容打印出来可能会占用大量的资源。

说明:日志内容应该打印哪些关键信息?

     a) 入参和关键响应结果:Request和Response

     b) 系统操作行为:读写文件、定时任务等

     c) 不符合业务逻辑预期:打印关键的参数,要能从这些参数中清楚地看出,谁的操作与预期不符,为什么与预期不符。并且唯一定位到这条日志,要包含用户id或者流水号,进件编号,订单号等。

    d) 对外提供的接口入口处:打印接口的唯一标识和简短描述,并且要将调用方传入的参数原样打印出来,这样当系统出现问题时,就能很容易的判断出是否是调用方出现了问题。

    e) 调用其它系统接口的前后:打印所调用接口的系统名称/接口名称和传入参数/响应参数,这样能方便做问题定界,通过这两条日志可以清楚地看出是否是所调用的系统出现了问题。

    f) 系统模块的入口与出口处:可以是重要方法级或模块级,记录它的输入与输出,方便定位。

    g) 非预期执行:为程序在“有可能”执行到的地方打印日志 switch case语句块中的default if…else if…else中很少出现的else情况 try catch语句块中catch分支。

   h) 服务状态变化(尽可能记录线索):程序中重要的状态信息的变化应该记录下来,方便查问题时还原现场推断程序运行过程。

   i) 一些可能很耗时的业务处理:批处理,IO操作。

   j) 程序运行耗时:通过它可以跟踪为什么系统响应变慢或者太快。

   k) 大批量数据的执行进度 。

日志归档

1、日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。

2、应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log。logType:日志类型,推荐分类有 stats/desc/monitor/visit 等;logName:日志描述。

这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。正例:mppserver 应用中单独监控时区转换异常,如: mppserver_monitor_timeZoneConvert.log

说明:推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。

3、采用dev(开发环境)、test(测试环境)、production(生产环境)等不同的日志配置,根据环境变量自动识别.

二、日志级别选择

日志级别分类

FATAL(致命):严重的,造成服务中断的错误。fatal为系统级别的异常,发生fatal错误,代表服务器整个或者核心功能已经无法工作了!!

1)在服务器启动时就应该检查,如果存在致命错误,直接抛异常,让服务器不要启动起来(启动了也无法正常工作,不如不启动)。

2)如果在服务器启动之后,发生了致命的错误,则记录fatal级别的错误日志,最好是同时触发相关的修复和告警工作(比如,给开发和维护人员发送告警邮件)。比如数据库服务连不上了。

ERROR(错误):运行期错误,代表功能或者重要逻辑遇到问题、无法正常工作

WAR(警告):记录可能会出现潜在错误的信息,但还没有大的影响,或者重要数据被修改,或者某些操作需要引起重视。

INFO(信息): 记录程序正常运行有意义的信息,如初始化步骤和重要操作的时候;

DEBUG(调试):调记录调试程序相关的信息,方便查看程序的执行过程和相关数据、了解程序的动态;

Trace(跟踪):更详细的跟踪信息;无需每次调试时都打印出来,只在需要更详细的调试信息时才开启。

级别的选择

开发环境

1)默认日志级别定义为:app包为TRACE级别。日志的ROOT Level为DEBUG级别。

2)启用 System.out 控制台输出日志;

启用error.log为错误和警告日志、app.log为应用日志(包括app包下的日志和其他INFO级别以上的日志)。

测试环境

1)默认日志级别定义为:app包为DEBUG级别。日志的ROOT Level为DEBUG级别。

2)禁用 System.out 控制台输出日志;

启用error.log为错误和警告日志、app.log为应用日志(包括app包下的日志和其他INFO级别以上的日志)。

生产环境

1)默认日志级别定义为:app包为DEBUG级别。日志的ROOT Level为INFO级别。

2)禁用 System.out 控制台输出日志;

启用error.log为错误和警告日志、app.log为应用日志(包括app包下的日志和其他INFO级别以上的日志)。

总结:WARN和ERROR级别如何选择?

1、ERROR级别选择:

RROR级别的错误需要马上被处理,当ERROR错误发生时,已经影响了用户的正常访问,是需要马上得到管理员介入并处理的。常见的ERROR异常有:空指针异常,数据库不可用,关键路径的用例无法继续执行等。如下列情形应该使用ERROR基本记录日志:

         a) 读写配置文件失败;

         b) 网络断线;

         c) 所有第三方对接的异常(包括第三方返回错误码);

         d) 所有影响功能使用的异常等。

 2、WARN级别选择:

虽然不需要管理员马上处理,但是也需要引起重视。WARN日志有两种级别:一个是解决方案存在明显的问题(例如Try Catch语句中由于不确定会出现什么异常,而用Exception统一捕获抛出的问题),另一个是潜在的问题和建议(例如系统性能可能会伴随着时间的迁移逐渐不能满足服务需要)。应用程序可以容忍这些信息,不过它们应该被及时地检查及修复。某些用户危险的操作,例如一直采用错误密码尝试登陆管理员账号的行为, 也可以提升到WARN日志级别记录。如下列情形应该使用WARN级别记录日志:

        a)由于在程序运行之前不能明确异常引发的原因,异常只进行了简单的捕获抛出,需要将这种笼统处理的异常打印为WARN格式的日志,提醒管理员干预处理;

         b)有容错机制的时候出现的错误情况;

         c) 找不到配置文件,但是系统能自动创建配置文件;

         d) 性能即将接近临界值的时候;

         e) 业务异常的记录,危险操作。

三、Linux查看日志

使用less、more、grep、awk和wc等Linux命令

参考:https://blog.csdn.net/waeceo/article/details/53258574

四、日志系统ELK使用

通常,日志被分散在储存不同的设备上。如果你管理数十上百台服务器,你还在使用依次登录每台机器的传统方法查阅日志。这样是不是感觉很繁琐和效率低下。当务之急我们使用集中化的日志管理,例如:开源的syslog,将所有服务器上的日志收集汇总。集中化管理日志后,日志的统计和检索又成为一件比较麻烦的事情,一般我们使用grep、awk和wc等Linux命令能实现检索和统计,但是对于要求更高的查询、排序和统计等要求和庞大的机器数量依然使用这样的方法难免有点力不从心。通过我们需要对日志进行集中化管理,将所有机器上的日志信息收集、汇总到一起。完整的日志数据具有非常重要的作用:

    1)信息查找。通过检索日志信息,定位相应的bug,找出解决方案。

    2)服务诊断。通过对日志信息进行统计、分析,了解服务器的负荷和服务运行状态,找出耗时请求进行优化等等。

    3)数据分析。如果是格式化的log,可以做进一步的数据分析,统计、聚合出有意义的信息,比如根据请求中的商品id,找出TOP10用户感兴趣商品。

 ElasticSearch是一个实时的分布式搜索和分析引擎,它可以用于全文搜索,结构化搜索以及分析。这是一个建立在全文搜索引擎 Apache Lucene基础上的搜索引擎。

Logstash是一个具有实时渠道能力的数据收集引擎,主要用于日志的收集,过滤与解析,并将其存入ElasticSearch中。

Kibana是一款 基于Apache开源协议,为ElasticSearch提供分析和可视化的 Web平台,它可以在Elasticsearch的索引中查找,交互数据,并生成各种维度的表图。

ELK提供了一整套解决方案,并且都是开源软件,之间互相配合使用,完美衔接,高 效的满足了很多场合的应用。目前主流的一种日志系统。

参考:https://www.cnblogs.com/wangzhuxing/p/9693707.html

猜你喜欢

转载自blog.csdn.net/u012562943/article/details/92638905