[JAVA] Log4j 2 Grundlegendes zu den Konzepten des Protokollsystems

In diesem Artikel werden wir den Quellcode für ** Log4j ** 2, ein neues Protokollierungssystem, das auf Apache Log4j basiert, aufschlüsseln und die zugrunde liegenden Konzepte vertiefen.

Überblick

Log4j 2 ist ein brandneues Protokollierungssystem, das auf Apache Log4j in Bezug auf die Logback-Architektur basiert. Ich denke, das liegt daran, dass Log4j von Logback grundsätzlich veraltet ist.

Informationen zu den Vorteilen von Log4j 2 finden Sie in der offiziellen Dokumentation Apache Log4j 2.

--Log4j 2 bietet viele Verbesserungen in Logback und behebt einige der Probleme, die mit der Logback-Architektur verbunden sind. --API-Trennung: Die Log4j 2-API kann mit der Log4j 2-Implementierung verwendet werden, sie kann jedoch auch vor anderen Protokollierungsimplementierungen wie Logback verwendet werden (obwohl dies niemand tun wird). Die Fassade.

--Automatisches Neuladen von Einstellungen: Ähnlich wie Logback kann Log4j 2 Einstellungen bei Änderungen automatisch neu laden. Im Gegensatz zu Logback geschieht dies, ohne dass Protokollereignisse während der Neukonfiguration verloren gehen.

Log4j 2 Klassendiagramm:

image.png

In diesem Artikel untersuchen wir den Log4j 2-Quellcode unter vier Gesichtspunkten: Start, Konfiguration, Desynchronisation und Plug-in-basierte Komponenten.

Suche nach Quellcode

Anlaufen

Hauptkomponenten von Log4j 2

1.  Parses the configuration file to obtain the corresponding Java object
2.  Cache log configuration through LoggerRegisty
3.  Obtain the configuration information
4.  Use the start() method to parse the configuration file, and convert the configuration file into the corresponding Java object
5.  Obtain the logger object through getLogger
Logger logger = LogManager.getLogger();

Sie sehen, dass LogManager eine wichtige Komponente ist. Daher werden wir hier den Startvorgang von LogManager im Detail analysieren.

Verwenden Sie das folgende statische Code-Snippet, um LogManager zu starten.

/**
     * Scans the classpath to find all logging implementation. Currently, only one will be used but this could be
     * extended to allow multiple implementations to be used.
     */
    static {
        // Shortcut binding to force a specific logging implementation.
        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) {
                LOGGER.error("Unable to locate configured LoggerContextFactory {}", factoryClassName);
            } catch (final Exception ex) {
                LOGGER.error("Unable to create configured LoggerContextFactory {}", factoryClassName, ex);
            }
        }

        if (factory == null) {
            final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>();
            // note that the following initial call to ProviderUtil may block until a Provider has been installed when
            // running in an OSGi environment
            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) {
                            LOGGER.error("Unable to create class {} specified in provider URL {}", factoryClass.getName(), provider
                                    .getUrl(), e);
                        }
                    }
                }

                if (factories.isEmpty()) {
                    LOGGER.error("Log4j2 could not find a logging implementation. "
                            + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...") ;
                    factory = new SimpleLoggerContextFactory();
                } else if (factories.size() == 1) {
                    factory = factories.get(factories.lastKey());
                } else { 
                    final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n");
                    for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) {
                        sb.append("Factory: ").append(entry.getValue().getClass().getName());
                        sb.append(", Weighting: ").append(entry.getKey()).append('\n');
                    }
                    factory = factories.get(factories.lastKey());
                    sb.append("Using factory: ").append(factory.getClass().getName()); 
                    LOGGER.warn(sb.toString());

                }
            } else { 
                LOGGER.error("Log4j2 could not find a logging implementation. "
                        + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...") ;
                factory = new SimpleLoggerContextFactory();
            }
        }
    }

Dieses statische Snippet wird wie folgt implementiert:

  1. Rufen Sie zunächst die loggerContextFactory basierend auf den Konfigurationsinformationen in einer bestimmten Konfigurationsdatei ab.
  2. Wenn die Implementierungsklasse LoggerContextFactory nicht gefunden wird, laden Sie den Anbieter mit der Methode getProviders () von ProviderUtil. Laden Sie als Nächstes die Implementierungsklasse von LoggerContextFactory mit der loadLoggerContextFactory () -Methode des Anbieters.
  3. Wenn der Anbieter die LoggerContextFactory-Implementierungsklasse nicht laden kann oder der Anbieter leer ist, verwenden Sie SimpleLoggerContextFactory als LoggerContextFactory.

Laden Sie LoggerContextFactory gemäß der Konfigurationsdatei

// Shortcut binding to force a specific logging implementation.
        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) { 
                LOGGER.error("Unable to locate configured LoggerContextFactory {}", factoryClassName);
            } catch (final Exception ex) { 
                LOGGER.error("Unable to create configured LoggerContextFactory {}", factoryClassName, ex);
            }
        }

In diesem Snippet ruft LogManager die LoggerContextFactory zuerst mithilfe des Konfigurationselements "log4j2.loggerContextFactory" in der Konfigurationsdatei "log4j2.component.properties" ab. Instanziieren Sie nach Abschluss der entsprechenden Einstellungen das LoggerContextFactory-Objekt mit der newCheckedInstanceOf () -Methode. Das folgende Snippet zeigt, wie es funktioniert.

public static <T> T newInstanceOf(final Class<T> clazz)
            throws InstantiationException, IllegalAccessException, InvocationTargetException {
        try {
            return clazz.getConstructor().newInstance();
        } catch (final NoSuchMethodException ignored) {
            // FIXME: looking at the code for Class.newInstance(), this seems to do the same thing as above
            return clazz.newInstance();
        }
    }

Standardmäßig ist die Initialisierungsdatei log4j2.component.properties nicht vorhanden. Daher müssen Sie die LoggerContextFactory mit einer anderen Methode abrufen.

Verwenden Sie den Provider, um das LoggerContextFactory-Objekt zu instanziieren

Der Code lautet:

if (factory == null) {
            final SortedMap<Integer, LoggerContextFactory> factories = new TreeMap<>();
            // note that the following initial call to ProviderUtil may block until a Provider has been installed when
            // running in an OSGi environment
            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) {
                            LOGGER.error("Unable to create class {} specified in provider URL {}", factoryClass.getName(), provider
                                    .getUrl(), e);
                        }
                    }
                }

                if (factories.isEmpty()) {
                    LOGGER.error("Log4j2 could not find a logging implementation. "
                            + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...") ;
                    factory = new SimpleLoggerContextFactory();
                } else if (factories.size() == 1) {
                    factory = factories.get(factories.lastKey());
                } else { 
                    final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n");
                    for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) {
                        sb.append("Factory: ").append(entry.getValue().getClass().getName());
                        sb.append(", Weighting: ").append(entry.getKey()).append('\n'); 
                    }
                    factory = factories.get(factories.lastKey());
                    sb.append("Using factory: ").append(factory.getClass().getName());
                    LOGGER.warn(sb.toString());

                }
            } else { 
                LOGGER.error("Log4j2 could not find a logging implementation. "
                        + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...") ;
                factory = new SimpleLoggerContextFactory();
            }
        }

Es ist interessant, dass sowohl hasProviders als auch getProviders thread-sichere Methoden verwenden, um das ProviderUtil-Objekt träge zu initialisieren. Werfen wir einen Blick auf die Methode lazyInit ().

protected static void lazyInit() {
        //noinspection DoubleCheckedLocking
        if (INSTANCE == null) {
            try {
                STARTUP_LOCK.lockInterruptibly();
                if (INSTANCE == null) {
                    INSTANCE = new ProviderUtil();
                }
            } catch (final InterruptedException e) {
                LOGGER.fatal("Interrupted before Log4j Providers could be loaded.", e);
                Thread.currentThread().interrupt();
            } finally {
                STARTUP_LOCK.unlock();
            }
        }
    }

Schauen wir uns die Bauweise an.

private ProviderUtil() {
        for (final LoaderUtil.UrlResource resource : LoaderUtil.findUrlResources(PROVIDER_RESOURCE)) {
            loadProvider(resource.getUrl(), resource.getClassLoader());
        }
    }

Initialisierung bedeutet hier die Initialisierung des Provider-Objekts. Wenn Sie eine neue Instanz von providerUtil erstellen, wird das Provider-Objekt direkt instanziiert. Rufen Sie zunächst die Klassenlader des Anbieters mit der Methode getClassLoaders () ab. Verwenden Sie dann loadProviders (classLoader), um die Klasse zu laden. Der letzte Schritt bei der Instanziierung von providerUtil ist eine einheitliche Suche nach der entsprechenden Anbieter-URL in der Datei "META-INF / log4j-provider.properties". Laden Sie den Anbieter möglicherweise remote. Die loadProviders () -Methode fügt den Provider der PROVIDERS-Liste von ProviderUtil hinzu. Der Standardanbieter ist org.apache.logging.log4j.core.impl.Log4jContextFactory.

LoggerContextFactory = org.apache.logging.log4j.core.impl.Log4jContextFactory
Log4jAPIVersion = 2.1.0
FactoryPriority= 10

Interessanterweise wird Lazy-Init mit der Methode lockInterruptibly () gesperrt. Was ist der Unterschied zwischen lockInterruptibly und lock? Was ist der Unterschied zwischen lock und lockInterruptibly?

lock erwirbt zuerst die Sperre und reagiert dann auf das Interrupt-Signal.

lockInterruptible reagiert sofort auf Interrupt-Signale, die von anderen Threads gesendet werden, anstatt eine Sperre erneut zu erhalten oder erneut zu erhalten.

ReentrantLock.lockInterruptibly hat einen anderen Thread

Die Thread.interrupt () -Methode des Threads, der darauf wartet, die Sperre zu erhalten, hört auf zu warten und kehrt sofort zurück. In diesem Fall erhält der wartende Thread die Sperre nicht. Stattdessen wird eine InterruptedException ausgelöst. ReentrantLock.lock erlaubt keine Thread-Unterbrechungen beim Aufrufen der Thread.interrupt () -Methode. Der Thread wiederholt weiterhin die Erfassung der Sperre, auch wenn Thread.isInterrupted erkannt wird. Threads, die die Sperre nicht erhalten, werden der Warteschlange hinzugefügt. Wenn ein Thread schließlich eine Sperre erhält, wird er in einen unterbrochenen Zustand versetzt und es tritt ein Interrupt auf.

Die obigen Kommentare sind bemerkenswert.

/**
     * Guards the ProviderUtil singleton instance from lazy initialization. This is primarily used for OSGi support.
     *
     * @since 2.1
     */
    protected static final Lock STARTUP_LOCK = new ReentrantLock();
    // STARTUP_LOCK guards INSTANCE for lazy initialization; this allows the OSGi Activator to pause the startup and
    // wait for a Provider to be installed. See LOG4J2-373
    private static volatile ProviderUtil INSTANCE;

Es stellt sich heraus, dass dieses Snippet verwendet wird, damit der OSGi Activator den Start unterbrechen kann.

Kehren Sie zu LogManager zurück.

Sobald der Anbieter installiert ist, fährt LogManager mit der Werksbindung fort.

if (factories.isEmpty()) {
                    LOGGER.error("Log4j2 could not find a logging implementation. "
                            + "Please add log4j-core to the classpath. Using SimpleLogger to log to the console...") ;
                    factory = new SimpleLoggerContextFactory();
                } else if (factories.size() == 1) {
                    factory = factories.get(factories.lastKey());
                } else { 
                    final StringBuilder sb = new StringBuilder("Multiple logging implementations found: \n");
                    for (final Map.Entry<Integer, LoggerContextFactory> entry : factories.entrySet()) {
                        sb.append("Factory: ").append(entry.getValue().getClass().getName());
                        sb.append(", Weighting: ").append(entry.getKey()).append('\n');
                    }
                    factory = factories.get(factories.lastKey());
                    sb.append("Using factory: ").append(factory.getClass().getName());
                    LOGGER.warn(sb.toString());

                }

Der LogManager-Startvorgang endet hier.

Aufbau

Um einen Logger ohne Verwendung von slf4j zu erhalten

Logger logger = logManager.getLogger(xx.class)

Werfen wir einen Blick auf die Methode getLogger ().

    public static Logger getLogger(final Class<? > clazz) {
        final Class<? > cls = callerClass(clazz);
        return getContext(cls.getClassLoader(), false).getLogger(toLoggerName(cls));
    }

Werfen wir einen Blick auf die Methode getContext ().

public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext) {
        try {
            return factory.getContext(FQCN, loader, null, currentContext);
        } catch (final IllegalStateException ex) {
            LOGGER.warn(ex.getMessage() + " Using SimpleLogger");
            return new SimpleLoggerContextFactory().getContext(FQCN, loader, null, currentContext);
        }
    }

Wie bereits erwähnt, ist die Factory-Methode in Log4jContextFactory implementiert. Werfen wir einen Blick auf getContext.

Methode:

public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
                                    final boolean currentContext) {
        final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext);
        if (externalContext ! = null && ctx.getExternalContext() == null) {
            ctx.setExternalContext(externalContext);
        }
        if (ctx.getState() == LifeCycle.State.INITIALIZED) {
            ctx.start();
        }
        return ctx;
    }

Werfen wir einen Blick auf die Methode start ().

public void start() {
        LOGGER.debug("Starting LoggerContext[name={}, {}]...", getName(), this);
        if (PropertiesUtil.getProperties().getBooleanProperty("log4j.LoggerContext.stacktrace.on.start", false)) {
            LOGGER.debug("Stack trace to locate invoker",
                    new Exception("Not a real error, showing stack trace to locate invoker"));
        }
        if (configLock.tryLock()) {
            try {
                if (this.isInitialized() || this.isStopped()) {
                    this.setStarting();
                    reconfigure();
                    if (this.configuration.isShutdownHookEnabled()) {
                        setUpShutdownHook();
                    }
                    this.setStarted();
                }
            } finally {
                configLock.unlock();
            }
        }
        LOGGER.debug("LoggerContext[name={}, {}] started OK.", getName(), this);
    }

Die Kernmethode ist reconfigure (). Bewegung

private void reconfigure(final URI configURI) {
        final ClassLoader cl = ClassLoader.class.isInstance(externalContext) ? (ClassLoader) externalContext : null;
        LOGGER.debug("Reconfiguration started for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
                contextName, configURI, this, cl);
        final Configuration instance = ConfigurationFactory.getInstance().getConfiguration(this, contextName, configURI, cl);
        if (instance == null) {
            LOGGER.error("Reconfiguration failed: No configuration found for '{}' at '{}' in '{}'", contextName, configURI, cl);
        } else { 
            setConfiguration(instance);
            /*
             * instance.start(); Configuration old = setConfiguration(instance); updateLoggers(); if (old ! = null) {
             * old.stop(); }
             */
            final String location = configuration == null ?  "?" : String.valueOf(configuration.getConfigurationSource());
            LOGGER.debug("Reconfiguration complete for context[name={}] at URI {} ({}) with optional ClassLoader: {}",
                    contextName, location, this, cl);
        }
    }

Sie können sehen, dass jede Einstellung von Configuration Factory stammt. Schauen wir uns zunächst die Methode getInstance () dieser Klasse an.

public static ConfigurationFactory getInstance() {
        // volatile works in Java 1.6+, so double-checked locking also works properly
        //noinspection DoubleCheckedLocking
        if (factories == null) {
            LOCK.lock();
            try {
                if (factories == null) {
                    final List<ConfigurationFactory> list = new ArrayList<ConfigurationFactory>();
                    final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY);
                    if (factoryClass ! = null) {
                        addFactory(list, factoryClass);
                    }
                    final PluginManager manager = new PluginManager(CATEGORY);
                    manager.collectPlugins();
                    final Map<String, PluginType<? >> plugins = manager.getPlugins();
                    final List<Class<? extends ConfigurationFactory>> ordered =
                        new ArrayList<Class<? extends ConfigurationFactory>>(plugins.size());
                    for (final PluginType<? > type : plugins.values()) {
                        try {
                            ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class));
                        } catch (final Exception ex) {
                            LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex);
                        }
                    }
                    Collections.sort(ordered, OrderComparator.getInstance());
                    for (final Class<? extends ConfigurationFactory> clazz : ordered) {
                        addFactory(list, clazz);
                    }
                    // see above comments about double-checked locking
                    //noinspection NonThreadSafeLazyInitialization
                    factories = Collections.unmodifiableList(list);
                }
            } finally {
                LOCK.unlock();
            }
        }

        LOGGER.debug("Using configurationFactory {}", configFactory);
        return configFactory;
    }

Sie können sehen, dass ConfigurationFactory PluginManager zur Initialisierung verwendet. PluginManager lädt eine Unterklasse von ConfigurationFactory. Die Standardunterklassen sind XmlConfigurationFactory, JsonConfigurationFactory und YamlConfigurationFactory. Diese werden als Plug-Ins geladen.

Kehren Sie zur Methode reconfigure () zurück. Sie können sehen, dass LogManager die Methode getConfiguration () aufruft, nachdem eine Instanz von ConfigurationFactory abgerufen wurde.

public Configuration getConfiguration(final String name, final URI configLocation, final ClassLoader loader) {
        if (! isActive()) {
            return null;
        }
        if (loader == null) {
            return getConfiguration(name, configLocation);
        }
        if (isClassLoaderUri(configLocation)) {
            final String path = extractClassLoaderUriPath(configLocation);
            final ConfigurationSource source = getInputFromResource(path, loader);
            if (source ! = null) {
                final Configuration configuration = getConfiguration(source);
                if (configuration ! = null) {
                    return configuration;
                }
            }
        }
        return getConfiguration(name, configLocation);
    }

Werfen wir einen Blick auf die Methode getConfiguration (). Achten Sie darauf, nicht verwirrt zu werden, da getConfiguration () mehrmals aufgerufen wird. Wenn Sie sich nicht sicher sind, versuchen Sie das Debuggen.

public Configuration getConfiguration(final String name, final URI configLocation) {

            if (configLocation == null) {
                final String config = this.substitutor.replace(
                    PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FILE_PROPERTY));
                if (config ! = null) {
                    ConfigurationSource source = null;
                    try {
                        source = getInputFromUri(FileUtils.getCorrectedFilePathUri(config));
                    } catch (final Exception ex) {
                        // Ignore the error and try as a String.
                        LOGGER.catching(Level.DEBUG, ex);
                    }
                    if (source == null) {
                        final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
                        source = getInputFromString(config, loader);
                    }
                    if (source ! = null) {
                        for (final ConfigurationFactory factory : factories) {
                            final String[] types = factory.getSupportedTypes();
                            if (types ! = null) {
                                for (final String type : types) {
                                    if (type.equals("*") || config.endsWith(type)) {
                                        final Configuration c = factory.getConfiguration(source);
                                        if (c ! = null) {
                                            return c;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } else { 
                for (final ConfigurationFactory factory : factories) {
                    final String[] types = factory.getSupportedTypes();
                    if (types ! = null) {
                        for (final String type : types) {
                            if (type.equals("*") || configLocation.toString().endsWith(type)) {
                                final Configuration config = factory.getConfiguration(name, configLocation);
                                if (config ! = null) {
                                    return config;
                                }
                            }
                        }
                    }
                }
            }

            Configuration config = getConfiguration(true, name);
            if (config == null) {
                config = getConfiguration(true, null);
                if (config == null) {
                    config = getConfiguration(false, name);
                    if (config == null) {
                        config = getConfiguration(false, null);
                    }
                }
            }
            if (config ! = null) {
                return config;
            }
            LOGGER.error("No log4j2 configuration file found. Using default configuration: logging only errors to the console.") ;
            return new DefaultConfiguration();
        }

LogManager ruft dann die Einstellungen von der zuvor geladenen Konfigurationsfactory ab.

Kehren Sie zur Methode reconfigure () zurück. Die nächste aufzurufende Methode ist setConfiguration () und gibt die zuvor erhaltene Konfiguration als Eingabeargument an.

private synchronized Configuration setConfiguration(final Configuration config) {
        Assert.requireNonNull(config, "No Configuration was provided");
        final Configuration prev = this.config;
        config.addListener(this);
        final ConcurrentMap<String, String> map = config.getComponent(Configuration.CONTEXT_PROPERTIES);

        try { // LOG4J2-719 network access may throw android.os.NetworkOnMainThreadException
            map.putIfAbsent("hostName", NetUtils.getLocalHostname());
        } catch (final Exception ex) {
            LOGGER.debug("Ignoring {}, setting hostName to 'unknown'", ex.toString());
            map.putIfAbsent("hostName", "unknown");
        }
        map.putIfAbsent("contextName", name);
        config.start();
        this.config = config;
        updateLoggers();
        if (prev ! = null) {
            prev.removeListener(this);
            prev.stop();
        }

        firePropertyChangeEvent(new PropertyChangeEvent(this, PROPERTY_CONFIG, prev, config));

        try {
            Server.reregisterMBeansAfterReconfigure();
        } catch (final Throwable t) {
            // LOG4J2-716: Android has no java.lang.management
            LOGGER.error("Could not reconfigure JMX", t);
        }
        return prev;
    }

Der wichtigste Schritt dieser Methode ist config.start, mit dem die Konfiguration analysiert wird.

public void start() {
        LOGGER.debug("Starting configuration {}", this);
        this.setStarting();
        pluginManager.collectPlugins(pluginPackages);
        final PluginManager levelPlugins = new PluginManager(Level.CATEGORY);
        levelPlugins.collectPlugins(pluginPackages);
        final Map<String, PluginType<? >> plugins = levelPlugins.getPlugins();
        if (plugins ! = null) {
            for (final PluginType<? > type : plugins.values()) {
                try {
                    // Cause the class to be initialized if it isn't already.
                    Loader.initializeClass(type.getPluginClass().getName(), type.getPluginClass().getClassLoader());
                } catch (final Exception e) {
                    LOGGER.error("Unable to initialize {} due to {}", type.getPluginClass().getName(), e.getClass()
                            .getSimpleName(), e);
                }
            }
        }
        setup();
        setupAdvertisement();
        doConfigure();
        final Set<LoggerConfig> alreadyStarted = new HashSet<LoggerConfig>();
        for (final LoggerConfig logger : loggers.values()) {
            logger.start();
            alreadyStarted.add(logger);
        }
        for (final Appender appender : appenders.values()) {
            appender.start();
        }
        if (! alreadyStarted.contains(root)) { // LOG4J2-392
            root.start(); // LOG4J2-336
        }
        super.start();
        LOGGER.debug("Started configuration {} OK.", this);
    }

Der Prozess umfasst die folgenden Schritte:

  1. Holen Sie sich das Plug-In auf Protokollebene
  2. Initialisierung
  3. Initialisierung des Werbetreibenden
  4. Konfiguration

Im Initialisierungsschritt wird die setup () -Methode aufgerufen. Sie müssen die setup () -Methode überschreiben. Die Operation wird am Beispiel der XMLConfiguration erläutert.

@Override
    public void setup() {
        if (rootElement == null) {
            LOGGER.error("No logging configuration");
            return; 
        }
        constructHierarchy(rootNode, rootElement);
        if (status.size() > 0) {
            for (final Status s : status) {
                LOGGER.error("Error processing element {}: {}", s.name, s.errorType);
            }
            return; 
        }
        rootElement = null;
    }

Hier wird die wichtige Methode constructHierarchy () verwendet. Werfen wir einen Blick darauf.

private void constructHierarchy(final Node node, final Element element) {
        processAttributes(node, element);
        final StringBuilder buffer = new StringBuilder();
        final NodeList list = element.getChildNodes();
        final List<Node> children = node.getChildren();
        for (int i = 0; i < list.getLength(); i++) { 
            final org.w3c.dom.Node w3cNode = list.item(i);
            if (w3cNode instanceof Element) {
                final Element child = (Element) w3cNode;
                final String name = getType(child);
                final PluginType<? > type = pluginManager.getPluginType(name);
                final Node childNode = new Node(node, name, type);
                constructHierarchy(childNode, child);
                if (type == null) {
                    final String value = childNode.getValue();
                    if (! childNode.hasChildren() && value ! = null) {
                        node.getAttributes().put(name, value);
                    } else {
                        status.add(new Status(name, element, ErrorType.CLASS_NOT_FOUND));
                    }
                } else { 
                    children.add(childNode);
                }
            } else if (w3cNode instanceof Text) {
                final Text data = (Text) w3cNode;
                buffer.append(data.getData());
            }
        }

        final String text = buffer.toString().trim();
        if (text.length() > 0 || (! node.hasChildren() && ! node.isRoot())) {
            node.setValue(text);
        }
    }

Wie Sie sehen können, handelt es sich um einen Baumdurchquerungsprozess. Natürlich wird die Konfigurationsdatei im XML-Format bereitgestellt. XML-Dokumente haben eine hierarchische Struktur und können konzeptionell als Baumstruktur interpretiert werden. Kehren Sie zur Methode start () zurück. Werfen wir einen Blick auf die Methode doConfigure ().

protected void doConfigure() {
        if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase("Properties")) {
            final Node first = rootNode.getChildren().get(0);
            createConfiguration(first, null);
            if (first.getObject() ! = null) {
                subst.setVariableResolver((StrLookup) first.getObject());
            }
        } else { 
            final Map<String, String> map = this.getComponent(CONTEXT_PROPERTIES);
            final StrLookup lookup = map == null ?  null : new MapLookup(map);
            subst.setVariableResolver(new Interpolator(lookup, pluginPackages));
        }

        boolean setLoggers = false;
        boolean setRoot = false;
        for (final Node child : rootNode.getChildren()) {
            if (child.getName().equalsIgnoreCase("Properties")) {
                if (tempLookup == subst.getVariableResolver()) {
                    LOGGER.error("Properties declaration must be the first element in the configuration");
                }
                continue;
            }
            createConfiguration(child, null);
            if (child.getObject() == null) {
                continue;
            }
            if (child.getName().equalsIgnoreCase("Appenders")) {
                appenders = child.getObject();
            } else if (child.isInstanceOf(Filter.class)) {
                addFilter(child.getObject(Filter.class));
            } else if (child.getName().equalsIgnoreCase("Loggers")) {
                final Loggers l = child.getObject();
                loggers = l.getMap();
                setLoggers = true;
                if (l.getRoot() ! = null) {
                    root = l.getRoot();
                    setRoot = true;
                }
            } else if (child.getName().equalsIgnoreCase("CustomLevels")) {
                customLevels = child.getObject(CustomLevels.class).getCustomLevels();
            } else if (child.isInstanceOf(CustomLevelConfig.class)) {
                final List<CustomLevelConfig> copy = new ArrayList<CustomLevelConfig>(customLevels);
                copy.add(child.getObject(CustomLevelConfig.class));
                customLevels = copy;
            } else { 
                LOGGER.error("Unknown object \"{}\" of type {} is ignored.", child.getName(),
                        child.getObject().getClass().getName());
            }
        }

        if (! setLoggers) {
            LOGGER.warn("No Loggers were configured, using default. Is the Loggers element missing?") ;
            setToDefault();
            return; 
        } else if (! setRoot) {
            LOGGER.warn("No Root logger was configured, creating default ERROR-level Root logger with Console appender");
            setToDefault();
            // return; // LOG4J2-219: creating default root=ok, but don't exclude configured Loggers
        }

        for (final Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) {
            final LoggerConfig l = entry.getValue();
            for (final AppenderRef ref : l.getAppenderRefs()) {
                final Appender app = appenders.get(ref.getRef());
                if (app ! = null) {
                    l.addAppender(app, ref.getLevel(), ref.getFilter());
                } else { 
                    LOGGER.error("Unable to locate appender {} for logger {}", ref.getRef(), l.getName());
                }
            }

        }

        setParents();
    }

Sie können sehen, dass diese Methode die zuvor erhaltene Konfiguration analysiert und das Ergebnis an der richtigen Stelle einfügt. Kehren Sie zur Methode start () zurück. Nach der Konfiguration müssen Sie den Logger und den Appender starten.

Asynchronisation

Asynchroner Appender

image.png

AsyncAppender ist eine großartige Funktion, die es von anderen Protokolldiensten unterscheidet. Schauen wir uns zunächst den Mechanismus an, indem wir das Protokoll drucken. Gehen Sie zu Logger und suchen Sie nach der Protokollierungsmethode.

public void debug(final Marker marker, final Message msg) {
        logIfEnabled(FQCN, Level.DEBUG, marker, msg, msg ! = null ? msg.getThrowable() : null);
    }

Ich werde umziehen.

@PerformanceSensitive
    // NOTE: This is a hot method. Current implementation compiles to 29 bytes of byte code.
    // This is within the 35 byte MaxInlineSize threshold. Modify with care!
    private void logMessageTrackRecursion(final String fqcn,
                                          final Level level,
                                          final Marker marker,
                                          final Message msg,
                                          final Throwable throwable) {
        try {
            incrementRecursionDepth(); // LOG4J2-1518, LOG4J2-2031
            tryLogMessage(fqcn, level, marker, msg, throwable);
        } finally {
            decrementRecursionDepth();
        }
    }

Wie Sie sehen, wird die Anzahl der Anrufe aufgezeichnet, bevor das Protokoll gedruckt wird. Schauen wir uns die tryLogMessage () -Methode an.

@PerformanceSensitive
    // NOTE: This is a hot method. Current implementation compiles to 26 bytes of byte code.
    // This is within the 35 byte MaxInlineSize threshold. Modify with care!
    private void tryLogMessage(final String fqcn,
                               final Level level,
                               final Marker marker,
                               final Message msg,
                               final Throwable throwable) {
        try {
            logMessage(fqcn, level, marker, msg, throwable);
        } catch (final Exception e) {
            // LOG4J2-1990 Log4j2 suppresses all exceptions that occur once application called the logger
            handleLogMessageException(e, fqcn, msg);
        }
    }

Ich werde es bewegen.

@Override
    public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message,
            final Throwable t) {
        final Message msg = message == null ?  new SimpleMessage(Strings.EMPTY) : message;
        final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy();
        strategy.log(this, getName(), fqcn, marker, level, msg, t);
    }

Wie Sie sehen können, wird beim Drucken eines Protokolls die Protokolldruckstrategie aus der Konfiguration übernommen. Mal sehen, wie man eine ReliabilityStrategy erstellt. Die Standardimplementierungsklasse ist DefaultReliabilityStrategy. Werfen wir einen Blick auf die tatsächliche Protokolldruckmethode.

@Override
    public void log(final Supplier<LoggerConfig> reconfigured, final String loggerName, final String fqcn, final Marker marker, final Level level,
            final Message data, final Throwable t) {
        loggerConfig.log(loggerName, fqcn, marker, level, data, t);
    }

Die eigentliche Protokolldruckmethode ist in config implementiert. Es ist ziemlich ungewöhnlich. Bitte schauen Sie genauer hin.

@PerformanceSensitive("allocation")
    public void log(final String loggerName, final String fqcn, final Marker marker, final Level level,
            final Message data, final Throwable t) {
        List<Property> props = null;
        if (! propertiesRequireLookup) {
            props = properties;
        } else { 
            if (properties ! = null) {
                props = new ArrayList<>(properties.size());
                final LogEvent event = Log4jLogEvent.newBuilder()
                        .setMessage(data)
                        .setMarker(marker)
                        .setLevel(level)
                        .setLoggerName(loggerName)
                        .setLoggerFqcn(fqcn)
                        .setThrown(t)
                        .build();
                for (int i = 0; i < properties.size(); i++) {
                    final Property prop = properties.get(i);
                    final String value = prop.isValueNeedsLookup() // since LOG4J2-1575
                            ? config.getStrSubstitutor().replace(event, prop.getValue()) //
                            : prop.getValue();
                    props.add(Property.createProperty(prop.getName(), value));
                }
            }
        }
        final LogEvent logEvent = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t);
        try {
            log(logEvent, LoggerConfigPredicate.ALL);
        } finally {
            // LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
            ReusableLogEventFactory.release(logEvent);
        }
    }

Es ist leicht zu erkennen, dass das Snippet vor dem Versuch das LogEvent erstellt hat und nur der Versuchsteil die Protokollierung implementiert.

private void processLogEvent(final LogEvent event, LoggerConfigPredicate predicate) {
        event.setIncludeLocation(isIncludeLocation());
        if (predicate.allow(this)) {
            callAppenders(event);
        }
        logParent(event, predicate);
    }

Schauen wir uns als nächstes die Methode callAppenders () an. Zunächst aus der append () -Methode von AsyncAppender.

/**
     * Actual writing occurs here.
     *
     * @param logEvent The LogEvent.
     */
    @Override
    public void append(final LogEvent logEvent) {
        if (! isStarted()) {
            throw new IllegalStateException("AsyncAppender " + getName() + " is not active");
        }
        final Log4jLogEvent memento = Log4jLogEvent.createMemento(logEvent, includeLocation);
        InternalAsyncUtil.makeMessageImmutable(logEvent.getMessage());
        if (! transfer(memento)) {
            if (blocking) {
                if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-1518, LOG4J2-2031
                    // If queue is full AND we are in a recursive call, call appender directly to prevent deadlock
                    AsyncQueueFullMessageUtil.logWarningToStatusLogger();
                    logMessageInCurrentThread(logEvent);
                } else { 
                    // delegate to the event router (which may discard, enqueue and block, or log in current thread)
                    final EventRoute route = asyncQueueFullPolicy.getRoute(thread.getId(), memento.getLevel());
                    route.logMessage(this, memento);
                }
            } else { 
                error("Appender " + getName() + " is unable to write primary appenders. queue is full");
                logToErrorAppenderIfNecessary(false, memento);
            }
        }
    }

Die wichtigen Schritte sind:

  1. Erstellen Sie ein LogEvent.
  2. Rufen Sie die Transfer () -Methode auf, um das LogEvent zur BlockingQueue hinzuzufügen.
  3. Wenn die BlockingQueue voll ist, aktivieren Sie die entsprechende Strategie.

In ähnlicher Weise werden Threads verwendet, um den asynchronen Verbrauch zu implementieren.

private class AsyncThread extends Log4jThread {

        private volatile boolean shutdown = false;
        private final List<AppenderControl> appenders;
        private final BlockingQueue<LogEvent> queue;

        public AsyncThread(final List<AppenderControl> appenders, final BlockingQueue<LogEvent> queue) {
            super("AsyncAppender-" + THREAD_SEQUENCE.getAndIncrement());
            this.appenders = appenders;
            this.queue = queue;
            setDaemon(true);
        }

        @Override
        public void run() {
            while (! shutdown) {
                LogEvent event;
                try {
                    event = queue.take();
                    if (event == SHUTDOWN_LOG_EVENT) {
                        shutdown = true;
                        continue;
                    }
                } catch (final InterruptedException ex) {
                    break; // LOG4J2-830
                }
                event.setEndOfBatch(queue.isEmpty());
                final boolean success = callAppenders(event);
                if (! success && errorAppender ! = null) { 
                    try { 
                        errorAppender.callAppender(event);
                    } catch (final Exception ex) {
                        // Silently accept the error.
                    }
                }
            }
            // Process any remaining items in the queue.
            LOGGER.trace("AsyncAppender.AsyncThread shutting down. Processing remaining {} queue events.",
                queue.size());
            int count = 0;
            int ignored = 0;
            while (! queue.isEmpty()) {
                try {
                    final LogEvent event = queue.take();
                    if (event instanceof Log4jLogEvent) {
                        final Log4jLogEvent logEvent = (Log4jLogEvent) event;
                        logEvent.setEndOfBatch(queue.isEmpty());
                        callAppenders(logEvent);
                        count++;
                    } else {
                        ignored++;
                        LOGGER.trace("Ignoring event of class {}", event.getClass().getName());
                    }
                } catch (final InterruptedException ex) {
                    // May have been interrupted to shut down.
                    // Here we ignore interrupts and try to process all remaining events.
                }
            }
            LOGGER.trace("AsyncAppender.AsyncThread stopped. Queue has {} events remaining. "
                + "Processed {} and ignored {} events since shutdown started.", queue.size(), count, ignored);
        }

        /**
         * Calls {@link AppenderControl#callAppender(LogEvent) callAppender} on all registered {@code AppenderControl}
         * objects, and returns {@code true} if at least one appender call was successful, {@code false} otherwise. Any
         * exceptions are silently ignored.
         *
         * @param event the event to forward to the registered appenders
         * @return {@code true} if at least one appender call succeeded, {@code false} otherwise
         */
        boolean callAppenders(final LogEvent event) {
            boolean success = false;
            for (final AppenderControl control : appenders) {
                try {
                    control.callAppender(event);
                    success = true;
                } catch (final Exception ex) {
                    // If no appender is successful the error appender will get it.
                }
            }
            return success;
        }

        public void shutdown() {
            shutdown = true;
            if (queue.isEmpty()) {
                queue.offer(SHUTDOWN_LOG_EVENT);
            }
            if (getState() == State.TIMED_WAITING || getState() == State.WAITING) {
                this.interrupt(); // LOG4J2-1422: if underlying appender is stuck in wait/sleep/join/park call
            }
        }
    }

Mal sehen, wie die run () -Methode funktioniert.

  1. Blockieren Sie den Thread, der das LogEvent erhält.
  2. Senden Sie ein LogEvent.
  3. Der Thread kann erst heruntergefahren werden, wenn alle Ereignisse in der Blockierungswarteschlange verbraucht wurden.

Asynchroner Logger

image.png

Beginnen wir mit der logMessage () -Methode von AsyncLogger.

public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message,
            final Throwable thrown) { 

        if (loggerDisruptor.isUseThreadLocals()) {
            logWithThreadLocalTranslator(fqcn, level, marker, message, thrown);
        } else { 
            // LOG4J2-1172: avoid storing non-JDK classes in ThreadLocals to avoid memory leaks in web apps
            logWithVarargTranslator(fqcn, level, marker, message, thrown);
        }
    }

Schauen wir uns die Methode logWithThreadLocalTranslator () an.

private void logWithThreadLocalTranslator(final String fqcn, final Level level, final Marker marker,
            final Message message, final Throwable thrown) {
        // Implementation note: this method is tuned for performance. MODIFY WITH CARE!

        final RingBufferLogEventTranslator translator = getCachedTranslator();
        initTranslator(translator, fqcn, level, marker, message, thrown);
        initTranslatorThreadValues(translator);
        publish(translator);
    }

Die Logik dieser Methode ist unkompliziert. Konvertiert protokollbezogene Informationen in RingBufferLogEvent und veröffentlicht sie in RingBuffer. RingBuffer kann als sperrfreie Warteschlange für Disruptor betrachtet werden. Sie können davon ausgehen, dass die Protokollinformationen nach der Veröffentlichung im RingBuffer von einer Verbrauchslogik verarbeitet werden. Es gibt zwei Möglichkeiten, diese Verbrauchslogik zu finden:

--Finden Sie den Ort, an dem Disruptor verwendet wird, und überprüfen Sie die Details. Dies kann jedoch leicht verwirrt werden. --Log4j 2-Logger haben normalerweise ihre eigene start () -Methode. Versuchen Sie es mit dieser start () -Methode.

In der start () -Methode befindet sich ein Snippet.

final RingBufferLogEventHandler[] handlers = {new RingBufferLogEventHandler()};
        disruptor.handleEventsWith(handlers);

Mal sehen, wie die RingBufferLogEventHandler-Klasse implementiert wird.

public class RingBufferLogEventHandler implements
        SequenceReportingEventHandler<RingBufferLogEvent>, LifecycleAware {

    private static final int NOTIFY_PROGRESS_THRESHOLD = 50;
    private Sequence sequenceCallback;
    private int counter;
    private long threadId = -1;

    @Override
    public void setSequenceCallback(final Sequence sequenceCallback) {
        this.sequenceCallback = sequenceCallback;
    }

    @Override
    public void onEvent(final RingBufferLogEvent event, final long sequence,
            final boolean endOfBatch) throws Exception {
        event.execute(endOfBatch);
        event.clear();

        // notify the BatchEventProcessor that the sequence has progressed.
        // Without this callback the sequence would not be progressed
        // until the batch has completely finished.
        if (++counter > NOTIFY_PROGRESS_THRESHOLD) {
            sequenceCallback.set(sequence);
            counter = 0;
        }
    }

    /**
     * Returns the thread ID of the background consumer thread, or {@code -1} if the background thread has not started
     * yet.
     * @return the thread ID of the background consumer thread, or {@code -1}
     */
    public long getThreadId() {
        return threadId;
    }

    @Override
    public void onStart() {
        threadId = Thread.currentThread().getId();
    }

    @Override
    public void onShutdown() {
    }
}

Dann haben Sie die folgende Schnittstelle.

/**
 * Callback interface to be implemented for processing events as they become available in the {@link RingBuffer}
 *
 * @param <T> event implementation storing the data for sharing during exchange or parallel coordination of an event.
 * @see BatchEventProcessor#setExceptionHandler(ExceptionHandler) if you want to handle exceptions propagated out of the handler.
 */
public interface EventHandler<T>
{
    /**
     * Called when a publisher has published an event to the {@link RingBuffer}
     *
     * @param event      published to the {@link RingBuffer}
     * @param sequence   of the event being processed 
     * @param endOfBatch flag to indicate if this is the last event in a batch from the {@link RingBuffer}
     * @throws Exception if the EventHandler would like the exception handled further up the chain.
     */
    void onEvent(T event, long sequence, boolean endOfBatch) throws Exception;
}

Aus den Kommentaren können Sie ersehen, dass die onEvent () -Methode die Verarbeitungslogik ist. Zurück zur onEvent () -Methode von RingBufferLogEventHandler gibt es eine execute () -Methode, wie unten gezeigt.

public void execute(final boolean endOfBatch) {
        this.endOfBatch = endOfBatch;
        asyncLogger.actualAsyncLog(this);
    }

Verwenden Sie diese Methode, um das Protokoll auszugeben. AsyncLogger scheint einfach zu sein, es verwendet nur Disruptor.

Plug-in-basierte Komponenten

In anderen Codefragmenten sehen Sie häufig Folgendes:

final PluginManager manager = new PluginManager(CATEGORY);
manager.collectPlugins(pluginPackages);

Zur besseren Erweiterbarkeit werden viele Log4j 2-Komponenten mit Plug-Ins hergestellt. Sie müssen Plug-in-basierte Komponenten während der Konfiguration laden.

Schauen Sie sich collectPlugins an.

 public void collectPlugins(final List<String> packages) {
        final String categoryLowerCase = category.toLowerCase();
        final Map<String, PluginType<? >> newPlugins = new LinkedHashMap<>();

        // First, iterate the Log4j2Plugin.dat files found in the main CLASSPATH
        Map<String, List<PluginType<? >>> builtInPlugins = PluginRegistry.getInstance().loadFromMainClassLoader();
        if (builtInPlugins.isEmpty()) {
            // If we didn't find any plugins above, someone must have messed with the log4j-core.jar.
            // Search the standard package in the hopes we can find our core plugins.
            builtInPlugins = PluginRegistry.getInstance().loadFromPackage(LOG4J_PACKAGES);
        }
        mergeByName(newPlugins, builtInPlugins.get(categoryLowerCase));

        // Next, iterate any Log4j2Plugin.dat files from OSGi Bundles
        for (final Map<String, List<PluginType<? >>> pluginsByCategory : PluginRegistry.getInstance().getPluginsByCategoryByBundleId().values()) {
            mergeByName(newPlugins, pluginsByCategory.get(categoryLowerCase));
        }

        // Next iterate any packages passed to the static addPackage method.
        for (final String pkg : PACKAGES) {
            mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));
        }
        // Finally iterate any packages provided in the configuration (note these can be changed at runtime).
        if (packages ! = null) {
            for (final String pkg : packages) {
                mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));
            }
        }

        LOGGER.debug("PluginManager '{}' found {} plugins", category, newPlugins.size());

        plugins = newPlugins;
    }

Die Verarbeitungslogik ist wie folgt.

  1. Laden Sie alle integrierten Plugins aus der Datei Log4j2Plugin.dat.
  2. Laden Sie alle Plugins aus der Datei Log4j2Plugin.dat der OSGi-Bundles.
  3. Laden Sie das an den Paketpfad übergebene Plug-In.
  4. Laden Sie abschließend das Plug-In aus den Einstellungen.

Logik ist nicht einfach. Als ich jedoch den Quellcode überprüfte, fand ich ihn interessant. Nach dem Laden des Log4j 2-Kern-Plug-Ins sieht es folgendermaßen aus:

PluginRegistry.getInstance().loadFromMainClassLoader()

Werfen wir einen Blick auf die Methode decodeCacheFiles ().

private Map<String, List<PluginType<? >>> decodeCacheFiles(final ClassLoader loader) {
        final long startTime = System.nanoTime();
        final PluginCache cache = new PluginCache();
        try {
            final Enumeration<URL> resources = loader.getResources(PluginProcessor.PLUGIN_CACHE_FILE);
            if (resources == null) {
                LOGGER.info("Plugin preloads not available from class loader {}", loader);
            } else { 
                cache.loadCacheFiles(resources);
            }
        } catch (final IOException ioe) {
            LOGGER.warn("Unable to preload plugins", ioe);
        }
        final Map<String, List<PluginType<? >>> newPluginsByCategory = new HashMap<>();
        int pluginCount = 0;
        for (final Map.Entry<String, Map<String, PluginEntry>> outer : cache.getAllCategories().entrySet()) {
            final String categoryLowerCase = outer.getKey();
            final List<PluginType<? >> types = new ArrayList<>(outer.getValue().size());
            newPluginsByCategory.put(categoryLowerCase, types);
            for (final Map.Entry<String, PluginEntry> inner : outer.getValue().entrySet()) {
                final PluginEntry entry = inner.getValue();
                final String className = entry.getClassName();
                try {
                    final Class<? > clazz = loader.loadClass(className);
                    final PluginType<? > type = new PluginType<>(entry, clazz, entry.getName());
                    types.add(type);
                    ++pluginCount;
                } catch (final ClassNotFoundException e) {
                    LOGGER.info("Plugin [{}] could not be loaded due to missing classes.", className, e);
                } catch (final LinkageError e) {
                    LOGGER.info("Plugin [{}] could not be loaded due to linkage error.", className, e);
                }
            }
        }

        final long endTime = System.nanoTime();
        final DecimalFormat numFormat = new DecimalFormat("#0.000000");
        final double seconds = (endTime - startTime) * 1e-9;
        LOGGER.debug("Took {} seconds to load {} plugins from {}",
            numFormat.format(seconds), pluginCount, loader);
        return newPluginsByCategory;
    }

Sie können sehen, dass alle erforderlichen Plugins aus derselben Datei geladen werden. PLUGIN_CACHE_FILE. Ich habe mich gefragt, warum das Plug-In aus einer Datei geladen wird, anstatt direkt von Reflect gescannt zu werden. Wenn Sie das Plug-In in eine Datei schreiben, ist es nicht erweiterbar. Also habe ich nach der Verwendung der statischen Variablen PLUGIN_CACHE_FILE gesucht. Dann fand ich eine [PluginProcessor] -Klasse (http://hannesdorfmann.com/annotation-processing/annotationprocessing101?spm=a2c65.11461447.0.0.15d96568eUttOF), die einen Annotationsprozessor verwendet.

/**
 * Annotation processor for pre-scanning Log4j 2 plugins.
 */
@SupportedAnnotationTypes("org.apache.logging.log4j.core.config.plugins. *")
public class PluginProcessor extends AbstractProcessor {

    // TODO: this could be made more abstract to allow for compile-time and run-time plugin processing

    /**
     * The location of the plugin cache data file. This file is written to by this processor, and read from by
     * {@link org.apache.logging.log4j.core.config.plugins.util.PluginManager}.
     */
    public static final String PLUGIN_CACHE_FILE =
            "META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat";

    private final PluginCache pluginCache = new PluginCache();

    @Override
    public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
        System.out.println("Processing annotations");
        try {
            final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Plugin.class);
            if (elements.isEmpty()) {
                System.out.println("No elements to process");
                return false;
            }
            collectPlugins(elements);
            writeCacheFile(elements.toArray(new Element[elements.size()]));
            System.out.println("Annotations processed");
            return true;
        } catch (final IOException e) {
            e.printStackTrace();
            error(e.getMessage());
            return false;
        } catch (final Exception ex) {
            ex.printStackTrace();
            error(ex.getMessage());
            return false;
        }
    }
}

(Die weniger wichtige Methode wird weggelassen)

In der process () -Methode können Sie sehen, dass die PluginProcessor-Klasse zuerst alle Plugins sammelt und in eine Datei schreibt. Dies spart Reflexionsaufwand.

Schauen Sie sich die Kommentare zum Plug-In an. Die Aufbewahrungsrichtlinie für das Plug-In lautet RUNTIME. Im Allgemeinen verwendet der Plugin-Prozessor RetentionPolicy.SOURCE und CLASS zusammen. Wenn Sie das Plug-In scannen und in eine Datei schreiben, ist es nicht sinnvoll, die Aufbewahrungsrichtlinie auf RUNTIME zu setzen. Da bin ich mir nicht sicher.

Zusammenfassung

Schließlich haben wir den Log4j 2-Code übergeben. Ich war beeindruckt von dem Designkonzept zur Verbesserung der Flexibilität durch Einstecken von Komponenten. Mit der rasanten Entwicklung der Internet-Technologie sind verschiedene Middlewares aufgetaucht. Wir müssen mehr über die Beziehung zwischen einem Code und einem anderen nachdenken. Es besteht kein Zweifel, dass die entkoppelte Beziehung die schönste ist.

Recommended Posts

Log4j 2 Grundlegendes zu den Konzepten des Protokollsystems
Informationen zur Protokollebene von java.util.logging.Logger
Vertiefte mein Verständnis der Zusammenführungsmethode
Informationen zur Beschreibungsreihenfolge der Java-Systemeigenschaften
Format der Protokollausgabe von Tomcat selbst in Tomcat 8
[Java] Beim Schreiben der Quelle ... Memorandum ①
Finden Sie mit Swift eine Annäherung an log (1 + x)