天天看點

1、LogBack入門案例入門案例 源碼解讀

Logback是由log4j創始人設計的另一個開源日志元件,性能比log4j要好。

官方網站:https://logback.qos.ch/index.html

Logback主要分為三個子產品:

  • logback-core:其它兩個子產品的基礎子產品
  • logback-classic:它是log4j的一個改良版本,同時它完整實作了slf4j API
  • logback-access:通路子產品與Servlet容器內建提供通過Http來通路日志的功能 後續的日志代碼都是通過SLF4J日志門面搭建日志系統,是以在代碼是沒有差別,主要是通過修改配置檔案和pom.xml依賴
項目中無需單獨引入slf4j,因為logback-classic依賴包裡引用了slf4j
1、LogBack入門案例入門案例 源碼解讀

入門案例

第一步:建立maven項目,添加依賴

<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/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.example</groupId>
	<artifactId>logging-test</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<maven.compiler.source>8</maven.compiler.source>
		<maven.compiler.target>8</maven.compiler.target>
	</properties>

	<dependencies>
		<dependency>
			<artifactId>junit</artifactId>
			<groupId>junit</groupId>
			<version>4.12</version>
		</dependency>

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

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.8.1</version>
				<configuration>
					<source>${maven.compiler.source}</source>
					<target>${maven.compiler.target}</target>
					<encoding>UTF-8</encoding>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>
           

第二步:測試代碼

package com.example.test;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestLogback {

	private static final Logger logger = LoggerFactory.getLogger(TestLogback.class);

	@Test
	public void testLogback() {
		logger.error("error");
		logger.warn("warn");
		logger.info("info");
		logger.debug("debug");
		logger.trace("trace");
	}
}

           
1、LogBack入門案例入門案例 源碼解讀

源碼解讀

1、我們從日志工廠的常見看起,這裡是slf4j的實作

核心方法隻有一句

public static Logger getLogger(Class<?> clazz) {
        Logger logger = getLogger(clazz.getName());
       	....省略
        return logger;
    }
           

看一下getLogger方法,這裡是先擷取日志工廠,在從工廠中提取日志對象,我們不考慮日志對象,主要看看日志工廠的環境怎麼初始化的

public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }
           

進入getILoggerFactory方法看:

public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == 0) {
            Class var0 = LoggerFactory.class;
            synchronized(LoggerFactory.class) {
                if (INITIALIZATION_STATE == 0) {
                    INITIALIZATION_STATE = 1;
                    //重要方法
                    performInitialization();
                }
            }
        }
        ...省略
    }
           

進入performInitialization方法

private static final void performInitialization() {
		//一看bind就知道slf4j綁定logback的重要方法
        bind();
        if (INITIALIZATION_STATE == 3) {
            versionSanityCheck();
        }

    }
           

進入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()) {
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
            //這一行做綁定操作,重要
            StaticLoggerBinder.getSingleton();
            ....省略
    }
           

進入getSingleton方法,此時已經進入橋接包的StaticLoggerBinder類了,log4j橋接包和jul橋接包中都有這個StaticLoggerBinder類:

public static StaticLoggerBinder getSingleton() {
        return SINGLETON;
    }
           

我們翻下可以看到它有個靜态代碼塊:

static {
        SINGLETON.init();
    }
           

進入init方法

void init() {
        try {
            try {
            	//感覺autoConfig是重要方法
                (new ContextInitializer(this.defaultLoggerContext)).autoConfig();
            ...省略

    }
           

進入autoConfig方法

public void autoConfig() throws JoranException {
        StatusListenerConfigHelper.installIfAsked(this.loggerContext);
        //生成URL通過預設配置檔案
        URL url = this.findURLOfDefaultConfigurationFile(true);
        if (url != null) {
            this.configureByResource(url);
        } else {
        	//spi機制擷取配置類
            Configurator c = (Configurator)EnvUtil.loadFromServiceLoader(Configurator.class);
            if (c != null) {
                try {
                    c.setContext(this.loggerContext);
                    c.configure(this.loggerContext);
                } catch (Exception var4) {
                    throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass().getCanonicalName() : "null"), var4);
                }
            } else {
            	//當沒有配置檔案時,預設的配置
                BasicConfigurator basicConfigurator = new BasicConfigurator();
                basicConfigurator.setContext(this.loggerContext);
                basicConfigurator.configure(this.loggerContext);
            }
        }

    }
           

