一、logback介紹
Logback是由log4j創始人設計的一個開源日誌組件。LogBack被分爲3個組件,logback-core, logback-classic 和 logback-access。
1. logback-core:提供了LogBack的核心功能,是另外兩個組件的基礎。
2. logback-classic:實現了Slf4j的API,所以當想配合Slf4j使用時,需要引入logback-classic。
3. logback-access:爲了集成Servlet環境而準備的,可提供HTTP-access的日誌接口。
Logback是要與SLF4J結合起來。這兩個組件的官方網站如下:
logback官方網站: logback官方網站
SLF4J官方網站: SLF4J官方網站
- Slf4j:簡單日誌門面(Simple Logging Facade for Java),不是具體的日誌解決方案,它只服務於各種各樣的日誌系統。
- 在使用SLF4J的時候,不需要在代碼中或配置文件中指定你打算使用那個具體的日誌系統。
二、slf4j + logback是如何綁定的
1. private static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);
2. 查看LoggerFactory.getLogger()方法
public static Logger getLogger(Class<?> clazz) {
// 獲取Logger對象
Logger logger = getLogger(clazz.getName());
if (DETECT_LOGGER_NAME_MISMATCH) {
Class<?> autoComputedCallingClass = Util.getCallingClass();
if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
autoComputedCallingClass.getName()));
Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
}
}
return logger;
}
3. 繼續跟進 getLogger()
/**
* Return a logger named according to the name parameter using the
* statically bound {@link ILoggerFactory} instance.
*
* @param name
* The name of the logger.
* @return logger
*/
public static Logger getLogger(String name) {
// 獲取日誌工廠
ILoggerFactory iLoggerFactory = getILoggerFactory();
// 返回日誌對象
return iLoggerFactory.getLogger(name);
}
4. 獲取工廠實例
/**
* Return the {@link ILoggerFactory} instance in use.
* <p/>
* <p/>
* ILoggerFactory instance is bound with this class at compile time.
* 編譯時綁定工廠實例
*
* @return the ILoggerFactory instance in use
*/
public static ILoggerFactory getILoggerFactory() {
// 沒有初始化情況
// 雙重檢測鎖
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
// 初始化
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
5. 初始化
private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
6. 綁定
private final static void bind() {
try {
Set<URL> staticLoggerBinderPathSet = null;
// skip check under android, see also
// http://jira.qos.ch/browse/SLF4J-328
if (!isAndroid()) {
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
// 真正的綁定,將具體的實現綁定到slf4j
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
fixSubstituteLoggers();
replayEvents();
// release all resources in SUBST_FACTORY
SUBST_FACTORY.clear();
}
}
7. logback-classic: org.slf4j.impl.StaticLoggerBinder
public static StaticLoggerBinder getSingleton() {
return SINGLETON;
}
由此可以看出slf4j在編譯時就找了具體的日誌實現了,也就是 org.slf4j.impl.StaticLoggerBinder。
三、logback對配置文件的加載
1. getSingleton()方法獲取logback實例對象,說明在對象之前已經加載了相關的配置文件,跟進 StaticLoggerBinder
static {
// 初始化
SINGLETON.init();
}
private boolean initialized = false;
private LoggerContext defaultLoggerContext = new LoggerContext();
private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton();
private StaticLoggerBinder() {
defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
}
public static StaticLoggerBinder getSingleton() {
return SINGLETON;
}
2. 查看 init()
/**
* Package access for testing purposes.
*/
void init() {
try {
try {
// 上下文初始化環境
new ContextInitializer(defaultLoggerContext).autoConfig();
} catch (JoranException je) {
Util.report("Failed to auto configure default logger context", je);
}
// logback-292
if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
}
contextSelectorBinder.init(defaultLoggerContext, KEY);
initialized = true;
} catch (Throwable t) {
// we should never get here
Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
}
}
3. 跟進autoConfig()
public void autoConfig() throws JoranException {
StatusListenerConfigHelper.installIfAsked(loggerContext);
// 尋找默認配置文件
URL url = findURLOfDefaultConfigurationFile(true);
if (url != null) {
configureByResource(url);
} else {
Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);
if (c != null) {
try {
c.setContext(loggerContext);
c.configure(loggerContext);
} catch (Exception e) {
throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass()
.getCanonicalName() : "null"), e);
}
} else {
// 沒有找到配置文件,則使用默認的配置器,那麼日誌只會打印在控制檯
BasicConfigurator basicConfigurator = new BasicConfigurator();
basicConfigurator.setContext(loggerContext);
basicConfigurator.configure(loggerContext);
}
}
}
4. findURLOfDefaultConfigurationFile() logback配置文件加載規則
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
// 獲取當前實例的類加載器,目的是在classpath下尋找配置文件
ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
// 先找logback.configurationFile文件
URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
if (url != null) {
return url;
}
// logback.configurationFile文件沒找到,再找logback.groovy
url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
// logback.groovy沒找到,再找logback-test.xml
url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
// logback-test.xml沒找到,最後找logback.xml
return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
}
小結:
編譯期間,完成slf4j的綁定已經logback配置文件的加載。slf4j會在classpath中尋找org/slf4j/impl/StaticLoggerBinder.class(會在具體的日誌框架如log4j、logback等中存在),找到並完成綁定;同時,logback也會在classpath中尋找配置文件,先找logback.configurationFile、沒有則找logback.groovy,若logback.groovy也沒有,則找logback-test.xml,若logback-test.xml還是沒有,則找logback.xml,若連logback.xml也沒有,那麼說明沒有配置logback的配置文件,那麼logback則會啓用默認的配置(日誌信息只會打印在控制檯)。
四、使用步驟
1.引入slf4j、logback相關依賴
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- logback -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>${logback.version}</version>
</dependency>
2.添加配置文件logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 定義日誌文件的存儲地址 -->
<!--
關於catalina.base解釋如下:
catalina.home指向公用信息的位置,就是bin和lib的父目錄。
catalina.base指向每個Tomcat目錄私有信息的位置,就是conf、logs、temp、webapps和work的父目錄。
-->
<property name="LOG_DIR" value="${catalina.base}/logs"/>
<!--
%p:輸出優先級,即DEBUG,INFO,WARN,ERROR,FATAL
%r:輸出自應用啓動到輸出該日誌訊息所耗費的毫秒數
%t:輸出產生該日誌事件的線程名
%f:輸出日誌訊息所屬的類別的類別名
%c:輸出日誌訊息所屬的類的全名
%d:輸出日誌時間點的日期或時間,指定格式的方式: %d{yyyy-MM-dd HH:mm:ss}
%l:輸出日誌事件的發生位置,即輸出日誌訊息的語句在他所在類別的第幾行。
%m:輸出代碼中指定的訊息,如log(message)中的message
%n:輸出一個換行符號
-->
<!--格式化輸出:%d表示日期,%thread表示線程名,%-5level:級別從左顯示5個字符寬度 %msg:日誌消息,%n是換行符-->
<property name="pattern" value="%d{yyyyMMdd:HH:mm:ss.SSS} [%thread] %-5level %msg%n"/>
<!--
Appender: 設置日誌信息的去向,常用的有以下幾個
ch.qos.logback.core.ConsoleAppender (控制檯)
ch.qos.logback.core.rolling.RollingFileAppender (文件大小到達指定尺寸的時候產生一個新文件)
ch.qos.logback.core.FileAppender (文件)
-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- 字符串System.out(默認)或者System.err -->
<target>System.out</target>
<!-- 對記錄事件進行格式化 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<appender name="SQL_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 被寫入的文件名,可以是相對目錄,也可以是絕對目錄,如果上級目錄不存在會自動創建 -->
<file>${LOG_DIR}/sql_info.log</file>
<!-- 當發生滾動時,決定RollingFileAppender的行爲,涉及文件移動和重命名。屬性class定義具體的滾動策略類 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 必要節點,包含文件名及"%d"轉換符,"%d"可以包含一個java.text.SimpleDateFormat指定的時間格式,默認格式是 yyyy-MM-dd -->
<fileNamePattern>${LOG_DIR}/sql_info_%d{yyyy-MM-dd}.log.%i.gz</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>20MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 可選節點,控制保留的歸檔文件的最大數量,超出數量就刪除舊文件。假設設置每個月滾動,如果是6,則只保存最近6個月的文件,刪除之前的舊文件 -->
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<!-- LevelFilter: 級別過濾器,根據日誌級別進行過濾 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<!-- 用於配置符合過濾條件的操作 ACCEPT:日誌會被立即處理,不再經過剩餘過濾器 -->
<onMatch>ACCEPT</onMatch>
<!-- 用於配置不符合過濾條件的操作 DENY:日誌將立即被拋棄不再經過其他過濾器 -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="SQL_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/sql_error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/sql_error_%d{yyyy-MM-dd}.log.%i.gz</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>20MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="APP_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_DIR}/info.%d{yyyy-MM-dd}.log
</FileNamePattern>
</rollingPolicy>
<encoder>
<Pattern>[%date{yyyy-MM-dd HH:mm:ss}] [%-5level] [%thread] [%logger:%line]--%mdc{client} %msg%n</Pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<property name="pattern" value="%d{yyyyMMdd:HH:mm:ss.SSS} [%thread] %-5level %msg%n"/>
<pattern>%d{yyyyMMdd:HH:mm:ss.SSS}%thread%-5level%F{32}%M%L%msg</pattern>
</layout>
</encoder>
<file>${LOG_DIR}/test.html</file>
</appender>
<!--
用來設置某一個包或者具體的某一個類的日誌打印級別、以及指定<appender>。
<loger>僅有一個name屬性,一個可選的level和一個可選的addtivity屬性
name:
用來指定受此logger約束的某一個包或者具體的某一個類。
level:
用來設置打印級別,大小寫無關:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
如果未設置此屬性,那麼當前logger將會繼承上級的級別。
additivity:
是否向上級loger傳遞打印信息。默認是true。
<logger>可以包含零個或多個<appender-ref>元素,標識這個appender將會添加到這個logger
-->
<logger name="java.sql" level="info" additivity="false">
<level value="info" />
<appender-ref ref="STDOUT"></appender-ref>
<appender-ref ref="SQL_INFO"></appender-ref>
<appender-ref ref="SQL_ERROR"></appender-ref>
</logger>
<logger name="com.souche.LogbackTest" additivity="false">
<level value="info" />
<appender-ref ref="STDOUT" />
<appender-ref ref="APP_INFO" />
<appender-ref ref="FILE"/>
</logger>
<!--
也是<logger>元素,但是它是根logger。默認debug
level:用來設置打印級別,大小寫無關:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
<root>可以包含零個或多個<appender-ref>元素,標識這個appender將會添加到這個logger。
-->
<root level="info">
<level>info</level>
<appender-ref ref="STDOUT"/>
<appender-ref ref="SQL_INFO"/>
<appender-ref ref="SQL_ERROR"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
五、實際應用
就舉例最近新做的項目彈個X中的應用吧。因爲在與前端聯調階段,api自測感覺沒啥問題的,然後聯調就會有各種問題,沒法避免,技術還是太水了,哈哈哈哈........
調詳情頁的時候,聽到有問題就趕緊看日誌去,果然報錯了。如下:
錯誤日誌
有錯誤信息就能當做本地控制檯一樣,可以直接看到錯誤信息。
Cause: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'saler_phone' at row 1
; SQL []; Data truncation: Data too long for column 'saler_phone' at row 1; nested exception is com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'saler_phone' at row 1
然後進一步加了log日誌
log日誌
傳參錯了,查了原因,登錄失效,然後salerPhone居然還有值,而且多了一位......
六、爲什麼使用logback
logback具有以下優點:
- 內核重寫、測試充分、初始化內存加載更小,這一切讓logback性能和log4j相比有諸多倍的提升
- logback非常自然地直接實現了slf4j,這個嚴格來說算不上優點,只是這樣,再理解slf4j的前提下會很容易理解logback,也同時很容易用其他日誌框架替換logbac
- logback有比較齊全的200多頁的文檔
- logback當配置文件修改了,支持自動重新加載配置文件,掃描過程快且安全,它並不需要另外創建一個掃描線程
- 支持自動去除舊的日誌文件,可以控制已經產生日誌文件的最大數量
總而言之,如果大家的項目裏面需要選擇一個日誌框架,那麼我個人非常建議使用logback。
七、logback性能大比拼
log4j、logback、log4j2性能測試,直接引用公司的博客吧。logback log4j log4j2 性能實測
參考鏈接
- Java日誌框架-logback的介紹及配置使用方法
- 從源碼來理解slf4j的綁定,以及logback對配置文件的加載
- logback 配置詳解和使用
- logback.xml常用配置詳解 : filter
- Java 日誌框架:logback 詳解
- logback log4j log4j2 性能實測
作者:MrL槑槑
鏈接:https://www.jianshu.com/p/b3dedb8fb61e
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。