Java日誌:slf4j與log4j、logback等

日誌使用

在項目中對於日誌的使用,應該爲slf4j+(log4j、logback等)。

slf4j

  • why 爲什麼使用
    《阿里巴巴Java開發手冊》很好的解釋了爲什麼使用slf4j而不是使用某個日誌框架
應用中不可直接使用日誌系統(Log4j、Logback)中的 API,而應依賴使用日誌框架
SLF4J 中的 API,使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式統一。

slf4j只是一個日誌標準,爲各個具體的日誌框架提供統一接口。官網文檔中的解釋:在這裏插入圖片描述
假設系統引入了zookeeper、dubbo而這兩個jar一個使用log4j一個使用logback,我們的系統又使用另一個log框架,此時就帶來了很大的維護成本。所以對於一個健壯、擴展性強的系統而言,最好的方法就是引入一個門面角色,業務系統也就是客戶端角色只調用門面角色的相關日誌接口,而具體的業務由facade交給真正的子系統。

  • how 怎樣使用
    1、引入maven依賴,xxx非真正的版本號
    slf4j必選
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>xxx</version>
  </dependency>

使用log4j、logback(選一個)
log4j

		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-api</artifactId>
			<version>xxx</version>
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<version>xxx</version>
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-slf4j-impl</artifactId>
			<version>xxx</version>
		</dependency>

logback

<dependency> 
	<groupId>ch.qos.logback</groupId> 
	<artifactId>logback-core</artifactId> 
	<version>xxx</version> 
</dependency> 
<dependency> 
    <groupId>ch.qos.logback</groupId> 
    <artifactId>logback-classic</artifactId> 
    <version>xxx</version> 
</dependency>

2、配置文件
log4j2:spring boot默認會加載log4j2-spring.xml(官方推薦)、log4j2.xml兩種命名的配置文件。
配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="debug">
      <AppenderRef ref="Console"/>
    </Root>
  </Loggers>
</Configuration>

logback:logback-spring.xml(官方推薦)、logback.xml。
配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
	  <file>/home/../logs/system.log</file>
	  <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
	          <fileNamePattern>../logs/${application.name}/${application.name}.%d{yyyy-MM-dd}.log</fileNamePattern>
	  </rollingPolicy>
	  <layout class="ch.qos.logback.classic.PatternLayout">
	          <pattern>%-20(%d{HH:mm:ss} [%thread]) %-5level %logger{80} - %msg%n</pattern>
	  </layout>
  </appender>
  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>
        <![CDATA[%date{HH:mm:ss.SSS} %highlight(%-5level) %cyan([%c{0}-%line]) %red([%mdc{requestId:-0}]) - <%m>%n]]></pattern>
    </encoder>
  </appender>
  <logger name="org.springframework.web" level="error"/>
  <logger name="org.springframework.jdbc.core" level="error"/> 
  <root level="error">
  	<appender-ref ref="FILE" />
    <appender-ref ref="console"/>
  </root>
</configuration>

3、日誌打印使用方法

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
調用以上接口獲取Logger對象,使用該對象進行日誌的打印

在這裏插入圖片描述

slf4j源碼

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);

LoggerFactory.getLogger(Abc.class)的源碼如下:

public static Logger getLogger(Class<?> clazz) {
	 // 再調用getLogger(String)方法
        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;
    }
    public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

getILoggerFactory方法
a、未進行初始化時,調用performInitialization()方法

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");
    }

最後會調用bind()方法

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()) {
            	// 關鍵是此findPossibleStaticLoggerBinderPathSet方法
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            reportActualBinding(staticLoggerBinderPathSet);
            fixSubstituteLoggers();
            replayEvents();
            // release all resources in SUBST_FACTORY
            SUBST_FACTORY.clear();
        } catch (NoClassDefFoundError ncde) {
            String msg = ncde.getMessage();
            if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
                Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
            } else {
                failedBinding(ncde);
                throw ncde;
            }
        } catch (java.lang.NoSuchMethodError nsme) {
            String msg = nsme.getMessage();
            if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
                INITIALIZATION_STATE = FAILED_INITIALIZATION;
                Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
                Util.report("Your binding is version 1.5.5 or earlier.");
                Util.report("Upgrade your binding to version 1.6.x.");
            }
            throw nsme;
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }

findPossibleStaticLoggerBinderPathSet方法重點是通過STATIC_LOGGER_BINDER_PATH加載資源,也就是在logback、log4j2或者其他框架中必須存在該接口

STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"
static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        // use Set instead of list in order to deal with bug #138
        // LinkedHashSet appropriate here because it preserves insertion order
        // during iteration
        Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
        try {
            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration<URL> paths;
            if (loggerFactoryClassLoader == null) {
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }
            while (paths.hasMoreElements()) {
                URL path = paths.nextElement();
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException ioe) {
            Util.report("Error getting resources from path", ioe);
        }
        return staticLoggerBinderPathSet;
    }

再來看接下來的StaticLoggerBinder.getSingleton()調用。
虛擬機類加載對初始化的要求之一:當調用靜態方法時如果類未進行過初始化則觸發初始化。初始化時調用< clinit>(若存在則調用)方法,靜態字段賦值、執行靜態代碼塊。以logback的實現爲例:
執行順序爲1-》2》3,說明在調用getSingleton()方法之前已經完成了一系列配置加載等操作

1
private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();

static {
   3
    SINGLETON.init();
}
2
private StaticLoggerBinder() {
    defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
}

public static StaticLoggerBinder getSingleton() {
    return SINGLETON;
}

b、當初始化成功後,則執行getILoggerFactory的StaticLoggerBinder.getSingleton().getLoggerFactory();方法獲取ILoggerFactory的實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章