You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by pk...@apache.org on 2024/08/13 09:58:28 UTC

(logging-log4j2) branch fix/2.x/package-scanning created (now b941a99093)

This is an automated email from the ASF dual-hosted git repository.

pkarwasz pushed a change to branch fix/2.x/package-scanning
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


      at b941a99093 Improve plugin descriptor warnings

This branch includes the following new commits:

     new b941a99093 Improve plugin descriptor warnings

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(logging-log4j2) 01/01: Improve plugin descriptor warnings

Posted by pk...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

pkarwasz pushed a commit to branch fix/2.x/package-scanning
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit b941a990935c4db0c77849904c45d8dfd62b00ca
Author: Piotr P. Karwasz <pi...@karwasz.org>
AuthorDate: Tue Aug 13 11:58:04 2024 +0200

    Improve plugin descriptor warnings
    
    This improves status logger warnings about:
    
    * missing `Log4j2Plugin.dat` plugin descriptors,
    * usage of package scanning.
    
    The warnings are reworded in a way that they are more useful to end-users.
    If package scanning is enabled, linkage errors (almost exclusively due to optional dependencies) are logged at a `DEBUG` level instead of `WARN`.
    
    This addresses @vlsi suggestions from
    https://github.com/apache/jmeter/issues/5937#issuecomment-2271160800 and fixes #2835.
---
 .../log4j/core/config/AbstractConfiguration.java   | 32 +++++---
 .../core/config/plugins/util/PluginManager.java    | 78 ++++++++++++++----
 .../core/config/plugins/util/ResolverUtil.java     | 14 ++--
 .../2835_improve_plugin_descriptor_warnings.xml    |  8 ++
 src/site/antora/modules/ROOT/pages/faq.adoc        | 92 ++++++++++++++++++++++
 .../antora/modules/ROOT/pages/manual/plugins.adoc  |  3 +-
 6 files changed, 190 insertions(+), 37 deletions(-)

diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
index d7ee730c5f..3fe9bf13b1 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AbstractConfiguration.java
@@ -156,7 +156,7 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
         // The loggerContext is null for the NullConfiguration class.
         // this.loggerContext = new WeakReference(Objects.requireNonNull(loggerContext, "loggerContext is null"));
         this.configurationSource = Objects.requireNonNull(configurationSource, "configurationSource is null");
-        componentMap.put(Configuration.CONTEXT_PROPERTIES, propertyMap);
+        componentMap.put(CONTEXT_PROPERTIES, propertyMap);
         pluginManager = new PluginManager(Node.CATEGORY);
         rootNode = new Node();
         setState(State.INITIALIZING);
@@ -240,6 +240,10 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
                 LOGGER.info("Cannot initialize scripting support because this JRE does not support it.", e);
             }
         }
+        if (!pluginPackages.isEmpty()) {
+            LOGGER.warn("The use of package scanning to locate Log4j plugins is deprecated.\n"
+                    + "Please remove the `packages` attribute from your configuration file.");
+        }
         pluginManager.collectPlugins(pluginPackages);
         final PluginManager levelPlugins = new PluginManager(Level.CATEGORY);
         levelPlugins.collectPlugins(pluginPackages);
