zoukankan      html  css  js  c++  java
  • 日志实践推荐

    背景


    软件系统生产稳定,依靠着各种高可用、高吞吐、高性能的设计。一旦出现生产问题,常常需要线上定位问题。

    日志则是必备的,问题定位利器。常常出现线上问题,我们都可以通过日志精确定位。同时在开发过程中,由于极长的调用链难以快速定位问题或难以复现时,它是极好的利器。

    日志级别


    一个项目各个log级别的定义应该是清楚明确的,是每个开发人员所遵循的;

    即使是TRACE或者DEBUG级别的日志,也应该有一定的规范,要保证除了开发人员自己以外,包括测试人员和运维人员都可以方便地通过日志定位问题;

    FATAL

    表示需要立即被处理的系统级错误。

    当该错误发生时,表示服务已经出现了某种程度的不可用,系统管理员需要立即介入。

    这属于最严重的日志级别,因此该日志级 别必须慎用,如果这种级别的日志经常出现,则该日志也失去了意义。

    通常情况下,一个进程的生命周期中应该只记录一次FATAL级别的日志,即该进程遇到无 法恢复的错误而退出时。

    当然,如果某个系统的子系统遇到了不可恢复的错误,那该子系统的调用方也可以记入FATAL级别日志,以便通过日志报警提醒系统管 理员修复;

    ERROR

    该级别的错误也需要马上被处理,但是紧急程度要低于FATAL级别。

    当ERROR错误发生时,已经影响了用户的正常访问。从该意义上来说,实际上 ERROR错误和FATAL错误对用户的影响是相当的。

    FATAL相当于服务已经挂了,而ERROR相当于好死不如赖活着,然而活着却无法提供正常的服务,只能不断地打印ERROR日志。

    特别需要注意的是,ERROR和FATAL都属于服务器自己的异常,是需要马上得到人工介入并处理的。

    而对于用户自己 操作不当,如请求参数错误等等,是绝对不应该记为ERROR日志的;

    WARN

    该日志表示系统可能出现问题,也可能没有,这种情况如网络的波动等。

    对于那些目前还不是错误,然而不及时处理也会变为错误的情况,也可以记为WARN日 志,例如一个存储系统的磁盘使用量超过阀值,或者系统中某个用户的存储配额快用完等等。

    对于WARN级别的日志,虽然不需要系统管理员马上处理,也是需要 即使查看并处理的。

    因此此种级别的日志也不应太多,能不打WARN级别的日志,就尽量不要打;

    INFO

    该种日志记录系统的正常运行状态,例如某个子系统的初始化,某个请求的成功执行等等。

    通过查看INFO级别的日志,可以很快地对系统中出现的 WARN,ERROR,FATAL错误进行定位。

    INFO日志不宜过多,通常情况下,INFO级别的日志应该不大于TRACE日志的10%;

    DEBUG or TRACE

    这两种日志具体的规范应该由项目组自己定义,该级别日志的主要作用是对系统每一步的运行状态进行精确的记录。

    通过该种日志,可以查看某一个操作每一步的执 行过程,可以准确定位是何种操作,何种参数,何种顺序导致了某种错误的发生。

    可以保证在不重现错误的情况下,也可以通过DEBUG(或TRACE)级别的 日志对问题进行诊断。

    需要注意的是,DEBUG日志也需要规范日志格式,应该保证除了记录日志的开发人员自己外,其他的如运维,测试人员等也可以通过 DEBUG(或TRACE)日志来定位问题;

    在出错时,日志中包含尽量多的有用上下文信息


    为什么可以这么做?

    日志过多大家伙会说影响性能,但是值得指出的是出错是小概率分支。如果是出错是大概率分支那,打日志以外操作更会成为瓶颈!

    多打日志减少支持时间,比起开发所用的实现时间,线上和线下的排查/支持要花费很多时间;

    别吞噬异常!

    抓住异常没有书写人和响应日志,将导致难以定位。
        @Override
        public UserAmountStat getUserYestodayStat(Long userId, String day) throws GlobalServiceException {
            // TODO Auto-generated method stub
            try {
                UserAmountStat stat = userAmountStatMapper.getUserYestodayStat(userId, day);
                if (stat == null) {
                    stat = new UserAmountStat();
                    stat.setBet(0l);
                    stat.setPrize(0l);
                    stat.setBonus(0l);
                }
                return stat;
            catch (Exception e) {
                // TODO: handle exception
                throw new GlobalServiceException(e);
            }
        }

    此处推荐的需要在日志中记录的内容

    • 在系统启动或初始化时记录重要的系统初始化参数
    • 记录系统运行过程中的所有的错误
    • 记录系统运行过程中的所有的警告
    • 在持久化数据修改时记录修改前和修改后的值
    • 记录系统各主要模块之间的请求和响应
    • 重要的状态变化(如对系统白名单的修改等)
    • 系统中一些长期执行的任务的执行进度
    • 服务信息:接口、方法、版本等等
    • 如果是超时,配置的超时时间,本次处理所用的时间,是Server端超时还Client超时?
    • 重试的次数,本次重试的是第几次(fail over)

    RequestID

    将一个请求的整个处理流程和唯一的requestID关联起来,requestID规则另行定义。

    日志输出级别

    在设置日志输出级别时,推荐如下:

    • 开发、测试环境打开DEBUG;
    • 线上生产环境保证设置为WARN级别;

    关于日志分类


    日志从功能来说,可分为诊断日志、统计日志、审计日志。

    诊断日志:

    • 请求入口和出口
    • 外部服务调用和返回
    • 资源消耗操作: 打开文件等
    • 容错行为: 譬如云硬盘的副本修复操作
    • 程序异常: 譬如数据库无法连接
    • 后台操作:清理程序

    • 启动、关闭、配置加载

    • 抛出异常时,不记录日志

    统计日志:

    • 用户访问统计
    • 计费日志(如记录用户交易流水日志,格式较为严格,便于统计)

    审计日志:

    • 管理操作(类似于登录信息记录)

    关于日志格式


    日志格式一定要统一,不能任由开发人员的喜好来。举例来说,对于NOS视频截图超时的ERROR日志,有以下几种方式打印:

    第一种:
    logger.error(“Gearman timeout exception for request ” + getRequestID() + ” value: ” + value, e);
    第二种:
    logger.error(“RequestID: ” + getRequestID() + “, Error Message: Gearman timeout exception: ” + e);
    第三种:
    logger.error(getErrorMessage(getRequestID(), getErrorMessage(), e));

    第一种方式打印日志即是开发人员按照自己的喜好来的,这种方法带来的问题是:

    • 系统中日志格式不统一,不利于自动化处理
    • 有些日志可能只有开发人员自己才能看懂
    • 代码规范性不好

    而第三种方式,通过一个函数来规范日志格式,所有开发人员便可以通过该接口实现统一的日志。

    关于日志语义


    日志书写语义一定要明确,定义明确语义的异常类信息,在此列举linux环境下两个报错信息作为示例;

    [root@ip-172-31-1-43 ~]# cd /file
    -bash: cd: /file: 没有那个文件或目录
    [root@ip-172-31-1-43 ~]# cd build-public-ult_6.0-2015-09-02-17_6d3c3ee51a.tar.gz
    -bash: cd: build-public-ult_6.0-2015-09-02-17_6d3c3ee51a.tar.gz: 不是目录

    我们可以看到同样是cd命令,在遇到不同的错误行为时,分别针对性的给出了报错信息。

  • 相关阅读:
    【DFS】XIII Open Championship of Y.Kupala Grodno SU Grodno, Saturday, April 29, 2017 Problem D. Divisibility Game
    【二分】【三分】【计算几何】XIII Open Championship of Y.Kupala Grodno SU Grodno, Saturday, April 29, 2017 Problem L. Lines and Polygon
    【线段树】XIII Open Championship of Y.Kupala Grodno SU Grodno, Saturday, April 29, 2017 Problem J. Jedi Training
    【贪心】【后缀自动机】XIII Open Championship of Y.Kupala Grodno SU Grodno, Saturday, April 29, 2017 Problem E. Enter the Word
    【转载】随机生成k个范围为1-n的随机数,其中有多少个不同的随机数?
    【推导】【贪心】XVII Open Cup named after E.V. Pankratiev Stage 4: Grand Prix of SPb, Sunday, Octorber 9, 2016 Problem H. Path or Coloring
    【枚举】XVII Open Cup named after E.V. Pankratiev Stage 4: Grand Prix of SPb, Sunday, Octorber 9, 2016 Problem D. Cutting Potatoes
    【找规律】【递归】XVII Open Cup named after E.V. Pankratiev Stage 4: Grand Prix of SPb, Sunday, Octorber 9, 2016 Problem F. Doubling
    【贪心】Codeforces Round #436 (Div. 2) D. Make a Permutation!
    【计算几何】【圆反演】计蒜客17314 2017 ACM-ICPC 亚洲区(南宁赛区)网络赛 G. Finding the Radius for an Inserted Circle
  • 原文地址:https://www.cnblogs.com/snifferhu/p/6010674.html
Copyright © 2011-2022 走看看