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

rgoers pushed a commit to branch release-2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/release-2.x by this push:
     new 5a459dd  LOG4J2-3341 - Support specifying the level and appender 
references on the Logger as on property
5a459dd is described below

commit 5a459dda1e731dfa93198db63493c1cb1cd1e941
Author: Ralph Goers <[email protected]>
AuthorDate: Fri Feb 11 23:47:50 2022 -0700

    LOG4J2-3341 - Support specifying the level and appender references on the 
Logger as on property
---
 .../apache/logging/log4j/util/PropertiesUtil.java  |  24 +-
 log4j-core/revapi.json                             |  42 +++
 .../log4j/core/async/AsyncLoggerConfig.java        |  41 ++-
 .../logging/log4j/core/config/LoggerConfig.java    | 315 ++++++++++++++++++++-
 .../properties/PropertiesConfigurationBuilder.java |  55 ++--
 .../properties/PropertiesConfigurationTest.java    |  17 ++
 .../resources/LoggerLevelAppenderTest.properties   |   2 +
 ... => LoggerLevelSysPropsAppenderTest.properties} |   8 +-
 .../RootLoggerLevelAppenderTest.properties         |   3 +-
 src/changes/changes.xml                            |   4 +-
 src/site/xdoc/manual/configuration.xml.vm          |  22 +-
 11 files changed, 461 insertions(+), 72 deletions(-)

