log4j

Log4j2的应用和原理

2021-02-20  本文已影响0人  晴天哥_王志

系列

简介

 Log4j2是apache在Log4j的基础上,参考logback架构实现的一套新的日志系统。本文基于Log4j-2.12.1的版本进行源码分析。
 本文的目的在于梳理Log4j2的Logger对象生成过程,借此理顺Log4j2各组件之间的联系。

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

配置文件解析

  1. 根节点Configuration有两个属性:status和monitorinterval,有两个子节点:Appenders和Loggers(表明可以定义多个Appender和Logger)。
    • status:用来指定log4j本身的打印日志的级别。
    • monitorinterval:用于指定log4j自动重新配置的监测间隔时间,单位是s,最小是5s。
  2. Appenders节点,常见的有三种子节点:Console、RollingFile、File。
    • Console节点用来定义输出到控制台的Appender。

      • name:指定Appender的名字。
      • target:SYSTEM_OUT 或 SYSTEM_ERR,一般只设置默认:SYSTEM_OUT。
      • PatternLayout:输出格式,不设置默认为:%m%n。
    • File节点用来定义输出到指定位置的文件的Appender。

      • name:指定Appender的名字。
      • fileName:指定输出日志的目的文件带全路径的文件名。
      • PatternLayout:输出格式,不设置默认为:%m%n。
    • RollingFile节点用来定义超过指定大小自动删除旧的创建新的Appender。

      • name:指定Appender的名字。
      • fileName:指定输出日志的目的文件带全路径的文件名。
      • PatternLayout:输出格式,不设置默认为:%m%n。
      • filePattern:指定新建日志文件的名称格式。
      • Policies:指定滚动日志的策略,就是什么时候进行新建日志文件输出日志。
      • TimeBasedTriggeringPolicy:Policies子节点,基于时间的滚动策略,interval属性用来指定多久滚动一次,默认是1 hour。modulate=true用来调整时间:比如现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am。
      • SizeBasedTriggeringPolicy:Policies子节点,基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小。
      • DefaultRolloverStrategy:用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性),默认是7个文件。
  3. Loggers节点,常见的有两种:Root和Logger。
    • Root节点用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出。
      • level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF。
    • AppenderRef:Root的子节点,用来指定该日志输出到哪个Appender。
      • Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。
      • level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF。
      • name:用来指定该Logger所适用的类或者类所在的包全路径,继承自Root节点。
      • AppenderRef:Logger的子节点,用来指定该日志输出到哪个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();
}
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;
    }
}
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;
}
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;
}
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;
}

Log4j2初始化流程

LogManager

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

根据配置文件加载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;
    }
}

通过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);
            }
        }
    }
}

生成默认Factory

factory = new SimpleLoggerContextFactory();

LoggerContext

LoggerContext 包含了配置信息,并能创建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);
        }
    }
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;
    }
}
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);
    }
}

配置解析过程

上一篇下一篇

猜你喜欢

热点阅读