被使用,但主要是logback和log4j2.x。虽然logback和log4j2.x对于异步日志记录的效率几乎相同,但log4j2.x仍然有一些轻微的优势。从日志接口框架可以看出,Java中有很多不同日志框架的实现,这会造成两个问题:多框架协同:在一个项目中,不仅是你的代码,还有各种Framework的代码,比如分布式协调将使用Zookeeper和Curator;RPC通信会用到Dubbo和Thrift。业务系统为了方便开发,往往会集成很多第三方框架。我们需要日志来了解各种第三方框架之间的协作状态,而这又依赖于各个日志框架的输出。这时候如果直接使用logback、log4j之类的日志框架,业务系统岂不是需要访问各个日志框架?不同框架之间的竞争:如果要引入多个日志框架,我们还需要考虑每个框架的输出位置。如果多个日志框架同时写入一个日志文件,会涉及到竞争问题,导致性能失败。由此,出现了一个面向接口的日志框架,它提供了统一的API。开发者在写代码的时候,直接使用这个面向接口的日志框架。业务项目人员使用时,只需要选择实现框架,统一日志实现框架即可。目前使用最广泛的日志接口框架是SLF4J,它出自logback的开发者之手,基本形成了规范。SLF4J提供了动态占位符的功能,大大提高了程序的性能,并且不需要开发者去拼接参数信息。例如,默认情况下,程序处于信息级别。原代码方式下,如果要log输出,需要自己拼接字符串:logger.debug("user"+userId+"startorder:"+orderNo+",requestinformation:"+Gson.toJson(要求));这会导致问题。如果系统中有很多类似的代码,而系统只输出info及以上级别的日志,那么在输出debuglogsstring时会产生大量的字符,却不输出debuglog,最后导致不断拼接的字符串,浪费系统性能。此时SLF4J可以使用占位符功能来写入日志,例如:logger.debug("用户{}开始下单:{},请求信息:",userId,orderNo,Gson.toJson(req));通过这种形式,SLF4J可以根据日志级别进行判断,只对符合要求的日志进行数据拼接和打印。有时日志输出需要进行数值计算或JSON转换,此时需要进行一定的计算任务。但是方法调用如果作为参数传递就会执行,所以Java8中的SLF4J也可以通过Supplier传递。如下图:logger.debug("用户{}开始下单:{},请求信息:",userId,orderNo,()->Gson.toJson(req))可以看出SLF4J不仅制定了a面向接口开发的方法也为我们理清了如何有效的写日志。这就是为什么越来越多的人喜欢使用SLF4J的原因。日志的写法在详细介绍了我们在开发中需要用到的日志框架之后,正式展开我们的标题:《如何写'可观察的'日志?8点,即特性、可读性、关键信息的隐藏、减少代码位置信息的输出、文件分类、日志审核,分为2个方向:日志开发(前5项):如何写一个更高效的日志?日志完成后(最后3条):上线前后需要注意什么?日志写入位置日志写入位置可以说是重中之重。一个好的日志定位可以帮助你解决问题,也可以让你更好的了解代码的运行状态。我总结了一些重要的地方可以写日志,以供参考。系统/应用程序启动及参数更改:系统启动时,可打印相关参数信息,以便在出现问题时更准确的查询原因;参数信息可能不会保存在本地,需要通过配置中心获取,同时参数信息发生变化时,也需要在日志中输出变化的内容。关键操作节点:最典型的是在关键位置增加日志,记录用户进行的某次操作。当出现问题时,可以通过该位置的日志了解用户的操作。同样,您也可以在系统执行某些操作时添加日志。比如当你准备启动线程池进行数据处理时,可以添加日志,方便以后分析问题。大型任务进度上报:当系统在处理比较大的任务时,可以在处理过程中加入相关的日志来提示任务处理的进度,防止程序执行状态因时间过长而无法获知术语非处理,比如在下载文件时,可以按百分比定时/固定上报数据。Exception:当程序出现异常时,我们通常使用try-catch记录当时的情况,然后以日志的形式显示出来。如果是try-catch处理的,需要注意日志写到哪里了。如果需要在这一层抛出日志,则不需要打日志,否则会出现日志重复的问题。如果需要记录异常之外的其他内容,可以通过自定义异常信息来实现。写入性能日志的写入性能会受到以下五个因素的影响:日志写入位置:日志写入的位置在程序中非常重要。如果写成for循环,因为这个循环会进行很多次,那么就会产生很多logging。此时可以考虑是否需要记录这条日志。日志量:如果写的日志多了,那么日志的质量肯定会下降。同时,大量的日志会让你难以排查问题,反而会成为一种负担。流量大的时候,日志过多也会影响程序的执行效率。日志写入级别:上一节说过,日志级别很容易被滥用,不正确的日志级别会增加我们查询问题的时间。日志输出级别:指配置日志输出级别的选择。线上环境不建议使用debug级别,因为线上一直有请求,debug级别会输出很多基础和请求信息,极度浪费资源,所以建议开启info或以上。无用输出参数:不要输出大字段和无用字段,因为这会极大地影响程序执行的效率和日志的内容。曾经遇到过一个案例,A同学在网上打印了一份完整的HTML内容,导致当天的部分日志内容乱七八糟,部分日志无法找回。原因是HTML中有多行内容无法解析。当开发人员到实时服务器查看时,日志文件的大小已经增长了3倍。好的日志一定能方便你排查问题。写日志的时候一定要想好日志能为你做什么。Placeholders之前在介绍日志接口框架的时候提到过,在写日志的时候,尽量选择基于占位符的写法。这里我会告诉你为什么要使用占位符:为了节省性能。在生成高级别日志时,低级别日志会不断叠加字符串,占用内存和CPU资源过多,造成性能浪费。容易写。先确认你要在日志中表达的内容,再确认你需要写的参数,这样写日志的时候,目的会更加明确。便于查看。codereview时,更方便的看log想表达什么,不受log参数的干扰。可读性日志的可读性也是日志写入的关键之一。好的日志就像一篇好文章,可以让你快速了解这篇日志中的关键信息。在工作中,我发现很多人写的日志都是没有意义的,不能帮助你定位到问题的根源。例如下面的代码:booleansuccess=doSomeThing();if(success){logger.info("数据保存成功!");}这段代码乍看没问题,但是运行之后,系统会输出大量的“datasavedsuccessful!”messages,和none一样,没有效果。我在日志中总结了一些容易遗漏的信息:SessionID:当前操作的用户和当前请求相关的信息,类似于Session。当出现问题/查看行为时,可以根据这个值快速识别相关日志。RequestID:每个请求都有一个唯一的ID,所以在看问题的时候,我们只需要查看这个请求中的所有日志即可。一般我们会和链接追踪系统一起使用,因为后者可以实现跨应用的日志追踪,从而帮助我们过滤掉不相关的信息。参数信息:在日志中添加参数信息,可以帮助您了解问题发生的情况,从而方便您重现问题和定位错误原因。生成数据的结果:对应参数信息,一个是执行前,一个是执行后。生成数据的结果可以帮助你了解程序执行的结果,也是出现错误时的重要参考条件。关键信息隐藏关键信息不显示或屏蔽显示,避免信息被盗后数据内容泄露。推特在2018年的日志中打印了用户,暴露了3.3亿人。如果不需要减少代码位置信息的输出,日志格式中尽量不要输出当前日志所在的代码行和方法名信息。如果看过logback和log4j的源码就会知道,这是通过获取当前线程堆栈快照信息来实现的,这会极大地影响程序执行的效率。log4j文档中有一段话:“使用同步方式获取位置信息会慢1.3到5倍,如果使用异步日志,因为会涉及到位置信息的跨线程获取,会慢30到100倍慢点。原文:https://logging.apache.org/log4j/2.0/manual/async.html#Location。因此,关闭代码位置信息的输出可以节省系统资源使用,提高性能。文件分类文件分类可以帮助您提高检索日志信息的效率。将不同的业务逻辑分类到不同的日志文件中,可以保证你看到的信息是与这个功能相关的,不会被其他日志干扰。这也是在大型系统中经常使用的功能。比如拉勾的单点登录系统,会将用户的极限测试验证功能和登录验证功能拆分成两个独立的日志文件。当出现问题时,可以根据相关功能的日志快速排查问题,减少排查所需的时间。每次日志审核功能上线后,除了返回业务功能外,还需要观察日志,确认日志内容的输出,比如日志内容是否符合预期,有没有不合适的地方?一个好的日志不能一次写出来。必须像代码一样不断迭代,才能写出处理问题更方便,可读性更强的日志。日志管理就像一个商店,售出产品后负责售后。日志写好之后,对其管理也很重要。一个好的日志管理方式可以提高读取日志的效率,这需要开发人员和运维人员的协作。日志格式日志的格式和布局会影响运维人员收集和管理这些日志内容的效率。如果编写者和管理者能够协商出一套完整的日志格式,那么在排查问题的时候就能事半功倍。简单介绍一下写日志需要注意的几点:系统之间的格式要保持一致:各个应用尽量保持日志格式统一,以便运维人员使用统一的日志在收集日志时使用模板来收集和格式化内容,减少双方的沟通成本。不写多行日志内容:除异常堆栈信息外,不输出多行日志内容。多行内容对数据分析很不方便,可能会产生多条日志。不要使用日志中常见的内容进行拆分:如果使用日志中常见的内容进行拆分,格式解析会出现问题。例如,用户ID中的空格是比较常见的内容。日志归档日志归档是一件很重要的事情。如果把所有的日志内容写到一个文件中,日志文件会变得越来越笨重,不利于日志的收集和查看。一般我们把日志按照日期归档,每天生成一个日志文件,这样在备份和恢复日志的时候,就可以按照日期进行。如果觉得天级别的日志还是偏大,可以考虑按小时细分。结束语今天我们学习了写日志的工具,写日志需要注意的8件事,以及日志管理的方法。你以前犯过哪些错误,哪些是你没有想到的?欢迎大家在留言区分享和讨论。希望您在以后的日志编写中能够注意到这些问题。
