Log4j2的應用和原理

系列

簡介

?Log4j2是apache在Log4j的基礎上,參考logback架構實現(xiàn)的一套新的日志系統(tǒng)。本文基于Log4j-2.12.1的版本進行源碼分析。
?本文的目的在于梳理Log4j2的Logger對象生成過程,借此理順Log4j2各組件之間的聯(lián)系。

Log4j2的用法

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug" strict="true">

    <Properties>
        <Property name="filename">target/test.log</Property>
    </Properties>
    <Filter type="ThresholdFilter" level="trace"/>

    <Appenders>
        <Appender type="Console" name="STDOUT">
            <Layout type="PatternLayout" pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
            <Filters>
                <Filter type="MarkerFilter" marker="FLOW" onMatch="DENY" onMismatch="NEUTRAL"/>
                <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="DENY" onMismatch="ACCEPT"/>
            </Filters>
        </Appender>
        <Appender type="Console" name="FLOW">
            <Layout type="PatternLayout" pattern="%C{1}.%M %m %ex%n"/><!-- class and line number -->
            <Filters>
                <Filter type="MarkerFilter" marker="FLOW" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
                <Filter type="MarkerFilter" marker="EXCEPTION" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
        </Appender>
        <Appender type="File" name="File" fileName="${filename}">
            <Layout type="PatternLayout">
                <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
            </Layout>
        </Appender>
    </Appenders>

    <Loggers>
        <Logger name="test1" level="debug" additivity="false">
            <Filter type="ThreadContextMapFilter">
                <KeyValuePair key="test" value="123"/>
            </Filter>
            <AppenderRef ref="STDOUT"/>
        </Logger>

        <Logger name="test2" level="debug" additivity="false">
            <AppenderRef ref="File"/>
        </Logger>

        <Root level="trace">
            <AppenderRef ref="STDOUT"/>
        </Root>
    </Loggers>

</Configuration>
  • 根節(jié)點Configuration設置Log4j2本身的屬性。
  • Appenders設置多個Appender對象,Appender內部包含Layout和Filters。
  • Loggers設置Logger對象,包含Logger和Root兩個子節(jié)點。
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4jDemo {

    private static Logger logger= 
       LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);

    public static void main(String[] args) {
        for(int i=0;i<3;i++){
            // 記錄trace級別的信息
            logger.trace("log4j2日志輸出:This is trace message.");
            // 記錄debug級別的信息
            logger.debug("log4j2日志輸出:This is debug message.");
            // 記錄info級別的信息
            logger.info("log4j2日志輸出:This is info message.");
            // 記錄error級別的信息
            logger.error("log4j2日志輸出:This is error message.");
        }
    }
}
  • 通過LogManager.getLogger()來獲取Logger對象進行日志輸出。

