java程式日誌管理
初入軟體開發這一行的人,可能對日誌管理的概念並不是很明確,大概是由於經驗所限,以至於根本還考慮不到這個問題。
而從某種意義上來說,日誌管理實際上也不需要初入這一行的人來管,他們只需要負責實現自己的主要業務邏輯和功能就好了。
我當初剛入行的時候就有很長一段時間完全不用去關心日誌,到後來偶爾涉及到的時候,也都是從其他地方採用cv大法直接搬用。
不過,隨著工作時間的變化,隨著手頭上任務重要程度的變化,也隨著接觸到的專案數量的變化,讓我越來越意識到日誌的重要性,它在整個系統中發揮著至關重要的作用!
尤其是涉及到需要後期維護的專案,更是經常需要依靠日誌來定位問題,可以說他是執行中的專案出問題時,找問題最好的手段。
java中日誌管理的技術有很多,像java自身的java.util.logging,apache的commons-logging,以及slf4j、log4j、logback等等。
其中java.util.logging在日常開發中用的不是很多,用的比較多的後邊四個,commons-logging和slf4j是介面,log4j和logback是具體的實現,在我所接觸的專案中就用到了這幾個。
因為java推薦的就是面向介面程式設計,所以一般推薦使用的就是那兩個介面,但是又由於commons-logging的動態繫結造成了一些問題,因此這兩個裡邊又推薦使用slf4j。
同樣的,在兩種實現中,logback和log4j是由同一個作者開發,logback出現的更晚,更好,因為也就更推薦用logback。
那麼綜上而言,目前最推薦的java中的日誌管理,就是使用slf4j+logback。
實際上,說了這麼多,真正用起來是很簡單的,只需要匯入相關jar包,寫好相關配置,然後需要的地方呼叫就好了,學習的過程中為了比較不同,我也寫了一個簡單的額例子。
因為目前大部分的專案都是maven管理,spring框架,所以這個例子中也算是順便聯絡spring的最基礎配置,就也用了spring。
maven的導包配置pom.xml如下,為了比較這四項技術,所以相關的包我全都導了進來,commons-logging是其他jar依賴的,所以便沒有手動再導一次:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>logTest</groupId>
<artifactId>logTest</artifactId >
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>logTest Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId >
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.12</version>
</dependency>
</dependencies>
<build>
<finalName>logTest</finalName>
</build>
</project>
然後寫了簡單的spring.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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd" >
<!-- 引入屬性檔案 -->
<!-- -->
<context:property-placeholder location="classpath:log4j.properties" ignore-unresolvable="true" />
<!-- 配置spring註解掃描 -->
<context:component-scan base-package="logService.service.impl" />
</beans>
很簡單,就是配置引入配置檔案和spring裝配掃描路徑,然後是兩個不同的日誌配置檔案,從命名就很容易知道哪個對應的是哪個,首先是log4j.properties:
`log4j.properties:
log4j.rootLogger=WARN, CONSOLE, FILE
## for console
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{MM-ddHH:mm:ss}[%c-%L][%t][%-4r] - %m%n
log4j.appender.CONSOLE.Encoding=utf-8
## for file
log4j.appender.FILE=org.apache.log4j.RollingFileAppender
log4j.appender.FILE.File=C:/Users/Think/Desktop/log4j.log
log4j.appender.FILE.MaxFileSize=1MB
log4j.appender.FILE.Append = true
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=%d{yyyy-MM-ddHH\:mm\:ss} [%t] %-5p %-4r %x - %m%n
log4j.appender.FILE.Encoding=utf-8`
然後是logback.xml:
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定義日誌檔案的儲存地址 勿在 LogBack 的配置中使用相對路徑-->
<property name="LOG_HOME" value="C:/Users/Think/Desktop" />
<!-- 控制檯輸出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化輸出:%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">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日誌檔案輸出的檔名-->
<FileNamePattern>${LOG_HOME}/logback.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日誌檔案保留天數-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化輸出:%d表示日期,%thread表示執行緒名,%-5level:級別從左顯示5個字元寬度%msg:日誌訊息,%n是換行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日誌檔案最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 日誌輸出級別 -->
<root level="WARN">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE"/>
</root>
</configuration>
這些配置基本上都是最最基礎的配置,具體的代表什麼意思,網上也有很多很多的說明。
然後分別寫了兩個使用了common-logging和slf4j的介面:
package logService.service;
/**
* 使用common-logger
*
* @author tuzongxun
* @date 2017年2月20日 下午3:36:19
*/
public interface CommonLogService {
public void printLog(String msg);
}
package logService.service;
/***
* Slf4j日誌列印
*
* @author tuzongxun
*
* @date 2017年2月20日 下午3:39:11
*/
public interface Slf4jLogService {
public void printLog(String msg);
}
還有對應的實現類,實現類實際上什麼都沒做,就是呼叫日誌介面隨便列印一條日誌而已:
package logService.service.impl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
import logService.service.CommonLogService;
/**
* 使用commons-logger和log4j列印日誌
*
* @author tuzongxun
* @date 2017年2月20日 下午3:36:33
*/
@Component
public class CommonLogServiceImp implements CommonLogService {
private Log logger = LogFactory.getLog(CommonLogServiceImp.class);
public void printLog(String msg) {
logger.warn("Commonlogger日誌列印:" + msg);
}
}
package logService.service.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import logService.service.Slf4jLogService;
/***
* Slf4j日誌列印
*
* @author tuzongxun
* @date 2017年2月20日 下午3:38:55
*/
@Component
public class Slf4jLogServiceImpl implements Slf4jLogService {
private Logger log = LoggerFactory.getLogger(Slf4jLogServiceImpl.class);
@Override
public void printLog(String msg) {
log.warn("slf4j日誌列印:" + msg);
}
}
從上邊的程式碼中,實際上根本看不出什麼問題,只看到呼叫了兩個介面而已,方法幾乎都是一模一樣,至於具體用了哪個實現,有什麼區別呢,完全不知道,所以我寫了對應的test類:
package logTest;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import logService.service.CommonLogService;
import logService.service.Slf4jLogService;
/**
* 日誌管理測試
*
* @author tuzongxun
* @date 2017年2月22日 下午3:40:17
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class LogTest {
@Autowired
private CommonLogService commonLogService;
@Autowired
private Slf4jLogService slf4jLogService;
@Test
public void commonLogTest() {
commonLogService.printLog("commonlog日誌列印");
}
@Test
public void slf4jLogTest() {
slf4jLogService.printLog("slf4j日誌列印");
}
}
那麼現在有了程式碼,我就可以說一說區別了,實際上一開始我說的pom.xml並不是一次匯入的,可能有的專案中只有其中幾個,而有的專案中我剛才匯入的jar包他們也全都匯入了。
經過我的測試發現,當使用common-logging的時候,是隻能使用log4j的,如果去掉log4j的jar包,那麼結果就是執行junit後沒有生成對應的日誌檔案。
而如果用slf4j就可以使用兩個實現,只不過和common-logging不同的是,使用slf4j時除開log4j的包,還需要slf4j連線log4j的包。
使用slf4j和logback要匯入logback的包自然就不必說了,但是同時到如log4j和logback的包就導致了另一個問題存在,就是使用slf4j的時候不僅會用log4j,還會用logback,導致結果每次都會有兩份日誌檔案。
因此呢,在這種情況下就需要匯入另一個包,也就是我匯入的
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.12</version>
</dependency>