专栏首页咖啡拿铁谈谈日志的最佳实践

谈谈日志的最佳实践

1.背景

日志是我们程序员的一个老生常谈的话题,你可能每天都会听到这个词。想起我刚刚大学毕业的时候刚进入公司,正逢做一些部门业务交接,也就是其他部门的服务交给我们维护。记得没交接多久,当时业务上微信公众号相关功能就出现了不可用,当时负责这部分业务的同学,排查问题及其艰难,整个链路一个日志都没打,就在入口处error日志,连续上了好几次线,加了好几轮日志,才把问题给定位住了。当时其他部门也出现了另外一个例子,日志打得太多了,由于业务访问的量级,导致大量日志打出,从而让磁盘IO打满,最后让整个服务瘫痪。

时间一晃就过了好几年,但是上面这样的问题在不同的公司,不同的部门都在上演着,很多开发人员为了图自己的一些方便,不重视日志的使用,有时候如果一直是你维护也还行,出了问题由于是你开发的,可能你一眼就能发现,但如果你的代码交接出去,让其他人进行维护。平时也有一些公众号的读者会向我咨询日志使用的一些问题,于是我在这里结合平时的一些使用经验还有《阿里巴巴java开发手册》,写一下我认为的日志最佳实践。

2.最佳实践

2.1 合理的级别划分

在日志系统中有6种级别来控制我们日志的输出:

  • TRACE: 在线调试,这个基本没有使用过,比较鸡肋。
  • DEBUG: 用于调试的日志,如果信息不是很重要,只是在某些极端的场景才会需要,那么就可以使用DEBUG。
  • INFO: INFO信息通常用于某些日志输出需要常态化,需要经常使用它,我们排查一些业务问题经常也需要这部分信息,那么你就可以使用INFO。
  • WARNING:警告信息,通常用于一些已知的业务错误,这部分错误基本能处理。
  • ERROR: 错误信息,通常用于我们无法处理的异常或者错误,对于这部分应该使用ERROR。
  • FATAL: 致命的错误,代表这程序需要马上终止,这个用得也很少,在业务使用中我们也不会使用

虽然有6种日志级别,但是我们在真正的业务开发中需要关注的业务级别一般来说只有3种,TRACE和FATAL这两个基本不会使用,DEBUG在一些基础工具开发中我们使用得比较多,因为这些基础工具打印日志是对于业务方来说是隐式打印,所以如果不是太重要的信息都需要使用debug。

对于ERROR和WARN这两种日志其实有挺多同学都会混用,通常来说出现ERROR日志会发各种提醒,比如短信这些,有一些同学老是把所有错误都给打成ERROR,比如用户没有权限,用户余额不足等等,那么难免少不了短信轰炸。其实这部分错误其实是属于我们业务流程中的一部分,那么其实应该使用WARN打印日志就够了。这里给大家推荐一个好的方法,来处理这种情况,我们将所有的业务异常都继承一个异常,在我们的业务中是BizException,通过捕获这部分异常去打WARN日志。

        try{
            // do something
        }catch (BizException e){
            LOG.warn("biz exception", e);
        }catch (Exception e){
            LOG.error("exception", e);
        }

当然这部分逻辑也可以通过切面去处理,把异常信息尽量塞入到exception中,统一去处理。

动态日志级别调整

我们上面讲了日志有6种级别,但是我们对于一个日志系统来说会有一个整体的级别的选择,通常在业务中我们会选择info,也就是我们输出的日志级别大于等于info就会被输出到文件中。我们想想下面的两种情况:

  • 在某个业务中触发了一个bug,大量的打出error日志,不仅影响机器的性能,并且还频繁的发出错误短信。
  • 需要排查某个基础工具中间件的问题,但是日志级别是debug,这个时候需要重新上线修改日志级别为debug排查问题。

这两种场景都可以利用动态日志级别调整去解决,当大量出现error日志的时候,可以立即把日志关闭,防止由于打印日志引发更多的问题。当需要排查一些debug级别的问题的时候,直接修改到debug输出级别就能满足需要。

动态日志级别调整的方法一般有下面几种方法:

  • 如果是spring-boot 1.5之后的版本,引入spring-boot-starter-actuator,通过http接口修改日志级别。
  • 使用arthas,通过ognl修改,如下面代码所示:
ognl -c 1be6f5c3 '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)'
  • 自己开发一套支持http协议修改级别的工具,之前美团有一篇文章(https://tech.meituan.com/2017/02/17/change-log-level.html)可以参考

2.2 合理的使用占位符

有一道经典面试题,如下面代码所示,你能找到这个代码的问题在哪吗?

        LOG.info("a:" + a + "b" + b );

如果我们日志输出级别是warn,那么我们的info日志实际上是不会打印的,但是里面的字符串相加这个操作确实会被执行,所以为了出现这个问题,之前很多人都会加一个级别判断:

        if (LOG.isInfoEnabled()){
            LOG.info("a:" + a + "b" + b );
        }

但是这么写,就会导致有点拖沓,于是就出现了占位符的写法:

        LOG.info("a:{}, b:{}", a, b);

这里一行代码就完成,这里要说明的是日志服务并没有做特别的事,占位符的替换依旧是通过MessageFormat这个类去做的,也就是会一个一个的遍历,所以在这里唯品会java手册上推荐如果是ERROR的日志,推荐使用上面的“a” + a + “b:” + b这种模式,因为ERROR一般来说都需要每次打印,所以不需要担心白做了String的拼接。我个人觉得没必要为了占位符的这点性能做太多的考虑,你info都使用了占位符,一般来说info的日志占据了这个系统99%的日志,只有那么1%的error日志,所以提升一般是不大的,并且反而会让人产生错用的可能。

异常不用占位符

     try{
           // do something
       }catch (Exception e){
           LOG.error("exception :{}", e);
       }

很多人在打印异常的时候都会像上面那样去写,占位符填充的时候其实取的就是toString方法,那么如果直接调用异常的toString就会导致异常的堆栈信息丢失,从而加大我们排查问题的难度,所以这里要强调如果打印异常不能直接用占位符,而是直接写在末尾,日志打印的时候会自动打印出一场的堆栈信息。

2.3 合理的选择日志输出方式

日志输出的模式分为两种:同步和异步模式。我们在业务中一般来说会选择异步模式,在log4j2中异步模式分为两种:

  • AsyncAppender:使用ArrayBlockingQueue保存异步日志,然后使用一个异步线程输出。
  • AsyncLogger:使用Disruptor框架保存日志,然后使用一个异步线程输出。

对于Disruptor之前写过一篇文章: 详解Disruptor,这是一个高性能队列,如果我们在log4j2中使用Dirsruptor会增加日志输出吞吐量。但是Disruptor的日志输出模式,我们一般用得比较少,之前在美团的时候因为用Disruptor输出日志,造成了cpu打满的情况,所以Disruptor输出日志在有些地方是被禁止使用的。一般来说AsyncAppender就能满足我们的使用了。

2.4 合理的保存日志

当我们思考清楚日志的输出方式之后,我们就可以考虑如何保存日志,因为我们的磁盘空间不是无限的,我们就需要考虑日志的过期删除,阿里巴巴java开发手册中明确的强制要求我们至少保存15天日志,对于用户的敏感操作和重要的日志,需要6个月的保存时间,在log4j2中也可以通过下面的配置进行日志删除操作:

<DefaultRolloverStrategy max="30">
                <Delete basePath="${LOG_HOME}/" maxDepth="2">
                    <IfFileName glob="*.log" />
                    <IfLastModified age="30d" />
                </Delete>
            </DefaultRolloverStrategy>

这里就代表这最多保存30天的日志,当然也可以在机器上面配置个定时任务进行删除。

有时候我们的机器比较多当我们排查某个问题的时候,最原始的方法我们会一个一个的机器上去看,后面我们又开始用polysh,只需要在一台机器上面执行命令,其他机器就会自动执行,但是这样还是有点不方便,所以后面又出来了ELK,我们使用Logstash收集所有的日志然后存储到Elasticsearch,最后使用Kibana进行可视化界面分析。所以使用Elasticsearch存储日志也是一个不错的办法。

2.5 合理的输出日志

在我们的系统中,如果输出大量的无效日志,会影响我们的系统的性能,所以我们的日志的打印也需要进行思考,哪些地方对我们有帮助,而不是一股脑的全部打出。

我们在通过日志排查问题的时候,通常会跨服务,有时候这个日志的信息不是能很好的对应上,所以我们这个时候就需要有一个东西去把他们给关联上,在这里的话就是traceId,我们通过一个traceId就可以拿到整个链路中的日志信息,对我们的日志排查特别方便。

2.6 不要有敏感信息

在2018年的时候facebook数据泄密,那个时候整个互联网突然开始重视起来敏感信息的泄漏,在日志系统中也是很容易泄密的,比如用户的姓名,手机号等等。如果打在日志中,很容易被不法分子盗用信息,所以我们在打印日志的时候要特别注意敏感信息的问题,具体的日志脱敏我之前也写过一篇文章有兴趣的可以看一下。手把手教你如何设计日志脱敏插件

2.7 合理的日志划分

有很多同学把所有日志都打在了同一个文件里面,对于我们的排查日志信息的时候特别不方便,我们可以把日志分成多个文件,比如根据不同的中间件,http,rpc,mq等等都可以单独搞成独立的日志文件,这下排查某个问题的时候就比较容易归纳查找。

2.8 第三方工具

虽然讲了这么多日志的一些规约,但是不可能很完美的每次都能打出我们想要的结果,比如某些方法可能没有加日志,但是又要排查一些问题。这个时候可以借用我们的第三方工具,比如arthas,我们使用arthas的很多命令比如watch,trace一样可以完成我们日志的功能,只是第三方工具不是万能,他只能帮助我们检查即将出现的一些数据,历史的数据还是得靠我们的日志系统去保证。

总结

当然日志的实践优化不仅仅上上面这些点,还有更多的场景需要结合实际业务去进行优化。这里希望大家能使用好日志,让天下没有难排查的问题!

如果大家觉得这篇文章对你有帮助,你的关注和转发是对我最大的支持,O(∩_∩)O:

本文分享自微信公众号 - 咖啡拿铁(close_3092860495),作者:咖啡拿铁

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-05-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 手把手教你如何设计日志脱敏插件

    我所在的公司最近要求需要在所有地方都要脱敏敏感数据,应该是受faceBook数据泄密影响吧。

    用户5397975
  • 从Service Mesh谈如何做好监控

    谈到 Service Mesh,人们总是想起微服务和服务治理,从 Dubbo 到 Spring Cloud (2016开始进入国内研发的视野,2017年繁荣)再...

    用户5397975
  • 研究网卡地址注册时的一点思考

    我曾经写过一篇和本文标题类似的文章《研究优雅停机时的一点思考》,上文和本文都有一个共同点:网卡地址注册和优雅停机都是一个很小的知识点,但是背后牵扯到的知识点却是...

    用户5397975
  • [喵咪BELK实战(1)]浅谈日志的重要性以及介绍BELK

    [喵咪BELK实战(1)]浅谈日志的重要性以及介绍BELK #w-blog博客 ? 前言 哈喽大家好呀!这次主要为大家带来BELK日志系统相关的博文,日志大家都...

    喵了个咪233
  • 【案例】鹏华基金——金融领域的日志大数据实践

    数据猿导读 依托移动终端的普及和互联科技的飞速发展,金融行业需要抓住机会技术升级、积极应变。在转型过程中如何利用大数据技术发掘数据真正的价值,是当前金融业打破传...

    数据猿
  • 业务上云使用腾讯云日志服务方案

    日志服务(Cloud Log Service,下文简称CLS服务)是腾讯云提供的一站式日志数据解决方案,可以快速便捷的接入,享受日志采集、日志存储到日志内容搜索...

    覃春善
  • MySQL常用工具、日志及读写分离

    该mysql不是值mysql服务,而是指mysql的客户端工具。 语法 : mysql [options] [database]

    海仔
  • Java日志正确使用姿势

    关于日志,在大家的印象中都是比较简单的,只须引入了相关依赖包,剩下的事情就是在项目中“尽情”的打印我们需要的信息了。但是往往越简单的东西越容易让我们忽视,从而导...

    方丈的寺院
  • 【学习】深度解析LinkedIn大数据平台(一)

    我在六年前的一个令人兴奋的时刻加入到LinkedIn公司。从那个时候开始我们就破解单一的、集中式数据库的限制,并且启动到特殊的分布式系统套件的转换。这是一件令人...

    小莹莹
  • 腾讯云容器服务日志采集最佳实践

    roc,腾讯高级工程师,Kubernetes Contributor,热爱开源,专注云原生领域。目前主要负责腾讯云TKE 的售中、售后的技术支持,根据客户需求...

    腾讯云原生

扫码关注云+社区

领取腾讯云代金券