配置文件解析

  1. 根節(jié)點Configuration有兩個屬性:status和monitorinterval,有兩個子節(jié)點:Appenders和Loggers(表明可以定義多個Appender和Logger)。
    • status:用來指定log4j本身的打印日志的級別。
    • monitorinterval:用于指定log4j自動重新配置的監(jiān)測間隔時間,單位是s,最小是5s。
  2. Appenders節(jié)點,常見的有三種子節(jié)點:Console、RollingFile、File。
    • Console節(jié)點用來定義輸出到控制臺的Appender。

      • name:指定Appender的名字。
      • target:SYSTEM_OUT 或 SYSTEM_ERR,一般只設置默認:SYSTEM_OUT。
      • PatternLayout:輸出格式,不設置默認為:%m%n。
    • File節(jié)點用來定義輸出到指定位置的文件的Appender。

      • name:指定Appender的名字。
      • fileName:指定輸出日志的目的文件帶全路徑的文件名。
      • PatternLayout:輸出格式,不設置默認為:%m%n。
    • RollingFile節(jié)點用來定義超過指定大小自動刪除舊的創(chuàng)建新的Appender。

      • name:指定Appender的名字。
      • fileName:指定輸出日志的目的文件帶全路徑的文件名。
      • PatternLayout:輸出格式,不設置默認為:%m%n。
      • filePattern:指定新建日志文件的名稱格式。
      • Policies:指定滾動日志的策略,就是什么時候進行新建日志文件輸出日志。
      • TimeBasedTriggeringPolicy:Policies子節(jié)點,基于時間的滾動策略,interval屬性用來指定多久滾動一次,默認是1 hour。modulate=true用來調整時間:比如現(xiàn)在是早上3am,interval是4,那么第一次滾動是在4am,接著是8am,12am...而不是7am。
      • SizeBasedTriggeringPolicy:Policies子節(jié)點,基于指定文件大小的滾動策略,size屬性用來定義每個日志文件的大小。
      • DefaultRolloverStrategy:用來指定同一個文件夾下最多有幾個日志文件時開始刪除最舊的,創(chuàng)建新的(通過max屬性),默認是7個文件。
  3. Loggers節(jié)點,常見的有兩種:Root和Logger。
    • Root節(jié)點用來指定項目的根日志,如果沒有單獨指定Logger,那么就會默認使用該Root日志輸出。
      • level:日志輸出級別,共有8個級別,按照從低到高為:All < Trace < Debug < Info < Warn < Error < Fatal < OFF。
    • AppenderRef:Root的子節(jié)點,用來指定該日志輸出到哪個Appender。
      • Logger節(jié)點用來單獨指定日志的形式,比如要為指定包下的class指定不同的日志級別等。
      • level:日志輸出級別,共有8個級別,按照從低到高為:All < Trace < Debug < Info < Warn < Error < Fatal < OFF。
      • name:用來指定該Logger所適用的類或者類所在的包全路徑,繼承自Root節(jié)點。
      • AppenderRef:Logger的子節(jié)點,用來指定該日志輸出到哪個Appender,如果沒有指定,就會默認繼承自Root.如果指定了,那么會在指定的這個Appender和Root的Appender中都會輸出,此時我們可以設置Logger的additivity="false"只在自定義的Appender中進行輸出。
  4. 關于日志level.
    • 共有8個級別,按照從低到高為:All < Trace < Debug < Info < Warn < Error < Fatal < OFF。
    • All:最低等級的,用于打開所有日志記錄。
    • Trace:是追蹤,就是程序推進以下,你就可以寫個trace輸出。
    • Debug:指出細粒度信息事件對調試應用程序是非常有幫助的。
    • Info:消息在粗粒度級別上突出強調應用程序的運行過程。
    • Warn:輸出警告及warn以下級別的日志。
    • Error:輸出錯誤信息日志。
    • Fatal:輸出每個嚴重的錯誤事件將會導致應用程序的退出的日志.
    • OFF:最高等級的,用于關閉所有日志記錄。

log4j2核心組件

public class LoggerContext extends AbstractLifeCycle
        implements org.apache.logging.log4j.spi.LoggerContext, 
        AutoCloseable, Terminable, ConfigurationListener,
        LoggerContextShutdownEnabled {
    // 保存Logger對象的LoggerRegistry對象
    private final LoggerRegistry<Logger> loggerRegistry = 
                                          new LoggerRegistry<>();
    // 保存配置的Configuration對象
    private volatile Configuration configuration = new DefaultConfiguration();
}
  • LoggerContext通過LoggerRegistry保存Logger對象。
public class Logger extends AbstractLogger implements Supplier<LoggerConfig> {
    // PrivateConfig包含LoggerConfig 
    protected volatile PrivateConfig privateConfig;
    // LoggerContext對象 
    private final LoggerContext context;

    protected class PrivateConfig {
        // config fields are public to make them visible to Logger subclasses
        /** LoggerConfig to delegate the actual logging to. */
        public final LoggerConfig loggerConfig; // SUPPRESS CHECKSTYLE
        /** The current Configuration associated with the LoggerConfig. */
        public final Configuration config; // SUPPRESS CHECKSTYLE
        private final Level loggerConfigLevel;
        private final int intLevel;
        private final Logger logger;
        private final boolean requiresLocation;
    }
}
  • Logger通過PrivateConfig來保存LoggerConfig對象。
