项目开发时,日志系统是必不可少的东西,不管是开发过程中的调试还是生产环境中的问题跟踪,都离不开日志。在日常的工作中,接触过了Commons logging,JDK logging,Log4J,slf4J还有logback,大概知道怎么用,但对很多细节都不太了解,下面系统地整理一遍。
日志接口与日志实现
首先要明确的是,Apache的commons logging和另外一个slf4j是日志控件,是日志接口,本身并没有具体的日志功能,它们需要和日志实现框架一起使用。
当然,实现了具体日志功能的日志框架也能单独使用,也就是说,JDK logging,Log4J和logback都能单独使用,但是这样就会造成在一个项目中使用了多个日志实现框架时,需要维护和管理不同的日志系统,非常麻烦。比如,不同的日志框架打印格式和日志级别不同,配置文件也不同。而日志接口框架起到这样一个作用:它可以将不同的日志实现框架整合起来使用。例如,你可以在项目中使用log4j,也可以使用logback,最终都通过slf4j表现出来,通过一个配置文件去控制日志打印格式和日志级别。
除了这两个日志接口控件之外,其他的都是能实现具体日志功能的日志系统。
常用的组合有:
- Commons logging + JDK logging
- Commons logging + Log4J
- slf4J + Log4J
- slf4J + logback。
在最简单的小系统中,可能只需要Commons logging + JDK logging就可以应对了。而在大系统中,具体的日志实现经常用到的是log4j和logback。
在早期大部分系统都使用的是Commons logging + Log4J,后来slf4J + Log4J使用较多,再然后则slf4J + logback用得比较多。
Commons logging + JDK logging/Log4J
先说Commons logging 与JDK logging/ Log4J的组合。
Commons logging使用时要引入commons-logging包,然后根据引入的具体的日志系统的包来选择具体的日志实现。只要在项目中添加了log4j的包,commons-logging就会自动选择log4j进行日志输出。
在进行日志输出时可以通过log4j.properties或log4j.xml进行日志系统配置,控制日志输出位置和输出级别等。
如果没有引入log4j的包,commons-logging就会使用JDK自带的logging实现日志功能。下面举几个简单的例子:
不引入log4j的包,默认使用JDK的日志输出功能:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
public class LogTest {
Log logger = LogFactory.getLog(LogTest.class);
@Test
public void test1(){
System.out.println("test1");
logger.debug("debug");
logger.debug("info");
logger.debug("error");
}
}
当然只能输出info级别以上的信息,因为其默认级别就是info。
引入log4j的包,不提供log4j.properties配置文件。控制台输出:
log4j:WARN No appenders could be found for logger (cn.tonghao.component.LogTest).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
添加log4j.properties配置文件:
log4j.rootLogger=DEBUG,console
#输出到控制台
log4j.appender.console=org.apache.log4j.ConsoleAppender
#编码格式
log4j.appender.console.encoding=UTF-8
#设置输出样式
log4j.appender.console.layout=org.apache.log4j.PatternLayout
#日志输出信息格式为
log4j.appender.console.layout.ConversionPattern=[%-d{yyyy-MM-dd HH:mm:ss}]-[%t-%5p]-[%C-%M(%L)]: %m%n
后正常输出日志信息。
这里需要说明一点:当log4j.properties放在类路径下时,LoggerFactory会自动去加载该配置文件。如果放在其他地方,则需要在web.xml中配置监听器,如下:
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:log4j.properties</param-value>
</context-param>
<!--检测日志配置 文件变化-->
<context-param>
<param-name>log4jRefreshInterval</param-name>
<param-value>60000</param-value>
</context-param>
<!--配置监听器-->
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
注意,检测日志配置文件变化的参数作用是在修改log4j.properties之后配置文件动态生效,生效时间就是配置的检测时间。
slf4j + Log4j/logback
然后说说slf4j。
Slf4j除了可以整合不同的日志实现组件外,还提供占位符,即可以用{}来代替变量,避免设置打印消息时进行字符串拼接。如logger.info(“xxx,{}”,a)打印出来就是xxx,a。
slf4j提供各种抽象接口,日志应该基于slf4j的API进行日志打印,这样无论迁移到那个项目,只需要配一个实现类log4j or logback,都能正常打印日志。所以在选择日志框架的时候,一般选用slf4j,当然,要选择一个具体实现的日志框架。slf4j的实现类不能有多个,不然会产生冲突。实现类用log4j还是logback好呢?其实这两个日志实现框架的作者是同一个人,logback是对log4j的升级改进。在大多数情况下,logback的性能都优于log4j。
要使用slf4j+log4j,需要引入几个包,分别是:slf4j-api,log4j的jar包,和两者绑定jar包slf4j-log4j12。Logback则需要slf4j-api,logback-core和logback-classic。如果要使用slf4j+logback,而项目中又有其他地方直接使用log4j的怎么办呢?可以加入log4j-over-slf4j,把旧的日志log4j适配到slf4j,这时候,再使用logback就可以了。同样,也可以通过引入jcl-over-slf4j来适配commons-logging系统。如:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.1.4.GA</version>
</dependency>
使用logback时,如果没有配置logback.xml,则系统默认打印DEBUG级别日志。系统启动的时候会自动扫描classpath路径下的logback.xml和logback-test.xml,如果找到就使用配置文件的配置。下面是一个logback.xml配置模板:
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="/home" />
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_JOME}/testFile.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 日志输出级别 -->
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
上面说过,系统启动时自动扫描类路径,按logback-groovy,logback-test,logback的顺序就行查找,找到了就使用其作为配置文件。但如果想使用自己指定的配置文件怎么办呢?方法是:根目录下保留一个最简单的logback.xml,然后在spring配置文件如spring-log.xml中配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="loggingInitialization"
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass"
value="cn.tonghao.environment.LogConfigurerSupportMultiEnvironment"/>
<property name="targetMethod" value="registLogConfiguration"/>
<property name="arguments">
<list>
<value>classpath:config/log/${logback.name}.xml</value>
</list>
</property>
</bean>
</beans>
通过LogConfigurerSupportMultiEnvironment类重新初始化指定的logback配置文件。
public class LogConfigurerSupportMultiEnvironment {
public static void registLogConfiguration(String logConfigLocation) throws FileNotFoundException, JoranException {
String resolvedLocation = SystemPropertyUtils.resolvePlaceholders(logConfigLocation);
if (resolvedLocation.toLowerCase().endsWith(".xml")) {
URL url = ResourceUtils.getURL(resolvedLocation);
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.reset();
JoranConfigurator joranConfigurator = new JoranConfigurator();
joranConfigurator.setContext(loggerContext);
joranConfigurator.doConfigure(url);
}
}
}
比如重新初始化logback-dev.xml(其中tomcat_base_path为tomcat配置文件中的变量):
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<property name = "path_bath" value="/home"/>
<!-- 控制台输出日志 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n</pattern>
</encoder>
</appender>
<!-- 文件输出日志 (文件大小策略进行文件输出,超过指定大小对文件备份) -->
<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${path_bath}/${tomcat_app_base}/ly.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>
${path_bath}/${tomcat_app_base}/ly.%d{yyyy-MM-dd}-%i.log
</FileNamePattern>
<maxHistory>30</maxHistory>
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{32} - %msg%n</Pattern>
</encoder>
</appender>
<!--业务监控日志-->
<appender name="BUS"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${path_bath}/${tomcat_app_base}/lyBus.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>
${path_bath}/${tomcat_app_base}/lyBus.%d{yyyy-MM-dd}-%i.log
</FileNamePattern>
<maxHistory>30</maxHistory>
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{32} - %msg%n</Pattern>
</encoder>
</appender>
<logger name="lyBusLog" level="INFO" additivity="false">
<appender-ref ref="BUS"/>
</logger>
<root level="INFO">
<appender-ref ref="FILE"/>
<appender-ref ref="STDOUT"/>
</root>
</configuration>
参考文档:
[1]. http://blog.youkuaiyun.com/tianlincao/article/details/6101630
[2]. http://www.cnblogs.com/huayu0815/p/5341712.html