关于java应用程序日志的一些思考

223 阅读11分钟

简介

在程序中写日志是一件非常重要,但是很容易被开发人员忽视的地方。写好程序的日志可以帮助我们大大减轻后期维护压力。日志也属于编程内容,在实际的开发工作中应为写日志预留足够的时间。

日志的作用

日志需求

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

  • 快速定位问题的根源,有助于根因分析.(甩锅必备)

  •  追踪程序执行的过程。

  •  追踪数据的变化

  •  数据统计和性能分析

  •  采集运行环境数据

一般在程序上线之后,一旦发生异常,第一件事就是要弄清楚当时发生了什么。用户当时做了什么操作,环境有无影响,数据有什么变化,是不是反复发生等,然后再进一步的确定大致是哪个方面的问题。确定是程序的问题之后再交由开发人员去重现、研究、提出解决方案。这时,日志就给我们提供了第一手的资料.

 

编写日志的要求

既然编写日志是有需求,而且也能在未来帮助我们提高工作效率的事情,长远来看是非常有利的一件事情。因此我们应该在自己开发的程序中符合规范的撰写日志,在写日志时要注意以下的问题。

日志的可读性

日志是给人读的,不仅仅是让自己明白,也要让没有接触过我们源代码的其他程序员也能够一目了然。有的同事在日志中打印特殊的标识符号,例如“++++++++++”, “===========”,“—————”,这些符号无益问题排查.

另外,把日志分类输出到不同的文件也有利于我们排除干扰,迅速找到我们需要的信息。而且,最好在打印日志时输出英文,防止中文不支持而打印出乱码的情况。

日志的性能

无论我们把日志写到文件还是数据库,都需要消耗IO资源。适当的控制日志的输出也有利于提高程序的性能

另外使用异步日主输出,也有利于提升系统吞吐量.

例如:尽量避免在在大的循环中打印意义不大的日志内容。输出日志之前最好能判断日志的级别(例如. debug前先调用isDebugEnabled()作出判断)。

占用磁盘空间

通常,我们都是把日志写入磁盘上的日志文件中。适当的使用滚动日志并且定时清除旧文件是有好处的。我见过这样一个例子,程序运行几次后就跑不起来了,前几次都是正常的。

怎么都想不明白程序有什么问题,最后才发现居然是日志文件占满了磁盘空间。在实际的应用中出现上G的日志文件也往往不少见,要在这样规模的日志文件中找出对解决问题有用的信息也是一大挑战。

日志的时效性

有的时候我们并不能及时的发现问题。需要追溯之前的日志。所以我们是需要保留一段时间以内的日志便于追溯。

日志内容

我们在写日志的时候,需要注意输出适当的内容。首先,尽量使用业务相关的描述。我们的程序是实现某种业务的,那么就最好能描述清楚这个时候走到了业务过程的哪一步。

其次,避免在日志中输出一些敏感信息,例如用户名和密码。以及,要保持编码的一致。如果不能保证就尽量使用英文而不是中文。这样当我们拿到日志之后就不会因为看到一堆乱码而不知所云了。

在能够记录关键信息的情况下尽可能减少日志内容.

应该日志链路追踪标记,从而更方便的从日志系统检索并观察中整个链路日志.

日志格式

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

日志级别

通常我们在生产环境中日志的级别都在INFO以上,所以我们必须保证在这样的情况下程序仍然能够输出足够我们作出判断的信息。

例如常见的系统具有如下的日志级别:FATAL, ERROR, EARN, INFO, DEBUG, TRACE

RFC 5424中日志级别

RFC 5424将日志级别分为以下 8 种等级:

0 Emergency: system is unusable

1 Alert: action must be taken immediately

2 Critical: critical conditions

3 Error: error conditions

4 Warning: warning conditions

5 Notice: normal but significant condition

6 Informational: informational messages

7 Debug: debug-level messages

各级日志等级信息记录内容如下:

Emergency

导致系统不可用的事故,属于最严重的日志级别,因此该日志级别必须慎用,通常情况下,一个进程的声明周期中应该只记录一次 Emergency 级别的日志

Alert

必须马上处理的问题,紧急程度低于 Emergency,Alert 错误发生时,已经影响了用户的正常访问与 Emergency 的区别是,Alert 状态下系统依旧是可用的。例如:DB / Cache 无法连接。

Critical

紧急情况,程序组件不可用,需要立刻进行修复。例如:用户注册逻辑模块不能发送邮件。

Error

运行时出现的错误,不必要立即进行修复,错误不影响整个逻辑的运行,但需要记录并做检测。