public abstract class AbstractFilterable 
    extends AbstractLifeCycle implements Filterable {

    private volatile Filter filter;
}

public class LoggerConfig extends AbstractFilterable implements LocationAware {

    private List<AppenderRef> appenderRefs = new ArrayList<>();
    private final AppenderControlArraySet appenders = 
                new AppenderControlArraySet();
    private LoggerConfig parent;
    private Map<Property, Boolean> propertiesMap;
    private final List<Property> properties;
    private final Configuration config;
}

public class AppenderControl extends AbstractFilterable {

    private final ThreadLocal<AppenderControl> recursive = new ThreadLocal<>();
    private final Appender appender;
    private final Level level;
    private final int intLevel;
    private final String appenderName;
}
  • LoggerConfig通過AppenderControlArraySet保存AppenderControl。
  • AppenderControl保存Appender。
  • LoggerConfig父類包含F(xiàn)ilter對象。
public abstract class AbstractFilterable 
    extends AbstractLifeCycle implements Filterable {

    private volatile Filter filter;
}

public abstract class AbstractAppender extends AbstractFilterable 
                              implements Appender, LocationAware {
    private final String name;
    private final boolean ignoreExceptions;
    private final Layout<? extends Serializable> layout;
}
  • Appender對象包含F(xiàn)ilter對象。
  • Appender對象包含Layout對象。
public abstract class AbstractFilterable 
    extends AbstractLifeCycle implements Filterable {

    private volatile Filter filter;
}


public abstract class AbstractConfiguration 
      extends AbstractFilterable implements Configuration {

    protected final List<String> pluginPackages = new ArrayList<>();
    protected PluginManager pluginManager;
    private String name;
    private ConcurrentMap<String, Appender> appenders 
                                = new ConcurrentHashMap<>();
    private ConcurrentMap<String, LoggerConfig> loggerConfigs 
                              = new ConcurrentHashMap<>();
    private LoggerConfig root = new LoggerConfig();
    private final WeakReference<LoggerContext> loggerContext;
}
  • Configuration包含Appender對象。
  • Configuration包含LoggerConfig對象。
  • Configuration包含F(xiàn)ilter對象。

Log4j2初始化流程

LogManager

  • LogManager是Log4J啟動的入口,后續(xù)的LoggerContext以及Logger都是通過調用LogManager的getLogger靜態(tài)方法獲得。
  • LogManager的static代碼塊初始化LoggerContextFactor對象 。
public class LogManager {
    public static String FACTORY_PROPERTY_NAME = "log4j2.loggerContextFactory";
    private static volatile LoggerContextFactory factory;

    static {
        // step1 通過特定配置文件的配置信息獲取loggerContextFactory
        final PropertiesUtil managerProps = PropertiesUtil.getProperties();
        final String factoryClassName = managerProps.getStringProperty(
                                                  FACTORY_PROPERTY_NAME);
        if (factoryClassName != null) {
            try {
                factory = LoaderUtil.newCheckedInstanceOf(
                  factoryClassName, LoggerContextFactory.class);
            } catch (final ClassNotFoundException cnfe) {
            } catch (final Exception ex) {
            }
        }

        if (factory == null) {
            final SortedMap<Integer, LoggerContextFactory> factories = 
                                                           new TreeMap<>();
            // step2 ProviderUtil中的getProviders()方法載入providers
            if (ProviderUtil.hasProviders()) {
                for (final Provider provider : ProviderUtil.getProviders()) {
                    final Class<? extends LoggerContextFactory> factoryClass 
                                = provider.loadLoggerContextFactory();
                    if (factoryClass != null) {
                        try {
                            factories.put(provider.getPriority(), 
                                        factoryClass.newInstance());
                        } catch (final Exception e) {
                        }
                    }
                }

                if (factories.isEmpty()) {
                    factory = new SimpleLoggerContextFactory();
                } else if (factories.size() == 1) {
                    factory = factories.get(factories.lastKey());
                } else {
                    factory = factories.get(factories.lastKey());
                }
            } else {
                // step3 默認SimpleLoggerContextFactory
                factory = new SimpleLoggerContextFactory();
            }
        }
    }
}
  • 1.首先通過PropertiesUtil.getProperties()根據(jù)特定配置文件的配置信息獲取loggerContextFactory的類。
  • 2.如果沒有找到對應的Factory則通過ProviderUtil中的getProviders()方法載入providers,通過provider的loadLoggerContextFactory方法載入LoggerContextFactory的實現(xiàn)類。
  • 3.如果provider中沒有獲取到LoggerContextFactory的實現(xiàn)類或provider為空,則使用SimpleLoggerContextFactory作為LoggerContextFactory。

