在我工作过的公司里,有一家公司的代码里全部都是日志,而另外一家则是一行日志记录都没有。
但是在线上环境中,日志记录是非常重要的,它能帮我们快速定位问题所在。整理之后,我发现下面几点关于日志的建议是非常有效的。
没有提示就是最好的提示
用过 类 Unix 系统的人都知道,Unix 系统的提示哲学是没提示就是最好的提示。如果一个命令执行成功了,那么系统是不会提示的,只有当执行出错的时候才会报错。反观我们的代码,一般情况下我们的代码99%都是正常的执行路径,不会出现逻辑异常或代码异常,只有1%的时候才会出现异常。在这种情况下,我们当然是选择在那1%的地方进行日志输出了。所以写日志我们应该达成一个共识,那就是:只在逻辑异常或代码异常的地方加上日志提示。
你应该考虑阅读者
在我们讨论“没有提示就是最好的提示”时,我们只是站在程序猿的角度来思考如何写提示,但是有时候阅读日志的人可能是
- 一个在调试产品问题的系统管理员或者运维工程师
- 一个尝试自己解决问题的终端用户(想象一个客户端或桌面程序)
- 一个在开发中debug,或者在解决产品问题的开发者
对于不同的”谁”,你将要写下的log信息的内容,上下文,类别和level会大不同。开发者了解程序内部,所以给他的log信息可以比给终端用户的复杂得多。所以为了兼顾其他人对于程序的理解,有时候需要在一些重要的业务逻辑里增加一些与逻辑异常或代码异常无关的一些代码,如:在P2P项目中,当用户完成充值的时候,你需要打印出用户充值成功的信息。
简单地说:我们不仅仅要在逻辑异常或代码异常的地方加上日志提示,而且也需要在每个环节完成的时候输出日志提示,这样会便于我们进行线上问题的定位。
你应在适当级别上进行log
如果你使用了开源日志框架,那么你要对你程序中每一个log语句使用不同的log级别。其中最困难的一个任务是找出这个log应该是什么级别。在 SLF4J 中常用的级别有这么几个,他们的日志级别从高到低是:ERROR > WARN > INFO > DEBUG。当代码中的级别大于设置文件中设置的级别时,对应的日志就会输出。
以下是我的一些建议:
- DEBUG Level:主要用于开发人员进行代码调试的时候使用。比如当开发人员发现了一种异常情况,这时候我们就需要输出一些日志信息,那么这时候就应该使用 log.debug() 进行日志记录。
- INFO Level:重要的业务逻辑处理完成。在理想情况下, INFO的日志信息要能让高级用户和系统管理员理解, 并从日志信息中能知道系统当前的运行状态。比如对于一个机票预订系统来说, 当一个用户完成一个机票预订操作之后, 提醒应该给出"谁预订了从A到B的机票"。另一个需要输出INFO信息的地方就是一个系统操作引起系统的状态发生了重大变化(比如数据库更新, 过多的系统请求)。
- WARN Level:系统能继续运行,但是必须引起关注。对于存在的问题一般可以分为两类: 一种系统存在明显的问题(比如数据不可用), 另一种就是系统存在潜在的问题, 需要引起注意或者给出一些建议(比如,系统运行在安全模式或者访问当前系统的账号存在安全隐患)。总之就是系统仍然可用,但是最好进行检查和调整。
- ERROR Level:系统发生了严重的错误, 必须马上进行处理, 否则系统将无法继续运行。比如:NullPointerException、内存不足等。
正确的使用输出模式
log 输出模式可以帮助我们在日志中增加一些清晰的上下文信息,不过对添加的信息还是要多加小心。比如说,如果你是每小时输出一个文件,这样你的日志文件名中已经包含了部分日期时间信息,因此就没必要在日志中再包含这些信息。另外在多线程环境下也不要在自己在日志中包含线程名称, 因为这个也可以在模式中配置。
根据我的经验, 一个理想的日志模式将包含下列信息:
- 当前时间(不需要包含日志, 精确到毫秒)
- 日志级别(如果你关心这个)
- 线程名称
- 简单的日志名(非全限定名的那种)
- 日志描述信息
根据实际情况,你还可以添加下面一些信息:
- 文件名
- 类名(我想这个应该是全限定名吧)
- 代码行号
但要记得,不要用反射去自己获取文件名、类名等信息再去作为日志信息输出,这样会极大降低日志效率。如果需要,你完成可以在 SLF4J 的配置文件中进行配置。
日志信息应该用英语
如果你的程序被大多数人使用,而你又没有足够的资源做国际化,英语会成为你的不二之选。
你应该给log带上上下文
没有什么比这样的log信息更糟的了
Transaction failed
或者
User operation succeeds
又或是API异常时:
java.lang.IndexOutOfBoundsException
没有相应的上下文,这些信息不过是噪音,它们不会对调试过程中有意义的数值或是空间起作用(add value and consume space)。带上上下文的信息要有价值得多,例如:
Transaction 234632 failed: cc number checksum incorrect
或是:
User 54543 successfully registered e-mail<a href="mailto:user@domain.com">user@domain.com</a>
又或是:
IndexOutOfBoundsException: index 12 is greater than collection size 10
在上面这一例子中的异常,如果你想把它传播开, 确保在处理的时候带上与当前level相应的上下文,让调试更简单,如下一个 Java 的例子:
public void storeUserRank(int userId,int rank,String game) {
try {
...deal database ...
} catch (DatabaseException de) {
throw new RankingException("Can't store ranking for user "+userId+" in game "+ game + " because " + de.getMessage() );
}
}
使用占位符而不是拼接字符串
使用占位符更有利于代码的阅读,并且可以提高性能。因此你应该写这样的日志记录:
logger.info("User {} login.", userId);
而不是这样的:
logger.info("User " + userId + " login.");
日志不能打断业务逻辑
在下面的日志记录中,如果 user 为空,那么将会抛出异常,那么就会直接导致程序中断。
logger.info("User {} login fail.", user.getUserInfo);
因此在日志记录中,要避免日志中发生异常而导致正常的业务逻辑受到影响,这是绝对不允许出现的。
纸上得来终觉浅,说了这么多,但还是要实践时不断去应用,去提取适合自己的日志规则,这样才能写出好的代码。
出处:http://www.cnblogs.com/chanshuyi/p/how_to_write_log_in_code.html