Warning

可能影响系统功能,需要提醒的重要事件

该日志标识系统可能出现问题,也可能没有(比如网络波动)。对于那些目前还不是错误,然而不及时处理也会变为错误的情况,也可以记为 Warning 日志。例如一个存储系统的磁盘使用量超过阀值,或者系统中某个用户的存储配额快用完等等

对于 Warining 级别的日志,虽然不需要马上处理,但也需要及时查看并处理

Notice

不影响正常功能,但需要注意的消息,执行过程中较 Infomational 级别更为重要的信息。

Infomational

用于记录系统正常运行情况下的一般信息,强调应用程序的运行过程。例如:某个子模块的初始化、某个请求的成功执行等

通过查看 Infomational 级别的日志,可以很快对系统中出现的 0~5 级别的错误进行定位

Debug

帮助开发、测试、运维人员对系统进行诊断的信息。

Sfl4j日志级别

slf4j有以下日志级别:

级别大小关系为: ALL<TRACE<DEBUG<INFO<WARN<ERROR<FATAL< OFF

各级日志等级信息记录内容如下:

ALL

ALL 具有可能的最低排名,旨在打开所有日志记录。

TRACE

TRACE designates finer-grained informational events than the DEBUG.Since:1.2.12,很低的日志级别,一般不会使用。TRACE一般跟踪的是函数的调用,并且TRACE不应该含有变量参数,而仅能提示函数的调用关系。

DEBUG

一般用于细粒度级别上,对调试应用程序非常有帮助,主要用于开发过程中打印一些运行信息。

INFO

INFO消息在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志。

WARN

WARN表示会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员一些提示。该级别表示程序会自动调整到正常的状态,类似参数未传入,使用了默认的参数,仍符合程序员预期之内的情况。

ERROR

ERROR指出虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别。一般在WARN之后的级别在打印错误时,应该同时打印错误码。

FATAL

FATAL指出每个严重的错误事件将会导致应用程序的退出,这个级别比较高,重大错误,程序无法恢复,必须通过重启程序来解决。

OFF

OFF 具有可能的最高等级,旨在关闭日志记录。

日志分类

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

诊断日志

  •  请求入口和出口

  •  外部服务调用和返回

  •  资源消耗操作: 打开文件等

  •  容错行为:譬如云硬盘的副本修复操作

  •  程序异常:譬如数据库无法连接

  •  后台操作:清理程序

  •  启动、关闭、配置加载

统计日志

  •  用户访问统计

  •  计费日志(如记录用户使用的网络资源或磁盘占用,格式较为严格,便于统计)

审计日志

  •  管理操作

  •  关键系统访问记录

日志中记录什么

推荐记录的日志内容

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

  •  记录系统运行过程中的所有的错误

  •  记录系统运行过程中的所有的警告

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

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

  •  重要的状态变化

  •  系统中一些长期执行的任务的执行进度

  •  增删改操作需要打印参数日志(以便定位一些异常业务问题)

  •  条件分支需要打印日志:包括条件值以及重要参数

  •  明确日志打印级别与包含的信息

1)提供方服务,建议以 INFO 级别记录入参,出参可选(多数情况下,出参内容比较多,慎用) 

2)消费队列消息,务必打印消息内容

3)调用方服务,建议以 INFO 级别记录入参和出参

4)运行环境问题,如网络错误、建议以 WARN 级别记录错误堆栈

5)定时任务,务必打印任务开始时间、结束时间。涉及扫描数据的任务,务必打印扫描范围

  •  谨慎地记录日志

1)生产环境禁止输出 debug 日志

2)有选择地输出 info 日志

3)如果使用 debug 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志

  •  可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从

   注意日志输出的级别,error 级别只记录系统逻辑出错、异常等重要的错误信息。如非必要,请不要在此场景打出 error 级别.

  •  对日志输出,必须使用条件输出形式或者使用占位符的方式

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

  •  不允许出现System print(包括System.out.println和System.error.println)语句作为日志的打印,不允许出现 e.printStackTrace

  •  应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一

 

不推荐记录的日志内容

  •  函数入口信息:除非该函数入口表示了一个重要事件的开始,或者将该信息记入 DEBUG 级别日志

  •  文件内容或者一大段消息的内容:如果实在需要记录,则可以截取其中一些重要的信息来记入日志

  •  “良性”错误:有时候虽然出现了错误,然而错误处理的流程可以正确解决这种情况,例如插入数据库时有重复的记录,尽管是个错误,然而错误处理流程可以对这种情况进行处理