diff --git 
a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java 
b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java
index 0fe8502..250333b 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java
@@ -549,10 +549,32 @@ public final class PropertiesUtil {
      * @since 2.6
      */
     public static Map<String, Properties> partitionOnCommonPrefixes(final 
Properties properties) {
+        return partitionOnCommonPrefixes(properties, false);
+    }
+
+    /**
+     * Partitions a properties map based on common key prefixes up to the 
first period.
+     *
+     * @param properties properties to partition
+     * @param includeBaseKey when true if a key exists with no '.' the key 
will be included.
+     * @return the partitioned properties where each key is the common prefix 
(minus the period) and the values are
+     * new property maps without the prefix and period in the key
+     * @since 2.17.2
+     */
+    public static Map<String, Properties> partitionOnCommonPrefixes(final 
Properties properties,
+            final boolean includeBaseKey) {
         final Map<String, Properties> parts = new ConcurrentHashMap<>();
         for (final String key : properties.stringPropertyNames()) {
             final int idx = key.indexOf('.');
-            if (idx < 0) continue;
+            if (idx < 0) {
+                if (includeBaseKey) {
+                    if (!parts.containsKey(key)) {
+                        parts.put(key, new Properties());
+                    }
+                    parts.get(key).setProperty("", 
properties.getProperty(key));
+                }
+                continue;
+            }
             final String prefix = key.substring(0, idx);
             if (!parts.containsKey(prefix)) {
                 parts.put(prefix, new Properties());
diff --git a/log4j-core/revapi.json b/log4j-core/revapi.json
index 10e586d..9c7f874 100644
--- a/log4j-core/revapi.json
+++ b/log4j-core/revapi.json
@@ -51,6 +51,48 @@
         "justification": "LOG4J2-2486 - Require enabled script languages to be 
specified in a system property"
       },
       {
+        "code": "java.annotation.removed",
+        "old": "method org.apache.logging.log4j.core.config.LoggerConfig 
org.apache.logging.log4j.core.config.LoggerConfig::createLogger(boolean, 
org.apache.logging.log4j.Level, java.lang.String, java.lang.String, 
org.apache.logging.log4j.core.config.AppenderRef[], 
org.apache.logging.log4j.core.config.Property[], 
org.apache.logging.log4j.core.config.Configuration, 
org.apache.logging.log4j.core.Filter) @ 
org.apache.logging.log4j.core.async.AsyncLoggerConfig.RootLogger",
+        "new": "method org.apache.logging.log4j.core.config.LoggerConfig 
org.apache.logging.log4j.core.config.LoggerConfig::createLogger(boolean, 
org.apache.logging.log4j.Level, java.lang.String, java.lang.String, 
org.apache.logging.log4j.core.config.AppenderRef[], 
org.apache.logging.log4j.core.config.Property[], 
org.apache.logging.log4j.core.config.Configuration, 
org.apache.logging.log4j.core.Filter) @ 
org.apache.logging.log4j.core.async.AsyncLoggerConfig.RootLogger",
+        "annotation": 
"@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+        "justification": "LoggerConfig now uses a Builder"
+      },
+      {
+        "code": "java.annotation.removed",
+        "old": "method org.apache.logging.log4j.core.config.LoggerConfig 
org.apache.logging.log4j.core.async.AsyncLoggerConfig.RootLogger::createLogger(java.lang.String,
 org.apache.logging.log4j.Level, java.lang.String, 
org.apache.logging.log4j.core.config.AppenderRef[], 
org.apache.logging.log4j.core.config.Property[], 
org.apache.logging.log4j.core.config.Configuration, 
org.apache.logging.log4j.core.Filter)",
+        "new": "method org.apache.logging.log4j.core.config.LoggerConfig 
org.apache.logging.log4j.core.async.AsyncLoggerConfig.RootLogger::createLogger(java.lang.String,
 org.apache.logging.log4j.Level, java.lang.String, 
org.apache.logging.log4j.core.config.AppenderRef[], 
org.apache.logging.log4j.core.config.Property[], 
org.apache.logging.log4j.core.config.Configuration, 
org.apache.logging.log4j.core.Filter)",
+        "annotation": 
"@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+        "justification": "AsyncLoggerConfig.RootLogger now uses a Builder"
+      },
+      {
+        "code": "java.annotation.removed",
+        "old": "method org.apache.logging.log4j.core.config.LoggerConfig 
org.apache.logging.log4j.core.async.AsyncLoggerConfig::createLogger(boolean, 
org.apache.logging.log4j.Level, java.lang.String, java.lang.String, 
org.apache.logging.log4j.core.config.AppenderRef[], 
org.apache.logging.log4j.core.config.Property[], 
org.apache.logging.log4j.core.config.Configuration, 
org.apache.logging.log4j.core.Filter)",
+        "new": "method org.apache.logging.log4j.core.config.LoggerConfig 
org.apache.logging.log4j.core.async.AsyncLoggerConfig::createLogger(boolean, 
org.apache.logging.log4j.Level, java.lang.String, java.lang.String, 
org.apache.logging.log4j.core.config.AppenderRef[], 
org.apache.logging.log4j.core.config.Property[], 
org.apache.logging.log4j.core.config.Configuration, 
org.apache.logging.log4j.core.Filter)",
+        "annotation": 
"@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+        "justification": "AsyncLoggerConfig now uses a Builder"
+      },
+      {
+        "code": "java.annotation.removed",
+        "old": "method org.apache.logging.log4j.core.config.LoggerConfig 
org.apache.logging.log4j.core.config.LoggerConfig::createLogger(boolean, 
org.apache.logging.log4j.Level, java.lang.String, java.lang.String, 
org.apache.logging.log4j.core.config.AppenderRef[], 
org.apache.logging.log4j.core.config.Property[], 
org.apache.logging.log4j.core.config.Configuration, 
org.apache.logging.log4j.core.Filter) @ 
org.apache.logging.log4j.core.config.LoggerConfig.RootLogger",
+        "new": "method org.apache.logging.log4j.core.config.LoggerConfig 
org.apache.logging.log4j.core.config.LoggerConfig::createLogger(boolean, 
org.apache.logging.log4j.Level, java.lang.String, java.lang.String, 
org.apache.logging.log4j.core.config.AppenderRef[], 
org.apache.logging.log4j.core.config.Property[], 
org.apache.logging.log4j.core.config.Configuration, 
org.apache.logging.log4j.core.Filter) @ 
org.apache.logging.log4j.core.config.LoggerConfig.RootLogger",
+        "annotation": 
"@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+        "justification": "LoggerConfig now uses a Builder"
+      },
+      {
+        "code": "java.annotation.removed",
+        "old": "method org.apache.logging.log4j.core.config.LoggerConfig 
org.apache.logging.log4j.core.config.LoggerConfig.RootLogger::createLogger(java.lang.String,
 org.apache.logging.log4j.Level, java.lang.String, 
org.apache.logging.log4j.core.config.AppenderRef[], 
org.apache.logging.log4j.core.config.Property[], 
org.apache.logging.log4j.core.config.Configuration, 
org.apache.logging.log4j.core.Filter)",
+        "new": "method org.apache.logging.log4j.core.config.LoggerConfig 
org.apache.logging.log4j.core.config.LoggerConfig.RootLogger::createLogger(java.lang.String,
 org.apache.logging.log4j.Level, java.lang.String, 
org.apache.logging.log4j.core.config.AppenderRef[], 
org.apache.logging.log4j.core.config.Property[], 
org.apache.logging.log4j.core.config.Configuration, 
org.apache.logging.log4j.core.Filter)",
+        "annotation": 
"@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+        "justification": "LoggerConfig.RootLogger now uses a Builder"
+      },
+      {
+        "code": "java.annotation.removed",
+        "old": "method org.apache.logging.log4j.core.config.LoggerConfig 
org.apache.logging.log4j.core.config.LoggerConfig::createLogger(boolean, 
org.apache.logging.log4j.Level, java.lang.String, java.lang.String, 
org.apache.logging.log4j.core.config.AppenderRef[], 
org.apache.logging.log4j.core.config.Property[], 
org.apache.logging.log4j.core.config.Configuration, 
org.apache.logging.log4j.core.Filter)",
+        "new": "method org.apache.logging.log4j.core.config.LoggerConfig 
org.apache.logging.log4j.core.config.LoggerConfig::createLogger(boolean, 
org.apache.logging.log4j.Level, java.lang.String, java.lang.String, 
org.apache.logging.log4j.core.config.AppenderRef[], 
org.apache.logging.log4j.core.config.Property[], 
org.apache.logging.log4j.core.config.Configuration, 
org.apache.logging.log4j.core.Filter)",
+        "annotation": 
"@org.apache.logging.log4j.core.config.plugins.PluginFactory",
+        "justification": "LoggerConfig now uses a Builder"
+      },
+      {
         "code": "java.method.returnTypeChanged",
         "old": "method java.lang.String 
org.apache.logging.log4j.core.lookup.StrSubstitutor::resolveVariable(org.apache.logging.log4j.core.LogEvent,
 java.lang.String, java.lang.StringBuilder, int, int)",
         "new": "method org.apache.logging.log4j.core.lookup.LookupResult 
org.apache.logging.log4j.core.lookup.StrSubstitutor::resolveVariable(org.apache.logging.log4j.core.LogEvent,
 java.lang.String, java.lang.StringBuilder, int, int)",
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java
index f8d4102..2cd731b 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfig.java
@@ -32,6 +32,7 @@ import org.apache.logging.log4j.core.config.Node;
 import org.apache.logging.log4j.core.config.Property;
 import org.apache.logging.log4j.core.config.plugins.Plugin;
 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
 import org.apache.logging.log4j.core.config.plugins.PluginElement;
 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
@@ -71,6 +72,23 @@ import org.apache.logging.log4j.util.Strings;
 @Plugin(name = "asyncLogger", category = Node.CATEGORY, printObject = true)
 public class AsyncLoggerConfig extends LoggerConfig {
 
+    @PluginBuilderFactory
+    public static <B extends Builder<B>> B newAsyncBuilder() {
+        return new Builder<B>().asBuilder();
+    }
+
+    public static class Builder<B extends Builder<B>> extends 
LoggerConfig.Builder<B> {
+
+        @Override
+        public LoggerConfig build() {
+            final String name = getLoggerName().equals(ROOT) ? Strings.EMPTY : 
getLoggerName();
+            LevelAndRefs container = LoggerConfig.getLevelAndRefs(getLevel(), 
getRefs(), getLevelAndRefs(),
+                    getConfig());
+            return new AsyncLoggerConfig(name, container.refs,getFilter(), 
container.level, isAdditivity(),
+                    getProperties(), getConfig(), 
includeLocation(getIncludeLocation(), getConfig()));
+        }
+    }
+
     private static final ThreadLocal<Boolean> ASYNC_LOGGER_ENTERED = new 
ThreadLocal<Boolean>() {
         @Override
         protected Boolean initialValue() {
@@ -258,7 +276,7 @@ public class AsyncLoggerConfig extends LoggerConfig {
      * @return A new LoggerConfig.
      * @since 3.0
      */
-    @PluginFactory
+    @Deprecated
     public static LoggerConfig createLogger(
             @PluginAttribute(value = "additivity", defaultBoolean = true) 
final boolean additivity,
             @PluginAttribute("level") final Level level,
@@ -284,6 +302,23 @@ public class AsyncLoggerConfig extends LoggerConfig {
     @Plugin(name = "asyncRoot", category = Core.CATEGORY_NAME, printObject = 
true)
     public static class RootLogger extends LoggerConfig {
 
+        @PluginBuilderFactory
+        public static <B extends Builder<B>> B newAsyncRootBuilder() {
+            return new Builder<B>().asBuilder();
+        }
+
+        public static class Builder<B extends Builder<B>> extends 
RootLogger.Builder<B> {
+
+            @Override
+            public LoggerConfig build() {
+                LevelAndRefs container = 
LoggerConfig.getLevelAndRefs(getLevel(), getRefs(), getLevelAndRefs(),
+                        getConfig());
+                return new AsyncLoggerConfig(LogManager.ROOT_LOGGER_NAME, 
container.refs, getFilter(), container.level,
+                        isAdditivity(), getProperties(), getConfig(), 
includeLocation(getIncludeLocation(),
+                        getConfig()));
+            }
+        }
+
         /**
          * @deprecated use {@link #createLogger(String, Level, String, 
AppenderRef[], Property[], Configuration, Filter)}
          */
@@ -311,9 +346,9 @@ public class AsyncLoggerConfig extends LoggerConfig {
         }
 
         /**
-         * @since 3.0
+         *
          */
-        @PluginFactory
+        @Deprecated
         public static LoggerConfig createLogger(
                 @PluginAttribute("additivity") final String additivity,
                 @PluginAttribute("level") final Level level,
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java
index d7d7ce6..cb76aed 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java
@@ -31,15 +31,20 @@ import org.apache.logging.log4j.core.Core;
 import org.apache.logging.log4j.core.Filter;
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender;
+import org.apache.logging.log4j.core.appender.FileAppender;
 import org.apache.logging.log4j.core.async.AsyncLoggerConfig;
 import org.apache.logging.log4j.core.async.AsyncLoggerContext;
 import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
 import org.apache.logging.log4j.core.config.plugins.Plugin;
 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
 import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
 import org.apache.logging.log4j.core.config.plugins.PluginElement;
 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
 import 
org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.core.config.properties.PropertiesConfiguration;
 import org.apache.logging.log4j.core.filter.AbstractFilterable;
 import org.apache.logging.log4j.core.impl.DefaultLogEventFactory;
 import org.apache.logging.log4j.core.impl.LocationAware;
@@ -99,6 +104,136 @@ public class LoggerConfig extends AbstractFilterable 
implements LocationAware {
         }
     }
 
+    @PluginBuilderFactory
+    public static <B extends Builder<B>> B newBuilder() {
+        return new Builder<B>().asBuilder();
+    }
+
+    /**
+     * Builds LoggerConfig instances.
+     *
+     * @param <B>
+     *            The type to build
+     */
+    public static class Builder<B extends Builder<B>>
+            implements 
org.apache.logging.log4j.core.util.Builder<LoggerConfig> {
+
+        @PluginBuilderAttribute
+        private Boolean additivity;
+        @PluginBuilderAttribute
+        private Level level;
+        @PluginBuilderAttribute
+        private String levelAndRefs;
+        @PluginBuilderAttribute("name")
+        @Required(message = "Loggers cannot be configured without a name")
+        private String loggerName;
+        @PluginBuilderAttribute
+        private String includeLocation;
+        @PluginElement("AppenderRef")
+        private AppenderRef[] refs;
+        @PluginElement("Properties")
+        private Property[] properties;
+        @PluginConfiguration
+        private Configuration config;
+        @PluginElement("Filter")
+        private Filter filter;
+
+        public boolean isAdditivity() {
+            return additivity == null || additivity;
+        }
+
+        public B withAdditivity(boolean additivity) {
+            this.additivity = additivity;
+            return asBuilder();
+        }
+
+        public Level getLevel() {
+            return level;
+        }
+
+        public B withLevel(Level level) {
+            this.level = level;
+            return asBuilder();
+        }
+
+        public String getLevelAndRefs() {
+            return levelAndRefs;
+        }
+
+        public B withLevelAndRefs(String levelAndRefs) {
+            this.levelAndRefs = levelAndRefs;
+            return asBuilder();
+        }
+
+        public String getLoggerName() {
+            return loggerName;
+        }
+
+        public B withLoggerName(String loggerName) {
+            this.loggerName = loggerName;
+            return asBuilder();
+        }
+
+        public String getIncludeLocation() {
+            return includeLocation;
+        }
+
+        public B withIncludeLocation(String includeLocation) {
+            this.includeLocation = includeLocation;
+            return asBuilder();
+        }
+
+        public AppenderRef[] getRefs() {
+            return refs;
+        }
+
+        public B withRefs(AppenderRef[] refs) {
+            this.refs = refs;
+            return asBuilder();
+        }
+
+        public Property[] getProperties() {
+            return properties;
+        }
+
+        public B withProperties(Property[] properties) {
+            this.properties = properties;
+            return asBuilder();
+        }
+
+        public Configuration getConfig() {
+            return config;
+        }
+
+        public B withConfig(Configuration config) {
+            this.config = config;
+            return asBuilder();
+        }
+
+        public Filter getFilter() {
+            return filter;
+        }
+
+        public B withtFilter(Filter filter) {
+            this.filter = filter;
+            return asBuilder();
+        }
+
+        @Override
+        public LoggerConfig build() {
+            final String name = loggerName.equals(ROOT) ? Strings.EMPTY : 
loggerName;
+            LevelAndRefs container = LoggerConfig.getLevelAndRefs(level, refs, 
levelAndRefs, config);
+            boolean useLocation = includeLocation(includeLocation, config);
+            return new LoggerConfig(name, container.refs, filter, 
container.level, isAdditivity(), properties, config,
+                    useLocation);
+        }
+
+        @SuppressWarnings("unchecked")
+        public B asBuilder() {
+            return (B) this;
+        }
+    }
+
     /**
      * Default constructor.
      */
@@ -598,22 +733,22 @@ public class LoggerConfig extends AbstractFilterable 
implements LocationAware {
      * @return A new LoggerConfig.
      * @since 2.6
      */
-    @PluginFactory
+    @Deprecated
     public static LoggerConfig createLogger(
-         // @formatter:off
-        @PluginAttribute(value = "additivity", defaultBoolean = true) final 
boolean additivity,
-        @PluginAttribute("level") final Level level,
-        @Required(message = "Loggers cannot be configured without a name") 
@PluginAttribute("name") final String loggerName,
-        @PluginAttribute("includeLocation") final String includeLocation,
-        @PluginElement("AppenderRef") final AppenderRef[] refs,
-        @PluginElement("Properties") final Property[] properties,
-        @PluginConfiguration final Configuration config,
-        @PluginElement("Filter") final Filter filter
-        // @formatter:on
+            // @formatter:off
+            @PluginAttribute(value = "additivity", defaultBoolean = true) 
final boolean additivity,
+            @PluginAttribute("level") final Level level,
+            @Required(message = "Loggers cannot be configured without a name") 
@PluginAttribute("name") final String loggerName,
+            @PluginAttribute("includeLocation") final String includeLocation,
+            @PluginElement("AppenderRef") final AppenderRef[] refs,
+            @PluginElement("Properties") final Property[] properties,
+            @PluginConfiguration final Configuration config,
+            @PluginElement("Filter") final Filter filter
+            // @formatter:on
     ) {
         final String name = loggerName.equals(ROOT) ? Strings.EMPTY : 
loggerName;
         return new LoggerConfig(name, Arrays.asList(refs), filter, level, 
additivity, properties, config,
-            includeLocation(includeLocation, config));
+                includeLocation(includeLocation, config));
     }
 
     /**
@@ -651,7 +786,124 @@ public class LoggerConfig extends AbstractFilterable 
implements LocationAware {
     @Plugin(name = ROOT, category = Core.CATEGORY_NAME, printObject = true)
     public static class RootLogger extends LoggerConfig {
 
-        @PluginFactory
+        @PluginBuilderFactory
+        public static <B extends Builder<B>> B newRootBuilder() {
+            return new Builder<B>().asBuilder();
+        }
+
+        /**
+         * Builds LoggerConfig instances.
+         *
+         * @param <B>
+         *            The type to build
+         */
+        public static class Builder<B extends Builder<B>>
+                implements 
org.apache.logging.log4j.core.util.Builder<LoggerConfig> {
+
+            @PluginBuilderAttribute
+            private boolean additivity;
+            @PluginBuilderAttribute
+            private Level level;
+            @PluginBuilderAttribute
+            private String levelAndRefs;
+            @PluginBuilderAttribute
+            private String includeLocation;
+            @PluginElement("AppenderRef")
+            private AppenderRef[] refs;
+            @PluginElement("Properties")
+            private Property[] properties;
+            @PluginConfiguration
+            private Configuration config;
+            @PluginElement("Filter")
+            private Filter filter;
+
+            public boolean isAdditivity() {
+                return additivity;
+            }
+
+            public B withAdditivity(boolean additivity) {
+                this.additivity = additivity;
+                return asBuilder();
+            }
+
+            public Level getLevel() {
+                return level;
+            }
+
+            public B withLevel(Level level) {
+                this.level = level;
+                return asBuilder();
+            }
+
+            public String getLevelAndRefs() {
+                return levelAndRefs;
+            }
+
+            public B withLevelAndRefs(String levelAndRefs) {
+                this.levelAndRefs = levelAndRefs;
+                return asBuilder();
+            }
+
+            public String getIncludeLocation() {
+                return includeLocation;
+            }
+
+            public B withIncludeLocation(String includeLocation) {
+                this.includeLocation = includeLocation;
+                return asBuilder();
+            }
+
+            public AppenderRef[] getRefs() {
+                return refs;
+            }
+
+            public B withRefs(AppenderRef[] refs) {
+                this.refs = refs;
+                return asBuilder();
+            }
+
+            public Property[] getProperties() {
+                return properties;
+            }
+
+            public B withProperties(Property[] properties) {
+                this.properties = properties;
+                return asBuilder();
+            }
+
+            public Configuration getConfig() {
+                return config;
+            }
+
+            public B withConfig(Configuration config) {
+                this.config = config;
+                return asBuilder();
+            }
+
+            public Filter getFilter() {
+                return filter;
+            }
+
+            public B withtFilter(Filter filter) {
+                this.filter = filter;
+                return asBuilder();
+            }
+
+            @Override
+            public LoggerConfig build() {
+                LevelAndRefs container = LoggerConfig.getLevelAndRefs(level, 
refs, levelAndRefs, config);
+                return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, 
container.refs, filter, container.level,
+                        additivity, properties, config, 
includeLocation(includeLocation, config));
+            }
+
+            @SuppressWarnings("unchecked")
+            public B asBuilder() {
+                return (B) this;
+            }
+        }
+
+
+        @Deprecated
         public static LoggerConfig createLogger(
                 // @formatter:off
                 @PluginAttribute("additivity") final String additivity,
@@ -665,12 +917,47 @@ public class LoggerConfig extends AbstractFilterable 
implements LocationAware {
             final List<AppenderRef> appenderRefs = Arrays.asList(refs);
             final Level actualLevel = level == null ? Level.ERROR : level;
             final boolean additive = Booleans.parseBoolean(additivity, true);
-
             return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, 
filter, actualLevel, additive,
                     properties, config, includeLocation(includeLocation, 
config));
         }
     }
 
+    protected static LevelAndRefs getLevelAndRefs(Level level, AppenderRef[] 
refs, String levelAndRefs,
+            Configuration config) {
+        LevelAndRefs result = new LevelAndRefs();
+        if (levelAndRefs != null) {
+            if (config instanceof PropertiesConfiguration) {
+                if (level != null) {
+                    LOGGER.warn("Level is ignored when levelAndRefs syntax is 
used.");
+                }
+                if (refs != null && refs.length > 0) {
+                    LOGGER.warn("Appender references are ignored when 
levelAndRefs syntax is used");
+                }
+                final String[] parts = Strings.splitList(levelAndRefs);
+                result.level = Level.getLevel(parts[0]);
+                if (parts.length > 1) {
+                    List<AppenderRef> refList = new ArrayList<>();
+                    Arrays.stream(parts).skip(1).forEach((ref) ->
+                            refList.add(AppenderRef.createAppenderRef(ref, 
null, null)));
+                    result.refs = refList;
+                }
+            } else {
+                LOGGER.warn("levelAndRefs are only allowed in a properties 
configuration. The value is ignored.");
+                result.level = level;
+                result.refs = Arrays.asList(refs);
+            }
+        } else {
+            result.level = level;
+            result.refs = Arrays.asList(refs);
+        }
+        return result;
+    }
+
+    protected static class LevelAndRefs {
+        public Level level;
+        public List<AppenderRef> refs;
+    }
+
     protected enum LoggerConfigPredicate {
         ALL() {
             @Override
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java
index bc7c0cd..6564a29 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java
@@ -17,8 +17,12 @@
 
 package org.apache.logging.log4j.core.config.properties;
 
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
 import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.core.Appender;
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.core.config.ConfigurationException;
@@ -39,15 +43,9 @@ import 
org.apache.logging.log4j.core.config.builder.api.ScriptComponentBuilder;
 import 
org.apache.logging.log4j.core.config.builder.api.ScriptFileComponentBuilder;
 import 
org.apache.logging.log4j.core.filter.AbstractFilter.AbstractFilterBuilder;
 import org.apache.logging.log4j.core.util.Builder;
-import org.apache.logging.log4j.status.StatusLogger;
 import org.apache.logging.log4j.util.PropertiesUtil;
 import org.apache.logging.log4j.util.Strings;
 
-import java.util.Map;
-import java.util.Objects;
-import java.util.Properties;
-import java.util.concurrent.TimeUnit;
-
 /**
  * Helper builder for parsing properties files into a PropertiesConfiguration.
  *
@@ -56,7 +54,6 @@ import java.util.concurrent.TimeUnit;
 public class PropertiesConfigurationBuilder extends ConfigurationBuilderFactory
     implements Builder<PropertiesConfiguration> {
 
-    private static final Logger LOGGER = StatusLogger.getLogger();
     private static final String ADVERTISER_KEY = "advertiser";
     private static final String STATUS_KEY = "status";
     private static final String SHUTDOWN_HOOK = "shutdownHook";
@@ -174,20 +171,23 @@ public class PropertiesConfigurationBuilder extends 
ConfigurationBuilderFactory
                 }
             }
         } else {
-            final Properties context = 
PropertiesUtil.extractSubset(rootProperties, "logger");
-            final Map<String, Properties> loggers = 
PropertiesUtil.partitionOnCommonPrefixes(context);
+
+            final Map<String, Properties> loggers = PropertiesUtil
+                    
.partitionOnCommonPrefixes(PropertiesUtil.extractSubset(rootProperties, 
"logger"), true);
             for (final Map.Entry<String, Properties> entry : 
loggers.entrySet()) {
                 final String name = entry.getKey().trim();
                 if (!name.equals(LoggerConfig.ROOT)) {
-                    final Properties loggerProps = entry.getValue();
-                    useSyntheticLevelAndAppenderRefs(name, loggerProps, 
context);
-                    builder.add(createLogger(name, loggerProps));
+                    builder.add(createLogger(name, entry.getValue()));
                 }
             }
         }
 
+        String rootProp = rootProperties.getProperty("rootLogger");
         final Properties props = PropertiesUtil.extractSubset(rootProperties, 
"rootLogger");
-        useSyntheticLevelAndAppenderRefs("rootLogger", props, rootProperties);
+        if (rootProp != null) {
+            props.setProperty("", rootProp);
+            rootProperties.remove("rootLogger");
+        }
         if (props.size() > 0) {
             builder.add(createRootLogger(props));
         }
@@ -197,23 +197,6 @@ public class PropertiesConfigurationBuilder extends 
ConfigurationBuilderFactory
         return builder.build(false);
     }
 
-    private static void useSyntheticLevelAndAppenderRefs(final String 
propertyName, final Properties loggerProps, final Properties context) {
-        final String propertyValue = (String) context.remove(propertyName);
-        if (propertyValue != null) {
-            final String[] parts = propertyValue.split(",");
-            if (parts.length > 0) {
-                final String level = parts[0].trim();
-                loggerProps.setProperty("level", level);
-                LOGGER.debug("Using log level '{}' for logger '{}'", level, 
propertyName);
-                for (int i = 1; i < parts.length; i++) {
-                    final String appenderRef = parts[i].trim();
-                    LOGGER.debug("Adding synthetic appender ref '{}' for 
logger '{}'", appenderRef, propertyName);
-                    loggerProps.setProperty("appenderRef.$" + i + ".ref", 
appenderRef);
-                }
-            }
-        }
-    }
-
     private ScriptComponentBuilder createScript(final Properties properties) {
         final String name = (String) properties.remove("name");
         final String language = (String) properties.remove("language");
@@ -231,7 +214,7 @@ public class PropertiesConfigurationBuilder extends 
ConfigurationBuilderFactory
     }
 
     private AppenderComponentBuilder createAppender(final String key, final 
Properties properties) {
-        final String name = Objects.toString(properties.remove(CONFIG_NAME), 
key);
+        final String name = (String) properties.remove(CONFIG_NAME);
         if (Strings.isEmpty(name)) {
             throw new ConfigurationException("No name attribute provided for 
Appender " + key);
         }
@@ -274,6 +257,7 @@ public class PropertiesConfigurationBuilder extends 
ConfigurationBuilderFactory
     }
 
     private LoggerComponentBuilder createLogger(final String key, final 
Properties properties) {
+        final String levelAndRefs = properties.getProperty("");
         final String name = (String) properties.remove(CONFIG_NAME);
         final String location = (String) properties.remove("includeLocation");
         if (Strings.isEmpty(name)) {
@@ -306,10 +290,14 @@ public class PropertiesConfigurationBuilder extends 
ConfigurationBuilderFactory
         if (!Strings.isEmpty(additivity)) {
             loggerBuilder.addAttribute("additivity", additivity);
         }
+        if (levelAndRefs != null) {
+            loggerBuilder.addAttribute("levelAndRefs", levelAndRefs);
+        }
         return loggerBuilder;
     }
 
     private RootLoggerComponentBuilder createRootLogger(final Properties 
properties) {
+        final String levelAndRefs = properties.getProperty("");
         final String level = Strings.trimToNull((String) 
properties.remove("level"));
         final String type = (String) properties.remove(CONFIG_TYPE);
         final String location = (String) properties.remove("includeLocation");
@@ -333,6 +321,9 @@ public class PropertiesConfigurationBuilder extends 
ConfigurationBuilderFactory
             loggerBuilder = builder.newRootLogger(level);
         }
         addLoggersToComponent(loggerBuilder, properties);
+        if (levelAndRefs != null) {
+            loggerBuilder.addAttribute("levelAndRefs", levelAndRefs);
+        }
         return addFiltersToComponent(loggerBuilder, properties);
     }
 
diff --git 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationTest.java
 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationTest.java
index ec6fcd5..d975a00 100644
--- 
a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationTest.java
+++ 
b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationTest.java
@@ -31,6 +31,7 @@ import org.apache.logging.log4j.junit.LoggerContextSource;
 import org.apache.logging.log4j.junit.Named;
 import org.apache.logging.log4j.test.appender.ListAppender;
 import org.junit.jupiter.api.Test;
+import org.junitpioneer.jupiter.SetSystemProperty;
 
 import java.util.List;
 import java.util.Map;
@@ -130,4 +131,20 @@ class PropertiesConfigurationTest {
         assertEquals(firstEvents, secondEvents);
         assertEquals(1, firstEvents.size());
     }
+
+    @SetSystemProperty(key = "coreProps", value = "DEBUG, first, second")
+    @Test
+    @LoggerContextSource("LoggerLevelSysPropsAppenderTest.properties")
+    void testLoggerLevelSysPropsAppender(final LoggerContext context, @Named 
final ListAppender first,
+            @Named final ListAppender second, @Named final ListAppender third) 
{
+        context.getLogger(getClass()).atInfo().log("message");
+        context.getLogger(getClass()).atDebug().log("debug message");
+        context.getRootLogger().atInfo().log("test message");
+        final List<LogEvent> firstEvents = first.getEvents();
+        final List<LogEvent> secondEvents = second.getEvents();
+        assertEquals(firstEvents, secondEvents);
+        assertEquals(2, firstEvents.size());
+        final List<LogEvent> thirdEvents = third.getEvents();
+        assertEquals(1, thirdEvents.size());
+    }
 }
diff --git a/log4j-core/src/test/resources/LoggerLevelAppenderTest.properties 
b/log4j-core/src/test/resources/LoggerLevelAppenderTest.properties
index 231bb98..8ac9576 100644
--- a/log4j-core/src/test/resources/LoggerLevelAppenderTest.properties
+++ b/log4j-core/src/test/resources/LoggerLevelAppenderTest.properties
@@ -20,5 +20,7 @@ name = LoggerLevelAppenderTest
 logger.core-config-properties.name = 
org.apache.logging.log4j.core.config.properties
 # whitespace added for testing trimming
 logger.core-config-properties = INFO , first , second
+appender.first.name = first
 appender.first.type = List
+appender.second.name = second
 appender.second.type = List
diff --git a/log4j-core/src/test/resources/LoggerLevelAppenderTest.properties 
b/log4j-core/src/test/resources/LoggerLevelSysPropsAppenderTest.properties
similarity index 80%
copy from log4j-core/src/test/resources/LoggerLevelAppenderTest.properties
copy to log4j-core/src/test/resources/LoggerLevelSysPropsAppenderTest.properties
index 231bb98..ffa5c35 100644
--- a/log4j-core/src/test/resources/LoggerLevelAppenderTest.properties
+++ b/log4j-core/src/test/resources/LoggerLevelSysPropsAppenderTest.properties
@@ -19,6 +19,12 @@ status = off
 name = LoggerLevelAppenderTest
 logger.core-config-properties.name = 
org.apache.logging.log4j.core.config.properties
 # whitespace added for testing trimming
-logger.core-config-properties = INFO , first , second
+logger.core-config-properties = ${sys:coreProps:-ERROR , first}
+logger.core-config-properties.additivity = false
+appender.first.name = first
 appender.first.type = List
+appender.second.name = second
 appender.second.type = List
+appender.third.name = third
+appender.third.type = List
+rootLogger = INFO, third
diff --git 
a/log4j-core/src/test/resources/RootLoggerLevelAppenderTest.properties 
b/log4j-core/src/test/resources/RootLoggerLevelAppenderTest.properties
index 3e08203..f753b9a 100644
--- a/log4j-core/src/test/resources/RootLoggerLevelAppenderTest.properties
+++ b/log4j-core/src/test/resources/RootLoggerLevelAppenderTest.properties
@@ -18,4 +18,5 @@
 status = off
 name = RootLoggerLevelAppenderTest
 rootLogger = INFO,app
-appender.app.type = List
+appender.app.name = app
+appender.app.type = List
\ No newline at end of file
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 3cd72ec..580c6ea 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -230,8 +230,8 @@
       <action type="add" dev="ggregory" due-to="Gary Gregory">
         Add Configurator.setLevel(Logger, Level), setLevel(String, String), 
and setLevel(Class, Level).
       </action>
-      <action type="add" dev="mattsicker" issue="LOG4J2-3341">
-        Add shorthand syntax for properties configuration format for 
specifying a logger level and appender refs and implicit appender names for 
when no name is specified.
+      <action type="add" dev="rgoers" issue="LOG4J2-3341">
+        Add shorthand syntax for properties configuration format for 
specifying a logger level and appender refs.
       </action>
       <action issue="LOG4J2-3391" type="add" dev="ggregory" due-to="Gary 
Gregory">
         Add optional additional fields to NoSQLAppender.
diff --git a/src/site/xdoc/manual/configuration.xml.vm 
b/src/site/xdoc/manual/configuration.xml.vm
index fc84ee2..fa73922 100644
--- a/src/site/xdoc/manual/configuration.xml.vm
+++ b/src/site/xdoc/manual/configuration.xml.vm
@@ -940,20 +940,6 @@ Configuration:
               definition in the rolling file appender below. You then define 
each of the subcomponents below that
               wrapper element, as the TimeBasedTriggeringPolicy and 
SizeBasedTriggeringPolicy are defined below.
             </p>
-            <p>
-              As of version 2.17.2,
-              appenders can use the wrapper element as the implicit name of 
the component.
-              If no name is specified, the grouping element is used as the 
name. For example, the following
-              two properties snippets are equivalent.
-            </p>
-            <pre class="prettyprint linenums">
-appender.STDOUT.type = Console
-            </pre>
-            <pre class="prettyprint linenums">
-appender.console.type = Console
-appender.console.name = STDOUT
-            </pre>
-            <p>
               As of version 2.17.2,
               <tt>rootLogger</tt> and <tt>logger.<var>key</var></tt> 
properties can be specified to set the
               level and zero or more appender refs to create for that logger. 
The level and appender refs are
@@ -968,9 +954,9 @@ appender.file.type = File
 logger.app = INFO, stdout, file
 logger.app.name = com.example.app
 
-# expanded to:
-appender.stdout.type = Console
-appender.stdout.name = stdout
+# is equivalent to:
+# appender.stdout.type = Console
+# appender.stdout.name = stdout
 # ...
 appender.file.type = File
 appender.file.name = file
@@ -985,7 +971,7 @@ logger.app.appenderRef.$2.ref = file
               shutdownTimeout, status, verbose, and dest attributes. See <a 
href="#ConfigurationSyntax">Configuration Syntax</a>
               for the definitions of these attributes.
             </p>
-            <pre class="prettyprint linenums">
+          <pre class="prettyprint linenums">
 status = error
 dest = err
 name = PropertiesConfig

Reply via email to