@@ -310,7 +314,7 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
     @Override
     public void start() {
         // Preserve the prior behavior of initializing during start if not initialized.
-        if (getState().equals(State.INITIALIZING)) {
+        if (getState() == State.INITIALIZING) {
             initialize();
         }
         LOGGER.debug("Starting configuration {}", this);
@@ -884,9 +888,9 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
     /**
      * Associates an Appender with a LoggerConfig. This method is synchronized in case a Logger with the same name is
      * being updated at the same time.
-     *
+     * <p>
      * Note: This method is not used when configuring via configuration. It is primarily used by unit tests.
-     *
+     * </p>
      * @param logger The Logger the Appender will be associated with.
      * @param appender The Appender.
      */
@@ -914,9 +918,9 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
     /**
      * Associates a Filter with a LoggerConfig. This method is synchronized in case a Logger with the same name is being
      * updated at the same time.
-     *
+     * <p>
      * Note: This method is not used when configuring via configuration. It is primarily used by unit tests.
-     *
+     * </p>
      * @param logger The Logger the Footer will be associated with.
      * @param filter The Filter.
      */
@@ -939,9 +943,9 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
     /**
      * Marks a LoggerConfig as additive. This method is synchronized in case a Logger with the same name is being
      * updated at the same time.
-     *
+     * <p>
      * Note: This method is not used when configuring via configuration. It is primarily used by unit tests.
-     *
+     * </p>
      * @param logger The Logger the Appender will be associated with.
      * @param additive True if the LoggerConfig should be additive, false otherwise.
      */
@@ -1098,7 +1102,7 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
      * @return The created object or null;
      */
     public Object createPluginObject(final PluginType<?> type, final Node node) {
-        if (this.getState().equals(State.INITIALIZING)) {
+        if (getState() == State.INITIALIZING) {
             return createPluginObject(type, node, null);
         } else {
             LOGGER.warn("Plugin Object creation is not allowed after initialization");
@@ -1116,7 +1120,7 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
      * . Parameters with {@link org.apache.logging.log4j.core.config.plugins.PluginElement} may be any plugin class or
      * an array of a plugin class. Collections and Maps are currently not supported, although the factory method that is
      * called can create these from an array.
-     *
+     * <p>
      * Plugins can also be created using a builder class that implements
      * {@link org.apache.logging.log4j.core.util.Builder}. In that case, a static method annotated with
      * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} should create the builder class, and
@@ -1124,16 +1128,18 @@ public abstract class AbstractConfiguration extends AbstractFilterable implement
      * of using PluginAttribute, one should use
      * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} where the default value can be
      * specified as the default field value instead of as an additional annotation parameter.
-     *
+     * </p>
+     * <p>
      * In either case, there are also annotations for specifying a
      * {@link org.apache.logging.log4j.core.config.Configuration} (
      * {@link org.apache.logging.log4j.core.config.plugins.PluginConfiguration}) or a
      * {@link org.apache.logging.log4j.core.config.Node} (
      * {@link org.apache.logging.log4j.core.config.plugins.PluginNode}).
-     *
+     * </p>
+     * <p>
      * Although the happy path works, more work still needs to be done to log incorrect parameters. These will generally
      * result in unhelpful InvocationTargetExceptions.
-     *
+     * </p>
      * @param type the type of plugin to create.
      * @param node the corresponding configuration node for this plugin to create.
      * @param event the LogEvent that spurred the creation of this plugin
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java
index e66fe4d9c6..cc6bb93734 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java
@@ -18,14 +18,18 @@ package org.apache.logging.log4j.core.config.plugins.util;
 
 import static org.apache.logging.log4j.util.Strings.toRootLowerCase;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.StackLocatorUtil;
 import org.apache.logging.log4j.util.Strings;
 
 /**
@@ -35,6 +39,13 @@ public class PluginManager {
 
     private static final CopyOnWriteArrayList<String> PACKAGES = new CopyOnWriteArrayList<>();
     private static final String LOG4J_PACKAGES = "org.apache.logging.log4j.core";
+    private static final String DEPRECATION_WARNING =
+            "The use of package scanning to locate Log4j plugins is deprecated.\n"
+                    + "Please remove the deprecated {} method call from {}.";
+    private static final String PLUGIN_DESCRIPTOR_DOC =
+            "See https://logging.apache.org/log4j/2.x/faq.html#plugin-descriptors for details.";
+    private static final String PLUGIN_REGISTRY_DOC =
+            "See https://logging.apache.org/log4j/2.x/manual/plugins.html#plugin-registry for details.";
 
     private static final Logger LOGGER = StatusLogger.getLogger();
 
@@ -63,7 +74,7 @@ public class PluginManager {
     public static void main(final String[] args) {
         System.err.println("ERROR: this tool is superseded by the annotation processor included in log4j-core.");
         System.err.println("If the annotation processor does not work for you, please see the manual page:");
-        System.err.println("http://logging.apache.org/log4j/2.x/manual/configuration.html#ConfigurationSyntax");
+        System.err.println("https://logging.apache.org/log4j/2.x/manual/configuration.html#ConfigurationSyntax");
         System.exit(-1);
     }
 
@@ -72,7 +83,9 @@ public class PluginManager {
      *
      * @param p The package name. Ignored if {@code null} or empty.
      */
+    @Deprecated
     public static void addPackage(final String p) {
+        LOGGER.warn(DEPRECATION_WARNING, "PluginManager.addPackage()", StackLocatorUtil.getStackTraceElement(2));
         if (Strings.isBlank(p)) {
             return;
         }
@@ -84,7 +97,9 @@ public class PluginManager {
      *
      * @param packages collection of package names to add. Empty and null package names are ignored.
      */
+    @Deprecated
     public static void addPackages(final Collection<String> packages) {
+        LOGGER.warn(DEPRECATION_WARNING, "PluginManager.addPackages()", StackLocatorUtil.getStackTraceElement(2));
         for (final String pkg : packages) {
             if (Strings.isNotBlank(pkg)) {
                 PACKAGES.addIfAbsent(pkg);
@@ -125,10 +140,6 @@ public class PluginManager {
      * @since 2.1
      */
     public void collectPlugins(final List<String> packages) {
-        if (isNotEmpty(packages) || isNotEmpty(PACKAGES)) {
-            LOGGER.warn(
-                    "The use of package scanning to locate plugins is deprecated and will be removed in a future release");
-        }
         final String categoryLowerCase = toRootLowerCase(category);
         final Map<String, PluginType<?>> newPlugins = new LinkedHashMap<>();
 
@@ -138,28 +149,61 @@ public class PluginManager {
         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.
+            LOGGER.warn(
+                    "No Log4j plugin descriptor was found in the classpath.\n"
+                            + "Falling back to scanning the `{}` package.\n{}",
+                    LOG4J_PACKAGES,
+                    PLUGIN_DESCRIPTOR_DOC);
             builtInPlugins = PluginRegistry.getInstance().loadFromPackage(LOG4J_PACKAGES);
         }
-        mergeByName(newPlugins, builtInPlugins.get(categoryLowerCase));
+        mergeByName(newPlugins, builtInPlugins.get(categoryLowerCase), null);
 
         // 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));
+            mergeByName(newPlugins, pluginsByCategory.get(categoryLowerCase), null);
         }
 
+        final List<String> scannedPluginClassNames = new ArrayList<>();
         // Next iterate any packages passed to the static addPackage method.
         for (final String pkg : PACKAGES) {
             mergeByName(
                     newPlugins,
-                    PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));
+                    PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase),
+                    scannedPluginClassNames);
         }
         // 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));
+                        PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase),
+                        scannedPluginClassNames);
+            }
+        }
+        // Report package scanning deprecation.
+        if (!scannedPluginClassNames.isEmpty()) {
+            final Predicate<String> customPluginPredicate = PluginManager::isCustomPlugin;
+            final String standardPlugins = scannedPluginClassNames.stream()
+                    .filter(customPluginPredicate.negate())
+                    .collect(Collectors.joining("\n\t"));
+            if (!standardPlugins.isEmpty()) {
+                LOGGER.warn(
+                        "The plugin descriptors for the following '{}' plugins are missing:\n\t{}\n{}",
+                        category,
+                        standardPlugins,
+                        PLUGIN_DESCRIPTOR_DOC);
+            }
+            final String customPlugins = scannedPluginClassNames.stream()
+                    .filter(customPluginPredicate)
+                    .collect(Collectors.joining("\n\t"));
+            if (!customPlugins.isEmpty()) {
+                LOGGER.warn(
+                        "Some custom '{}' plugins are not properly registered:\n\t{}\n"
+                                + "Please consider reporting this to the maintainers of these plugins.\n{}",
+                        category,
+                        customPlugins,
+                        PLUGIN_REGISTRY_DOC);
             }
         }
 
@@ -168,7 +212,14 @@ public class PluginManager {
         plugins = newPlugins;
     }
 
-    private static void mergeByName(final Map<String, PluginType<?>> newPlugins, final List<PluginType<?>> plugins) {
+    private static boolean isCustomPlugin(final String className) {
+        return !className.startsWith("org.apache.logging.log4j") && !className.startsWith("org.apache.log4j");
+    }
+
+    private static void mergeByName(
+            final Map<String, PluginType<?>> newPlugins,
+            final List<PluginType<?>> plugins,
+            final List<String> mergedPluginClassNames) {
         if (plugins == null) {
             return;
         }
@@ -177,6 +228,9 @@ public class PluginManager {
             final PluginType<?> existing = newPlugins.get(key);
             if (existing == null) {
                 newPlugins.put(key, pluginType);
+                if (mergedPluginClassNames != null) {
+                    mergedPluginClassNames.add(pluginType.getPluginClass().getName());
+                }
             } else if (!existing.getPluginClass().equals(pluginType.getPluginClass())) {
                 LOGGER.warn(
                         "Plugin [{}] is already mapped to {}, ignoring {}",
@@ -186,8 +240,4 @@ public class PluginManager {
             }
         }
     }
-
-    private boolean isNotEmpty(final List<String> list) {
-        return list != null && !list.isEmpty();
-    }
 }
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtil.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtil.java
index 6cb3806cc9..bae33794a6 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtil.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtil.java
@@ -77,7 +77,7 @@ import org.osgi.framework.wiring.BundleWiring;
  * </pre>
  *
  * <p>
- * This class was copied and modified from Stripes - http://stripes.mc4j.org/confluence/display/stripes/Home
+ * This class was copied and modified from <a href="http://stripes.mc4j.org/confluence/display/stripes/Home">Stripes</a>.
  * </p>
  */
 public class ResolverUtil {
@@ -179,7 +179,7 @@ public class ResolverUtil {
     public void findInPackage(final Test test, String packageName) {
         packageName = packageName.replace('.', '/');
         final ClassLoader loader = getClassLoader();
-        Enumeration<URL> urls;
+        final Enumeration<URL> urls;
 
         try {
             urls = loader.getResources(packageName);
@@ -397,10 +397,6 @@ public class ResolverUtil {
         }
     }
 
-    /**
-     * @param jarStream
-     * @param source
-     */
     private void close(final JarInputStream jarStream, final Object source) {
         if (jarStream != null) {
             try {
@@ -473,8 +469,10 @@ public class ResolverUtil {
                     resourceMatches.add(url.toURI());
                 }
             }
-        } catch (final Throwable t) {
-            LOGGER.warn("Could not examine class {}", fqn, t);
+        } catch (final LinkageError e) {
+            LOGGER.debug("Could not scan class {}, since an optional dependency is missing.", fqn, e);
+        } catch (final Exception e) {
+            LOGGER.warn("Could not scan class {}.", fqn, e);
         }
     }
 
diff --git a/src/changelog/.2.x.x/2835_improve_plugin_descriptor_warnings.xml b/src/changelog/.2.x.x/2835_improve_plugin_descriptor_warnings.xml
new file mode 100644
index 0000000000..f410d78d47
--- /dev/null
+++ b/src/changelog/.2.x.x/2835_improve_plugin_descriptor_warnings.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns="https://logging.apache.org/xml/ns"
+       xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
+       type="fixed">
+  <issue id="2835" link="https://github.com/apache/logging-log4j2/issues/2835"/>
+  <description format="asciidoc">Improve missing plugin descriptor warnings.</description>
+</entry>
diff --git a/src/site/antora/modules/ROOT/pages/faq.adoc b/src/site/antora/modules/ROOT/pages/faq.adoc
index 7a17111df5..e3f5f584f3 100644
--- a/src/site/antora/modules/ROOT/pages/faq.adoc
+++ b/src/site/antora/modules/ROOT/pages/faq.adoc
@@ -214,3 +214,95 @@ When you are using Log4j with ProGuard/R8 enabled, you need to add the following
 ----
 -keep,allowoptimization class org.apache.logging.log4j.** { *; }
 ----
+
+[#plugin-descriptors]
+== Why am I receiving warnings about missing plugin descriptors?
+
+Since Log4j 2.0
+xref:manual/plugins.adoc[Log4j Plugins]
+should be registered in a:
+
+[source]
+----
+META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat
+----
+
+file on the classpath of the Java applications.
+
+Since Log4j 2.24.0, you will receive a warning if a plugin is not registered properly.
+The common causes for improperly registered plugins are:
+
+* One of the xref:components.adoc[Log4j artifacts] might be corrupted.
+If you are using a single-JAR application, this might be caused by an improper shading configuration.
+See <<shading>> on how to properly shade Log4j artifacts.
+* A custom Log4j plugin has not been properly registered.
+See xref:manual/plugins.adoc#plugin-registry[Registering plugins] for more details.
+
+[#single-jar]
+== How to correctly create single-JAR applications?
+
+There are two ways to create single-JAR applications: you can create
+<<jar-in-jar,JAR-in-JAR executable packages>>
+or
+<<shading,shaded JARs>>.
+See the sections below on how to properly use these techniques with Log4j Core.
+
+[#jar-in-jar]
+=== Jar-in-Jar
+
+The easiest and recommended way to create single-JAR applications based on Log4j Core is to include **unmodified** versions of Log4j artifacts inside the JAR file of your application.
+You can do it with the following build tool plugins:
+
+* https://docs.spring.io/spring-boot/maven-plugin/packaging.html[Spring Boot Maven Plugin],
+* https://docs.spring.io/spring-boot/gradle-plugin/packaging.html[Spring Boot Gradle Plugin],
+* https://github.com/sbt/sbt-onejar[sbt-onejar plugin].
+
+[#shading]
+=== Shading/Shadowing
+
+The shading process unwraps all the files contained in dependency JARs and copies them to the JAR containing the application.
+Since multiple JARs can contain files with the same name, you need to properly resolve the file naming conflicts that arise.
+
+If your application uses Log4j Core, the following conflicts must be resolved:
+
+`module-info.class`::
+If you are writing a JPMS application, you need to propertly merge these files.
+Otherwise, they should be discarded.
+
+`META-INF/MANIFEST.MF`::
++
+If you are writing a Java SE application, you only need to add:
++
+[source,manifest]
+----
+Multi-Release: true
+----
++
+to your application manifest.
++
+If you are writing an OSGi application, you need to properly merge the OSGi headers.
+
+`META-INF/DEPENDENCIES`::
+`META-INF/LICENSE`::
+`META-INF/NOTICE`::
+Ask your legal department on how to handle these files.
+
+`META-INF/services/*`::
+These files contain
+https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html[`ServiceLoader` descriptors].
+You need to properly merge them by concatenating conflicting files.
+
+`META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat`::
+These files contain Log4j plugin descriptors and need to be properly merged using the appropriate resource transformer for your shading plugin:
+
+https://maven.apache.org/plugins/maven-assembly-plugin/[Maven Assembly Plugin]:::
+https://github.com/sbt/sbt-assembly[SBT Assembly Plugin]:::
+We are not aware of any resource transformers capable of merging Log4j plugin descriptors.
+
+https://maven.apache.org/plugins/maven-shade-plugin/[Maven Shade Plugin]:::
+You need to use the
+https://logging.staged.apache.org/log4j/transform/log4j-transform-maven-shade-plugin-extensions.html#log4j-plugin-cache-transformer[Log4j Plugin Descriptor Transformer].
+
+https://gradleup.com/shadow/[Gradle Shadow Plugin]:::
+You need to use the
+https://github.com/GradleUp/shadow/blob/main/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/transformers/Log4j2PluginsCacheFileTransformer.groovy[`Log4j2PluginsCacheFileTransformer`].
diff --git a/src/site/antora/modules/ROOT/pages/manual/plugins.adoc b/src/site/antora/modules/ROOT/pages/manual/plugins.adoc
index 1f18c5c23b..1c33e97f32 100644
--- a/src/site/antora/modules/ROOT/pages/manual/plugins.adoc
+++ b/src/site/antora/modules/ROOT/pages/manual/plugins.adoc
@@ -105,8 +105,7 @@ Intended for simple plugins that receive less than a handful of values.
 +
 See {project-github-url}/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/CsvParameterLayout.java[`CsvParameterLayout.java`] for an example on `@PluginFactory` usage.
 
-a link:../javadoc/log4j-core/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.html[`@PluginBuilderFactory`]-annotated static method of return type
-link:../javadoc/log4j-core/org/apache/logging/log4j/core/util/Builder.html[`Builder<T>`]::
+a link:../javadoc/log4j-core/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.html[`@PluginBuilderFactory`]-annotated static method of return type link:../javadoc/log4j-core/org/apache/logging/log4j/core/util/Builder.html[`Builder<T>`]::
 What is expected to be received is modelled as fields of a builder class.
 Intended for more sophisticated wiring needs.
 +