我們可以看下findConfigFileURLFromSystemProperties方法:

public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
        ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
        URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
        if (url != null) {
            return url;
        }

        url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
        if (url != null) {
            return url;
        }
    
        return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
    }
           
1、LogBack入門案例入門案例 源碼解讀
1、LogBack入門案例入門案例 源碼解讀

我們發現,它讀取配置檔案有優先級,先讀取classpath下是否有logback-test.xml,沒有則讀取classpath下是否有logback.xml

debug發現Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);傳回為null。

然後進入到BasicConfigurator,然後發現basicConfigurator.configure(loggerContext);比較重要

public void configure(LoggerContext lc) {
        addInfo("Setting up default configuration.");
        
        ConsoleAppender<ILoggingEvent> ca = new ConsoleAppender<ILoggingEvent>();
        ca.setContext(lc);
        ca.setName("console");
        LayoutWrappingEncoder<ILoggingEvent> encoder = new LayoutWrappingEncoder<ILoggingEvent>();
        encoder.setContext(lc);
        
 
        // same as 
        // PatternLayout layout = new PatternLayout();
        // layout.setPattern("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n");
        TTLLLayout layout = new TTLLLayout();
 
        layout.setContext(lc);
        layout.start();
        encoder.setLayout(layout);
        
        ca.setEncoder(encoder);
        ca.start();
        
        Logger rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
        rootLogger.addAppender(ca);
    }
           

結果一看,rootLogger指向appender,appender指向encoder,encoder指向layout,比log4j多了encoder,但大緻一樣

我們在看TTLLLayout類

package ch.qos.logback.classic.layout;

import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.LayoutBase;
import ch.qos.logback.core.util.CachingDateFormatter;

/**
 * A layout with a fixed format. The output is equivalent to that produced by {@link ch.qos.logback.classic.PatternLayout PatternLayout} with the pattern:</p>
 * 
 * <pre>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pre>
 * 
 *<p>TTLLLayout has the advantage of faster load time whereas {@link ch.qos.logback.classic.PatternLayout PatternLayout}
 * requires roughly 40 milliseconds to load its parser classes.  Note that the second run of PatternLayout will be much much faster (approx. 10 micro-seconds).</p>
 * 
 * <p>Fixed format layouts such as TTLLLayout should be considered as an alternative to PatternLayout only if the extra 40 milliseconds at application start-up is considered significant.</p>
 * 
 * @author Ceki G&uuml;lc&uuml;
 * @since 1.1.6
 */
public class TTLLLayout extends LayoutBase<ILoggingEvent> {

    CachingDateFormatter cachingDateFormatter = new CachingDateFormatter("HH:mm:ss.SSS");
    ThrowableProxyConverter tpc = new ThrowableProxyConverter();

    @Override
    public void start() {
        tpc.start();
        super.start();
    }

    @Override
    public String doLayout(ILoggingEvent event) {
        if (!isStarted()) {
            return CoreConstants.EMPTY_STRING;
        }
        StringBuilder sb = new StringBuilder();

        long timestamp = event.getTimeStamp();

        sb.append(cachingDateFormatter.format(timestamp));
        sb.append(" [");
        sb.append(event.getThreadName());
        sb.append("] ");
        sb.append(event.getLevel().toString());
        sb.append(" ");
        sb.append(event.getLoggerName());
        sb.append(" - ");
        sb.append(event.getFormattedMessage());
        sb.append(CoreConstants.LINE_SEPARATOR);
        IThrowableProxy tp = event.getThrowableProxy();
        if (tp != null) {
            String stackTrace = tpc.convert(event);
            sb.append(stackTrace);
        }
        return sb.toString();
    }

}

           

發現doLayout做的操作跟列印的出來格式一模一樣的