根據(jù)配置文件加載Factory

public final class PropertiesUtil {

    private static String LOG4J_PROPERTIES_FILE_NAME = 
                                        "log4j2.component.properties";
    private static final PropertiesUtil LOG4J_PROPERTIES = 
                    new PropertiesUtil(LOG4J_PROPERTIES_FILE_NAME);

    public static PropertiesUtil getProperties() {
        return LOG4J_PROPERTIES;
    }

    public PropertiesUtil(final String propertiesFileName) {
        this.environment = new Environment(
                  new PropertyFilePropertySource(propertiesFileName));
    }
}

public class PropertyFilePropertySource extends PropertiesPropertySource {

    public PropertyFilePropertySource(final String fileName) {
        super(loadPropertiesFile(fileName));
    }

    // 查找指定文件內容并加載至Properties當中
    private static Properties loadPropertiesFile(final String fileName) {
        final Properties props = new Properties();
        for (final URL url : LoaderUtil.findResources(fileName)) {
            try (final InputStream in = url.openStream()) {
                props.load(in);
            } catch (final IOException e) {
            }
        }
        return props;
    }
}
  • PropertiesUtil.getProperties()負責加載log4j2.component.properties文件。
  • log4j2.component.properties內包含log4j2.loggerContextFactory的配置。

通過ProviderUtil加載Factory

public final class ProviderUtil {

    static String PROVIDER_RESOURCE = "META-INF/log4j-provider.properties";

    private ProviderUtil() {
        // 通過SPI機制進行加載
        for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
            try {
                loadProviders(classLoader);
            } catch (final Throwable ex) {
                
            }
        }
        // 查找log4j-provider.properties文件
        for (final LoaderUtil.UrlResource resource : 
                   LoaderUtil.findUrlResources(PROVIDER_RESOURCE)) {
            loadProvider(resource.getUrl(), resource.getClassLoader());
        }
    }

    protected static void loadProviders(final ClassLoader classLoader) {
        // 通過SPI方式加載Provider
        final ServiceLoader<Provider> serviceLoader = 
                  ServiceLoader.load(Provider.class, classLoader);
        for (final Provider provider : serviceLoader) {
            if (validVersion(provider.getVersions()) 
                                && !PROVIDERS.contains(provider)) {
                PROVIDERS.add(provider);
            }
        }
    }
}
  • loadProviders的通過ServiceLoader實現(xiàn)SPI的加載制定的Factory。

生成默認Factory

factory = new SimpleLoggerContextFactory();
  • 默認的工廠為SimpleLoggerContextFactory。

LoggerContext

LoggerContext 包含了配置信息,并能創(chuàng)建log4j-api定義的Logger接口實例。

public class LogManager {

    public static LoggerContext getContext(final boolean currentContext) {
        try {
            return factory.getContext(FQCN, null, null, 
                      currentContext, null, null);
        } catch (final IllegalStateException ex) {
            return new SimpleLoggerContextFactory().
              getContext(FQCN, null, null, currentContext, null, null);
        }
    }
  • 通過Log4jContextFactory的getContext來獲取LoggerContext對象。
public class Log4jContextFactory implements LoggerContextFactory {

    private static ContextSelector createContextSelector() {
        // 省略其他代碼

        return new ClassLoaderContextSelector();
    }

