目录
日志是一个业务系统的重要组成部分。因为日志通常不属于系统的核心功能,所以常常不被开发人员所重视,然而一旦需要用到日志时,大家又会生出诸多吐槽:想要的日志没有;无用的日志一大堆;有用的日志被大量无效的日志信息淹没,查找起来非常困难。
打印日志简单,打印好日志很难。写好每一条日志,与君共勉!
1 日志是什么
日志:记录离散的事件,包含程序执行到某一点或某一阶段的详细信息。
日志的作用:
- 快速定位问题:包括线上问题的定位和线下环境的开发调试;
- 记录用户的操作行为:主要用于安全审计,合规要求,大数据分析,场景回溯等;
- 采集程序的运行状态:比如接口流量、耗时、成功率,JVM信息等;
2 Java日志框架
SLF4J(Simple Logging Facade for Java)是日志门面框架,仅提供日志记录的API,不实现日志记录的功能。Logback、Log4j2分别是两种日志框架实现。
代码中不可直接使用Logback或Log4j2中的API,而应依赖使用SLF4J中的API。这样能够提升可移植性,在使用不同日志框架实现的应用之间能够做到无缝的适配,同时应用在切换日志框架实现时无需代码改造。
3 日志记录什么
3.1 日志格式
日志格式分为元信息和日志内容两部分:
- 元信息由日志框架实现打印,包括日志时间、日志级别、线程名、opentracing标识、位置信息(类名、方法名、文件名、行号)等;
- 日志内容由业务方打印,根据业务场景打印合适的内容,团队内部需要一些约定进行统一,通过代码评审持续改进;
重要的信息统一用key=value格式输出,前后空格分离,这是很多log处理工具(splunk,logentries)的推荐。
3.2 日志级别
事有轻重缓急,日志的输出也是分级别的,不同的级别打印不同的日志。
常用的日志级别有四个,按照优先级从高到低分别是ERROR、WARN、INFO、DEBUG:
- ERROR(错误):表明系统出现了异常,无法正常工作,比如请求或跑批运行失败等。异常错误信息(Throwable)记录在ERROR日志中,方便后续人工回溯解决。
- WARN(警告):表明系统出现了不应该出现的问题,但不影响正常工作,比如请求参数错误等。WARN级别日志也需给予足够关注,当WARN日志超过一定阈值时,应当及时介入。
- INFO(通知):记录系统正常工作期间关键运行信息,该级别日志主要用于生产环境的日志输出。
- DEBUG(调试):该级别日志只在开发或测试环境开启,输出调试性质的内容,便于在开发、测试阶段出现异常或问题时,对其进行分析、定位和诊断。
3.3 日志信息
理想的日志中应该记录不多不少的信息。
- 不多,是指不要在日志中记录无用的信息。过多的信息会干扰我们的视听。
- 不少,是指对于日志的使用者,能够从日志中得到所有需要的信息。信息太少不足以满足我们的需求。
INFO(通知)中建议记录的日志有:
- 用户的交互
- 重要的启动配置
- 持久化数据的更改,重要的状态更改
- 主要系统模块间的请求和响应
- 长时间运行任务的进度,需要很长时间才会满足条件的等待
- 重要的逻辑和条件判断分支
不建议记录的日志有:
- 方法入口:不要在函数的入口打印日志,除非它是重要的
- 内容非常大的消息或文件:文件或一大段消息的内容:可以精简或总结其中重要的信息打印
- “良性”的错误:不是真正错误的错误会混淆日志信息,当异常处理是成功执行流程的一部分时容易发生这种情况
- 在循环中打印日志:这会造成日志打印过多,建议在循环外打印精简总结后的日志
记录多少的日志信息是最合理的?这个尺度很难把握,对开发人员而言这是最大的挑战。记录日志时请先思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给你带来用处?
4 日志红线
4.1 不能影响系统正常工作
一定要确保不会因为Log语句的问题而抛出异常造成程序中断。举个例子,如果request为null,就会抛空指针异常。
log.info("execute failed, id={}", request.getId());
4.2 不允许输出敏感信息
日志中不能打印用户的姓名,手机号,身份证号等敏感信息。
4.3 生产环境禁止开启DEBUG级别日志
DEBUG日志太多,不仅会影响业务系统的性能,甚至将磁盘打满,影响业务系统的正常运行。
5 反面case
- 使用字符串拼接:会产生很多String对象,占用空间,影响性能,而且使用字符串拼接的可读性和可维护性都比较差,建议使用占位符。
- 使用标准输出:不要使用System.out或System.err 输出日志,这个只会打印到控制台,不方便管理日志,且标准输出的元信息不全,很难排查问题。
- 使用e.printStackTrace():它会通过System.error打印到控制台,且会对System.error加锁从而影响性能。
- 打印错误日志后又抛出异常:这会造成重复打印日志,如果捕获异常后又抛出了自定义业务异常,此时无需打印错误日志,由最终捕获方进行异常处理。
- 错误日志信息不全:比如只打印异常的基本描述信息,未打印异常的堆栈信息,不利于排查问题。每一条错误日志都应该是独立的,尽可能完整、具体、直接说明何种场景下发生了什么错误,由什么原因导致,要采用什么措施或步骤。
- 参数校验错误打印ERROR日志:对于用户自己操作不当,如请求参数错误等,推荐打印WARN日志。