简介
Java里存在众多的开源日志框架: slf4j, logback, log4j, JCL(Apache Common Logging), JUL(JDK自带的java.util.logging)等。这其中slf4j属于一套简洁的日志API,其并不包含日志的实现(它并不负责日志输出等,JCL也包含API)。slf4j提供了众多的适配器可以适配其他所有开源日志框架,这样让我们在代码中只需面对slf4j的API,然后可以任意切换日志的实现。你可能会讲我们并不需要切换日志框架这种功能,但是我们项目中需要使用大量的第三方库,而这些第三方库使用的日志框架各不相同,不同的日志框架需要不同的配置,不同的配置会导致日志输出到不同的位置。一个应用肯定需要将日志level,日志输出等收纳起来统一管理。对于这种情况,我们可以使用slf4j的适配器将第三方库中各种日志的实现接管,接管之后就可以统一配置这些第三方库中使用的日志了。
在这些日志实现框架中又数logback性能最优,所以在选择日志实现的时候推荐使用logback。
举例
我们的系统中使用了两个开源类库zookeeper.jar和dubbo.jar。假如zookeeper.jar中使用的日志框架是log4j,而dubbo.jar中使用的是JCL。那我们采取何种方式将这两者的日志统一管理呢?参考下面的pom文件(仅为举例):
<!--注意各个依赖的scope --> <dependency> <groupId>com.alibaba</groupId> <artifactId>dubbo</artifactId> <version>${dubbo.version}</version> <!-- 排除dubbo中依赖的JCL --> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>${zookeeper.version}</version> <!-- 排除zookeeper中依赖的log4j --> <exclusions> <exclusion> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </exclusion> </exclusions> </dependency> <!--使用适配器将log4j接管--> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>${slf4j.version}</version> <scope>runtime</scope> </dependency> <!-- 使用适配器将JCL接管 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${slf4j.version}</version> <scope>runtime</scope> </dependency> <!-- 引入slf4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <!-- 在我们的项目中我们选择了logback --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>${slf4j.version}</version> <scope>runtime</scope> </dependency>
这样我们在classpath下放置一个logback.xml就可以配置系统中所有日志了(包括第三方类库内使用的日志)。而我们的应用中的代码日志都应该采用类似下面的方式:
//我们的代码中只应该直面slf4j的API
private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(Broker.class);
//...
LOGGER.error("Create broker for {} error", name, e);
这里需要注意的是,如果你的日志实现选择log4j,但因为log4j比slf4j先发布,所以需要添加slf4j到log4j的适配:
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency>
要点
- 统一使用 slf4j , 避免在代码中直接使用log4j 或 java logging 等实现类
- slf4j 本质是 Facade , 便于我们后期随时切换日志实现.
- INFO 及以上的系统日志统一输出到Console
- 我们发现,在生产环境中如果将系统log切分成多个文件, 是非常不利于问题排查的. 不仅要同时追找多个文件, 还要匹配问题发生的时间点. 最要命的是, 有些异常是被print到 console里的.
- 第二个就是, 我们原先切分日志是为了报警和rolling, 但实施下来其实仍然有问题, 经常会因为添加一个类型的日志而遗失相当一部分报警信息. 现在我们有了唐娟的强大日志分析系统可以更好的满足这部分需求.
- 业务日志使用独立的路径配置, 不能采用 className
- 业务日志需要与系统日志明确区分开, 尤其是日志级别, 一定要设置成最低级别, 免得被调试过程中的其它设置捕获到.
- 所有日志都输出到 catalina.base/logs 下
- 避免大家到处找日志, 降低沟通成本.
- 最佳实践
- 禁止使用 System.out 或 System.error
- 禁止使用 Apache Commons Logging , Java Util Logging
- 推荐 slf4j
- 推荐 Logback
- 代码中使用 SLF4J (not log4j, not jcl, not jul, not logback):
- 类库项目不允许引入logback, log4j等日志实现,只允许引入slf4j
名词解释
业务日志
- 业务相关的信息. 特征是不包含异常栈.
系统日志
- 调试相关的信息, 异常信息. 用于程序员收集系统健康状况信息.
日志级别
编者按: level 定义简直就是一门艺术, 好的定义应该遵循以下原则:
warn以上信息通俗易懂能清楚的告知所有人发生了什么情况.能引起人的重视.
info 应该简洁明确让管理员确认状态.
debug 完整详细的记录流程的关键路径.
TRACE
- 在qunar用于业务日志记录.
DEBUG 调试
- 通常,DEBUG 级别应该用于开发人员比较感兴趣的跟踪和调试信息, 生产环境中默认为关闭状态。
INFO
- 通常,INFO 消息被写入到控制台或与之相当的地方。因此,INFO 级别应该只用于相当重要的,对于最终用户和系统管理员有意义的消息。
- 关键系统参数的回显、后台服务的初始化状态、需要系统管理员知会确认的关键信息都需要使用INFO级别
WARN
- 是指示潜在问题的消息级别。
- 通常,WARNING 消息应该描述最终用户或系统管理员感兴趣的事件,或者那些指示潜在问题的事件。
ERROR
- 严重失败, 推荐使用FATAL.
FATAL
- 是指示严重失败的消息级别。
- FATAL 消息应该描述相当重要的事件,这些事件会阻止正常程序的执行。它们对于最终用户和系统管理员来说应该是很容易理解的。
Maven 依赖配置(logback)
<!--Java Commons Logging redirect to slf4j--> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> </dependency> <!--Apache log4j redirect to slf4j--> <dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> </dependency> <!--Java Util Logging redirect to slf4j--> <dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> </dependency> <!--将所有日志归一成 logback--> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency>
系统日志配置
- INFO 及以上的系统日志统一输出到Console
- 异常监控、分类以及rolling目前已经有现成的组件支持, 所以这些系统信息输出到同一个文件里更有利于问题排查。
<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="console"> <encoder> <pattern>[%d{yyyy-MM-dd HH:mm:ss} %5p %c:%L] %m%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="console" /> </root> </configuration>
业务日志配置
- 业务日志(比如订单信息,用户输入的日志等等), 不能采用 className. 请采用模式: datalog.PRODUCTNAME.LOGNAME
//定义,一定不要使用类名, 以datalog开头的pattern datalog.${productName}.${logName} private final static Logger logger = LoggerFactory.getLogger("datalog.hoteltts.orderlog");
- 为避免业务日志错误的被其它捕获器拦截, 业务日志一律使用最低级别的输出 (TRACE).
logger.trace("###############")
- 设置指向
log4j.logger.datalog.hoteltts.orderlog = TRACE,orderlog log4j.appender.orderlog=org.apache.log4j.DailyRollingFileAppender log4j.appender.orderlog.File=${catalina.base}/logs/orderlog.log log4j.appender.orderlog.DatePattern='.'yyyy-MM-dd'.log' log4j.appender.orderlog.Threshold=TRACE log4j.appender.orderlog.layout=org.apache.log4j.PatternLayout log4j.appender.orderlog.layout.ConversionPattern=\[%d{yyyy-MM-dd HH:mm:ss} %m%n
Comments (1)
2014-01-15
严家强 says:
其中一种常用的配置: 日志API使用slf4j <!-- 引入slf4j --> <dependency> <g...其中一种常用的配置:
日志API使用slf4j
日志实现使用logback
日志配置文件使用log4j的格式,需要适配
如果使用第三方组件使用JUC,那么我们需要再加:
这样第三方组件中的日志级别的配置 也就是用了log4j的配置