[JAVA] Log4j 2 Comprendre les concepts sous-jacents du système de journalisation

Dans cet article, nous décomposerons le code source de ** Log4j ** 2, un nouveau système de journalisation basé sur Apache Log4j, et approfondirons ses concepts sous-jacents.

Aperçu

Log4j 2 est un tout nouveau système de journalisation construit sur Apache Log4j en référence à l'architecture Logback. Je pense que c'est parce que Log4j est fondamentalement obsolète par Logback.

Pour les avantages de Log4j 2, consultez la documentation officielle Apache Log4j 2.

--Log4j 2 offre de nombreuses améliorations disponibles dans Logback tout en corrigeant certains des problèmes inhérents à l'architecture Logback. --API Separation: L'API Log4j 2 peut être utilisée avec l'implémentation Log4j 2, mais elle peut également être utilisée avant d'autres implémentations de journalisation telles que Logback (bien que personne ne le fasse). La façade.

Diagramme de classe Log4j 2:

image.png

Dans cet article, nous explorerons le code source de Log4j 2 sous quatre aspects: lancement, configuration, désynchronisation et composants basés sur des plug-ins.

Recherche de code source

Commencez

Principaux composants de 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();

Vous pouvez voir que LogManager est un composant important. Par conséquent, nous analyserons ici en détail le processus de démarrage de LogManager.

Utilisez l'extrait de code statique suivant pour lancer LogManager.

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

Cet extrait de code statique est implémenté comme suit:

  1. Premièrement, récupérez la loggerContextFactory en fonction des informations de configuration dans un fichier de configuration particulier.
  2. Si vous ne trouvez pas la classe d'implémentation pour LoggerContextFactory, utilisez la méthode GetProviders () de ProviderUtil pour charger le fournisseur. Ensuite, chargez la classe d'implémentation de LoggerContextFactory avec la méthode loadLoggerContextFactory () du fournisseur.
  3. Si le fournisseur ne parvient pas à charger la classe d'implémentation LoggerContextFactory ou si le fournisseur est vide, utilisez SimpleLoggerContextFactory comme LoggerContextFactory.

Chargez LoggerContextFactory en fonction du fichier de configuration

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

Dans cet extrait de code, LogManager obtient d'abord LoggerContextFactory à l'aide de l'élément de configuration «log4j2.loggerContextFactory» dans le fichier de configuration «log4j2.component.properties». Après avoir terminé les paramètres correspondants, instanciez l'objet LoggerContextFactory avec la méthode newCheckedInstanceOf (). L'extrait suivant montre comment cela fonctionne.

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

Par défaut, le fichier d'initialisation log4j2.component.properties n'existe pas. Par conséquent, vous devez obtenir le LoggerContextFactory par une autre méthode.

Utiliser le fournisseur pour instancier l'objet LoggerContextFactory

Le code est:

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

Il est intéressant que hasProviders et getProviders utilisent des méthodes thread-safe pour initialiser paresseusement l'objet ProviderUtil. Jetons un coup d'œil à la méthode 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();
            }
        }
    }

Voyons la méthode de construction.

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

L'initialisation signifie ici l'initialisation de l'objet fournisseur. Lorsque vous créez une nouvelle instance de providerUtil, l'objet fournisseur est instancié directement. Tout d'abord, récupérez les chargeurs de classe du fournisseur à l'aide de la méthode getClassLoaders (). Utilisez ensuite loadProviders (classLoader); pour charger la classe. La dernière étape de l'instanciation de providerUtil est une recherche uniforme de l'URL du fournisseur correspondant dans le fichier "META-INF / log4j-provider.properties". Pensez à charger le fournisseur à distance. La méthode loadProviders () ajoute le fournisseur à la liste PROVIDERS de ProviderUtil. Le fournisseur par défaut est org.apache.logging.log4j.core.impl.Log4jContextFactory.

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

Fait intéressant, lazy-init est verrouillé à l'aide de la méthode lockInterruptably (). Quelle est la différence entre lockInterruptably et Lock? Quelle est la différence entre lock et lockInterruptably?

lock acquiert d'abord le verrou, puis répond au signal d'interruption.

lockInterruptiblement répond immédiatement aux signaux d'interruption envoyés par d'autres threads, plutôt que de réacquérir ou de réacquérir un verrou.

ReentrantLock.lockInterruptiblement a un autre thread

La méthode Thread.interrupt () du thread en attente d'acquérir le verrou arrête l'attente et retourne immédiatement. Dans ce cas, le thread en attente n'acquiert pas le verrou. Au lieu de cela, il lève une InterruptedException. ReentrantLock.lock n'autorise pas les interruptions de thread lors de l'appel de la méthode Thread.interrupt (). Le thread continuera à réessayer l'acquisition du verrou même si Thread.isInterrupted est détecté. Les threads qui ne parviennent pas à acquérir le verrou seront ajoutés à la file d'attente. Enfin, lorsqu'un thread acquiert un verrou, il est mis dans un état interrompu et une interruption se produit.

Les commentaires ci-dessus méritent d'être signalés.

/**
     * 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;

Il s'avère que cet extrait de code est utilisé pour permettre à l'activateur OSGi de suspendre son lancement.

Revenez à LogManager.

Une fois le fournisseur installé, LogManager procédera à la liaison d'usine.

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

                }

Le processus de démarrage de LogManager se termine ici.

configuration

Pour obtenir un enregistreur sans utiliser slf4j

Logger logger = logManager.getLogger(xx.class)

Jetons un coup d'œil à la méthode getLogger ().

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

Jetons un coup d'œil à la méthode 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);
        }
    }

Comme mentionné précédemment, la méthode de fabrique est implémentée dans Log4jContextFactory. Jetons un coup d'œil à getContext.

Méthode:

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

Jetons un coup d'œil à la méthode 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);
    }

La méthode principale est reconfigure (). Bouge toi

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

Vous pouvez voir que chaque paramètre provient de Configuration Factory. Tout d'abord, jetons un œil à la méthode getInstance () de cette classe.

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

Vous pouvez voir que ConfigurationFactory utilise PluginManager pour l'initialisation. PluginManager charge une sous-classe de ConfigurationFactory. Les sous-classes par défaut sont XmlConfigurationFactory, JsonConfigurationFactory et YamlConfigurationFactory. Ceux-ci sont chargés sous forme de plug-ins.

Revenez à la méthode reconfigure (). Vous pouvez voir que LogManager appelle la méthode getConfiguration () après avoir obtenu une instance de ConfigurationFactory.

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

Jetons un coup d'œil à la méthode getConfiguration (). Veillez à ne pas vous confondre car getConfiguration () sera appelée plusieurs fois. Si vous n'êtes pas sûr, essayez le débogage.

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 obtient ensuite les paramètres de la fabrique de configuration précédemment chargée.

Revenez à la méthode reconfigure (). La méthode suivante à appeler est setConfiguration (), et spécifiez la configuration obtenue précédemment comme argument d'entrée.

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

L'étape la plus importante de cette méthode est config.start, qui analyse la configuration.

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

Le processus comprend les étapes suivantes:

  1. Obtenez le plug-in de niveau de journalisation
  2. Initialisation
  3. Initialisation de l'annonceur
  4. Configuration

Dans l'étape d'initialisation, la méthode setup () est appelée. Vous devez remplacer la méthode setup (). L'opération est expliquée en utilisant XMLConfiguration comme exemple.

@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;
    }

La méthode importante constructHierarchy () est utilisée ici. Jetons un coup d'œil.

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

Comme vous pouvez le voir, c'est un processus de traversée d'arbre. Bien entendu, le fichier de configuration est fourni au format XML. Les documents XML ont une structure hiérarchique et peuvent être interprétés conceptuellement comme une structure arborescente. Revenez à la méthode start (). Jetons un coup d'œil à la méthode 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();
    }

Vous pouvez voir que cette méthode analyse la configuration précédemment obtenue et insère le résultat au bon endroit. Revenez à la méthode start (). Une fois configuré, vous devez lancer l'enregistreur et l'appender.

Asynchronisation

Appender asynchrone

image.png

AsyncAppender est une excellente fonctionnalité qui le distingue des autres services de journalisation. Tout d'abord, regardons le mécanisme en imprimant le journal. Accédez à Logger et recherchez la méthode de journalisation.

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

Je vais bouger.

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

Comme vous pouvez le voir, le nombre d'appels est enregistré avant l'impression du journal. Jetons un coup d'œil à la méthode tryLogMessage ().

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

Je vais le déplacer.

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

Comme vous pouvez le voir, lors de l'impression d'un journal, la stratégie d'impression du journal est tirée de config. Voyons comment créer une ReliabilityStrategy. La classe d'implémentation par défaut est DefaultReliabilityStrategy. Jetons un coup d'œil à la méthode réelle d'impression des journaux.

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

La méthode réelle d'impression du journal est implémentée dans config. C'est assez inhabituel. Veuillez regarder de plus près.

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

Il est facile de voir que l'extrait de code avant l'essai a créé le LogEvent et que seule la partie try implémente la journalisation.

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

Ensuite, regardons la méthode callAppenders (). Tout d'abord, à partir de la méthode append () d'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);
            }
        }
    }

Les étapes importantes sont:

  1. Créez un LogEvent.
  2. Appelez la méthode Transfer () pour ajouter le LogEvent à BlockingQueue.
  3. Si BlockingQueue est plein, activez la stratégie correspondante.

De même, les threads sont utilisés pour implémenter une consommation asynchrone.

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
            }
        }
    }

Voyons comment fonctionne la méthode run ().

  1. Bloquez le thread qui obtient le LogEvent.
  2. Envoyez un LogEvent.
  3. Le thread ne peut pas être arrêté tant que tous les événements de la file d'attente de blocage n'ont pas été consommés.

Enregistreur asynchrone

image.png

Commençons par la méthode logMessage () d'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);
        }
    }

Jetons un coup d'œil à la méthode logWithThreadLocalTranslator ().

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

La logique de cette méthode est simple. Convertit les informations relatives au journal en RingBufferLogEvent et les publie sur RingBuffer. RingBuffer peut être considéré comme une file d'attente sans verrouillage pour Disruptor. On peut supposer que les informations du journal seront traitées par une logique de consommation après avoir été publiées sur le RingBuffer. Il existe deux manières de trouver cette logique de consommation:

Il y a un extrait de code dans la méthode start ().

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

Voyons comment la classe RingBufferLogEventHandler est implémentée.

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

Ensuite, vous aurez l'interface suivante.

/**
 * 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;
}

À partir des commentaires, vous pouvez voir que la méthode onEvent () est la logique de traitement. Pour revenir à la méthode onEvent () de RingBufferLogEventHandler, il existe une méthode execute () comme indiqué ci-dessous.

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

Utilisez cette méthode pour sortir le journal. AsyncLogger semble simple, il utilise juste Disruptor.

Composants basés sur des plug-ins

Dans d'autres extraits de code, vous verrez souvent quelque chose comme ceci:

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

Pour une meilleure extensibilité, de nombreux composants Log4j 2 sont réalisés avec des plug-ins. Vous devez charger des composants basés sur des plug-ins pendant la configuration.

Jetez un œil à collectPlugins.

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

La logique de traitement est la suivante.

  1. Chargez tous les plugins intégrés à partir du fichier Log4j2Plugin.dat.
  2. Chargez tous les plugins à partir du fichier OSGi Bundles Log4j2Plugin.dat.
  3. Chargez le plug-in transmis au chemin du package.
  4. Enfin, chargez le plug-in à partir des paramètres.

La logique n'est pas simple. Cependant, lorsque j'ai vérifié le code source, je l'ai trouvé intéressant. Après avoir chargé le plug-in principal Log4j 2, il ressemble à ceci:

PluginRegistry.getInstance().loadFromMainClassLoader()

Jetons un coup d'œil à la méthode 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;
    }

Vous pouvez voir que tous les plugins requis sont chargés à partir du même fichier. PLUGIN_CACHE_FILE. Je me demandais pourquoi le plug-in est chargé à partir d'un fichier au lieu d'être analysé directement par Reflect? Si vous écrivez le plug-in dans un fichier, il ne sera pas extensible. J'ai donc cherché comment utiliser la variable statique PLUGIN_CACHE_FILE. Ensuite, j'ai trouvé une classe PluginProcessor qui utilise un processeur d'annotations.

/**
 * 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;
        }
    }
}

(La méthode la moins importante est omise)

Dans la méthode process (), vous pouvez voir que la classe PluginProcessor collecte d'abord tous les plugins et les écrit dans un fichier. Cela évite les frais généraux de réflexion.

Jetez un œil aux commentaires sur le plug-in. La politique de rétention du plug-in est RUNTIME. En général, Plugin Processor utilise RetentionPolicy.SOURCE et CLASS ensemble. Si vous scannez le plug-in et l'écrivez dans un fichier, il est inutile de définir la stratégie de rétention sur RUNTIME. C'est quelque chose dont je ne suis pas sûr.

Résumé

Enfin, nous avons passé le code Log4j 2. J'ai été impressionné par le concept de conception d'amélioration de la flexibilité en branchant les composants. Avec le développement rapide de la technologie Internet, divers middlewares sont apparus. Nous devons réfléchir davantage à la relation entre un code et un autre. Il ne fait aucun doute que la relation découplée est la plus belle.

Recommended Posts

Log4j 2 Comprendre les concepts sous-jacents du système de journalisation
À propos du niveau de journalisation de java.util.logging.Logger
J'ai approfondi ma compréhension de la méthode de fusion
A propos de l'ordre de description des propriétés système Java
Format de la sortie du journal par Tomcat lui-même dans Tomcat 8
[Java] Lors de l'écriture du source ... Mémorandum ①
Trouvez la valeur approximative de log (1 + x) avec Swift