    public LoggerContext getContext(final String fqcn, 
                                    final ClassLoader loader, 
                                    final Object externalContext,
                                    final boolean currentContext, 
                                    final URI configLocation, 
                                    final String name) {
        // 通過selector=ClassLoaderContextSelector來獲取ctx
        final LoggerContext ctx = 
          selector.getContext(fqcn, loader, currentContext, configLocation);
        if (externalContext != null && ctx.getExternalContext() == null) {
            ctx.setExternalContext(externalContext);
        }
        if (name != null) {
            ctx.setName(name);
        }
        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
            if (configLocation != null || name != null) {
                ContextAnchor.THREAD_CONTEXT.set(ctx);
                final Configuration config = ConfigurationFactory.getInstance()
                     .getConfiguration(ctx, name, configLocation);
                ctx.start(config);
                ContextAnchor.THREAD_CONTEXT.remove();
            } else {
                ctx.start();
            }
        }
        return ctx;
    }
}
  • 通過ClassLoaderContextSelector的getContext來獲取LoggerContext。
  • ctx.start()負責配置的解析,這部分待后續(xù)進行分析。
public class ClassLoaderContextSelector 
    implements ContextSelector, LoggerContextShutdownAware {

    public LoggerContext getContext(final String fqcn, 
            final ClassLoader loader, 
            final boolean currentContext,
            final URI configLocation) {
        if (currentContext) {
            final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
            if (ctx != null) {
                return ctx;
            }
            return getDefault();
        } else if (loader != null) {
            return locateContext(loader, configLocation);
        } else {
            final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
            if (clazz != null) {
                return locateContext(clazz.getClassLoader(), configLocation);
            }
            final LoggerContext lc = ContextAnchor.THREAD_CONTEXT.get();
            if (lc != null) {
                return lc;
            }
            return getDefault();
        }
    }

    private LoggerContext locateContext(final ClassLoader loaderOrNull, 
                                         final URI configLocation) {
        final ClassLoader loader = loaderOrNull != null ? 
           loaderOrNull : ClassLoader.getSystemClassLoader();
        final String name = toContextMapKey(loader);
        AtomicReference<WeakReference<LoggerContext>> ref = 
                                      CONTEXT_MAP.get(name);
        if (ref == null) {
            if (configLocation == null) {
                ClassLoader parent = loader.getParent();
                while (parent != null) {
                    ref = CONTEXT_MAP.get(toContextMapKey(parent));
                    if (ref != null) {
                        final WeakReference<LoggerContext> r = ref.get();
                        final LoggerContext ctx = r.get();
                        if (ctx != null) {
                            return ctx;
                        }
                    }
                    parent = parent.getParent();
            
                }
            }
            LoggerContext ctx = createContext(name, configLocation);
            final AtomicReference<WeakReference<LoggerContext>> r = 
                                            new AtomicReference<>();
            r.set(new WeakReference<>(ctx));
            CONTEXT_MAP.putIfAbsent(name, r);
            ctx = CONTEXT_MAP.get(name).get().get();
            return ctx;
        }
        final WeakReference<LoggerContext> weakRef = ref.get();
        LoggerContext ctx = weakRef.get();
        if (ctx != null) {
            if (ctx.getConfigLocation() == null && configLocation != null) {
                ctx.setConfigLocation(configLocation);
            } 
            return ctx;
        }
        ctx = createContext(name, configLocation);
        ref.compareAndSet(weakRef, new WeakReference<>(ctx));
        return ctx;
    }

    protected LoggerContext createContext(final String name, 
                                          final URI configLocation) {
        return new LoggerContext(name, null, configLocation);
    }
}
  • 按照getContext、locateContext、createContext的邏輯創(chuàng)建LoggerContext。
  • CONTEXT_MAP通過線程安全的putIfAbsent保存新建的LoggerContext。
  • ClassLoaderContextSelector負責LoggerContext的創(chuàng)建。

配置解析過程

  • LoggerContext的start過程就是配置文件的解析過程,這部分邏輯后續(xù)補充。


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容