一个在生产环境里运行的程序如果没有日志是很让维护者提心吊胆的,有太多杂乱又无意义的日志也是令人伤神。程序出现问题时候,从日志里如果发现不了问题可能的原因是很令人受挫的。本文想讨论的是如何在Java程序里写好日志。大多数的Web服务器(如Apache,Nginx)都有access日志和error日志,分别记录在不同的文件内;我们使用的服务器操作系统Linux有Syslog日志, /var/log目录下也有很多基础应用和服务的日志文件;桌面Windows有事件查看器, Mac有Console应用可以查看和管理日志;这些成熟的系统及工具方法都值得我们学习并在自己的项目中应用。
一般来说日志分为两种:业务日志和异常日志。使用日志我们希望能达到以下目标:
程序框架应该提供统一的日志记录接口,日志格式也需要有一定的规范,方便利用日志工具来分析日志。 首先我们有必要了解一下Linux普遍使用的Syslog标准协议,协议规定日志中应包含产生日志的模块(Facility),严重性(Severity Level),时间,主机名或IP,进程名,进程ID和日志内容,根据模块和严重性可以配置相应的动作:是否需要记录,日志存储路径(文件或网络)。
下面是部分常见的Syslog模块类型:
模块ID | 关键词 | 描述 |
---|---|---|
0 | kern | 内核消息 |
1 | user | 用户级别消息 |
2 | 邮件系统 | |
3 | daemon | 系统后台守护程序 |
4 | auth | 安全/鉴权消息 |
5 | syslog | syslogd内部产生的日志消息 |
… |
以及Syslog严重程度划分:
代码 | 严重程度 | 关键词 | 描述 |
---|---|---|---|
0 | Emergency | emerg(panic) | 紧急,系统已经不稳定了 |
1 | Alert | alert | 需要立刻采取措施 |
2 | Critical | crit | 严重情况 |
3 | Error | err (error) | 系统出错 |
4 | Warning | warning(warn) | 系统警告 |
5 | Notice | notice | 系统仍然正常,但值得注意 |
6 | Informational | info | 正常系统通告 |
7 | Debug | debug | 系统调试信息 |
在你的Java程序里日志也可以参考Syslog的设计,根据业务对程序的模块和日志级别做一定的规划和设计。
Java的日志框架太多了。。。
选项太多了的后果就是选择困难症,我的看法是没有最好的,只有最合适的。在比较关注性能的地方,选择Logback或自己实现高性能Logging API可能更合适;在已经使用了Log4j的项目中,如果没有发现问题,继续使用可能是更合适的方式;我一般会在项目里选择使用Slf4j, 如果不想有依赖则使用java.util.logging或框架容器已经提供的日志接口。
Java的日志框架一般会提供以下日志级别,缺省打开info级别,也就是debug,trace级别的日志在生产环境不会输出,在开发和测试环境可以通过不同的日志配置文件打开debug级别。
在程序里要合理使用日志分级:
//调试的时候可以知道进入了方法
logger.debug("entering getting content");
String content = CacheManager.getCachedContent();
if (content == null) {
//使用warn,因为程序还可以继续执行,但类似警告太多可能说明缓存服务不可用了,值得引起注意
logger.warn("Got empty content from cache, need perform database lookup");
Connection conn = ConnectionFactory.getConnection();
if (conn == null) {
logger.error("Can't get database connection, failed to return content");
//尽量提供详细的信息,知道错误的原因,而不能简单的写logger.error("failed")
} else {
try {
content = conn.query(...);
} catch (IOException e) {
//异常要记录错误堆栈
logger.error("Failed to perform database lookup", e);
} finally {
ConnectionFactory.releaseConnection(conn);
}
}
}
//调试的时候可以知道方法返回了
logger.debug("returning content: "+ content);
return content;
上面这段示范代码演示了各种级别的使用,但其中有个问题是debug日志太多后可能会影响性能?有一种改进方法是:
if (logger.isDebugEnabled()) {
logger.debug("returning content: "+ content);
}
但更好的方法是Slf4j提供的最佳实践:
logger.debug("returning content: {}", content);
一方面可以减少参数构造的开销,另一方面也不用多写两行代码;
通常情况下在程序日志里记录一些比较有意义的状态数据:
程序运行消耗时间;
long startTime = System.currentTime();
// business logical
logger.info("execution cost : " + (System.currentTime() - startTime) + "ms");
日志中尽量不要包含敏感信息,对于敏感信息如用户身份证号码,密码可以加密后存储;以防止日志文件不慎外泄时保全用户的数据安全;日志通常不允许修改,必要时还可以通过校验位来鉴别日志是否正确。
TODO:
TODO:
TODO: