LOG4J2-2731 - Add a LevelPatternSelector
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LevelPatternSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LevelPatternSelector.java
new file mode 100644
index 0000000..0a08efb
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LevelPatternSelector.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.layout;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
+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.pattern.PatternFormatter;
+import org.apache.logging.log4j.core.pattern.PatternParser;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Selects the pattern to use based on the Level in the LogEvent.
+ */
+@Plugin(name = "LevelPatternSelector", category = Node.CATEGORY, elementType = PatternSelector.ELEMENT_TYPE, printObject = true)
+public class LevelPatternSelector implements PatternSelector{
+
+    /**
+     * Custom MarkerPatternSelector builder. Use the {@link LevelPatternSelector#newBuilder() builder factory method} to create this.
+     */
+    public static class Builder implements org.apache.logging.log4j.core.util.Builder<LevelPatternSelector> {
+
+        @PluginElement("PatternMatch")
+        private PatternMatch[] properties;
+
+        @PluginBuilderAttribute("defaultPattern")
+        private String defaultPattern;
+
+        @PluginBuilderAttribute(value = "alwaysWriteExceptions")
+        private boolean alwaysWriteExceptions = true;
+
+        @PluginBuilderAttribute(value = "disableAnsi")
+        private boolean disableAnsi;
+
+        @PluginBuilderAttribute(value = "noConsoleNoAnsi")
+        private boolean noConsoleNoAnsi;
+
+        @PluginConfiguration
+        private Configuration configuration;
+
+        @Override
+        public LevelPatternSelector build() {
+            if (defaultPattern == null) {
+                defaultPattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
+            }
+            if (properties == null || properties.length == 0) {
+                LOGGER.warn("No marker patterns were provided with PatternMatch");
+                return null;
+            }
+            return new LevelPatternSelector(properties, defaultPattern, alwaysWriteExceptions, disableAnsi,
+                    noConsoleNoAnsi, configuration);
+        }
+
+        public Builder setProperties(final PatternMatch[] properties) {
+            this.properties = properties;
+            return this;
+        }
+
+        public Builder setDefaultPattern(final String defaultPattern) {
+            this.defaultPattern = defaultPattern;
+            return this;
+        }
+
+        public Builder setAlwaysWriteExceptions(final boolean alwaysWriteExceptions) {
+            this.alwaysWriteExceptions = alwaysWriteExceptions;
+            return this;
+        }
+
+        public Builder setDisableAnsi(final boolean disableAnsi) {
+            this.disableAnsi = disableAnsi;
+            return this;
+        }
+
+        public Builder setNoConsoleNoAnsi(final boolean noConsoleNoAnsi) {
+            this.noConsoleNoAnsi = noConsoleNoAnsi;
+            return this;
+        }
+
+        public Builder setConfiguration(final Configuration configuration) {
+            this.configuration = configuration;
+            return this;
+        }
+
+    }
+
+    private final Map<String, PatternFormatter[]> formatterMap = new HashMap<>();
+
+    private final Map<String, String> patternMap = new HashMap<>();
+
+    private final PatternFormatter[] defaultFormatters;
+
+    private final String defaultPattern;
+
+    private static Logger LOGGER = StatusLogger.getLogger();
+
+    private final boolean requiresLocation;
+
+    /**
+     * @deprecated Use {@link #newBuilder()} instead. This will be private in a future version.
+     */
+    @Deprecated
+    public LevelPatternSelector(final PatternMatch[] properties, final String defaultPattern,
+                                 final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi,
+                                 final Configuration config) {
+        this(properties, defaultPattern, alwaysWriteExceptions, false, noConsoleNoAnsi, config);
+    }
+
+    private LevelPatternSelector(final PatternMatch[] properties, final String defaultPattern,
+                                 final boolean alwaysWriteExceptions, final boolean disableAnsi,
+                                 final boolean noConsoleNoAnsi, final Configuration config) {
+        boolean needsLocation = false;
+        final PatternParser parser = PatternLayout.createPatternParser(config);
+        for (final PatternMatch property : properties) {
+            try {
+                final List<PatternFormatter> list = parser.parse(property.getPattern(), alwaysWriteExceptions,
+                        disableAnsi, noConsoleNoAnsi);
+                PatternFormatter[] formatters = list.toArray(new PatternFormatter[0]);
+                formatterMap.put(property.getKey(), formatters);
+                for (int i = 0; !needsLocation && i < formatters.length; ++i) {
+                    needsLocation = formatters[i].requiresLocation();
+                }
+
+                patternMap.put(property.getKey(), property.getPattern());
+            } catch (final RuntimeException ex) {
+                throw new IllegalArgumentException("Cannot parse pattern '" + property.getPattern() + "'", ex);
+            }
+        }
+        try {
+            final List<PatternFormatter> list = parser.parse(defaultPattern, alwaysWriteExceptions, disableAnsi,
+                    noConsoleNoAnsi);
+            defaultFormatters = list.toArray(new PatternFormatter[0]);
+            this.defaultPattern = defaultPattern;
+            for (int i = 0; !needsLocation && i < defaultFormatters.length; ++i) {
+                needsLocation = defaultFormatters[i].requiresLocation();
+            }
+        } catch (final RuntimeException ex) {
+            throw new IllegalArgumentException("Cannot parse pattern '" + defaultPattern + "'", ex);
+        }
+        requiresLocation = needsLocation;
+    }
+
+    @Override
+    public boolean requiresLocation() {
+        return requiresLocation;
+    }
+
+    @Override
+    public PatternFormatter[] getFormatters(final LogEvent event) {
+        final Level level = event.getLevel();
+        if (level == null) {
+            return defaultFormatters;
+        }
+        for (final String key : formatterMap.keySet()) {
+            if (level.name().equalsIgnoreCase(key)) {
+                return formatterMap.get(key);
+            }
+        }
+        return defaultFormatters;
+    }
+
+    /**
+     * Creates a builder for a custom ScriptPatternSelector.
+     *
+     * @return a ScriptPatternSelector builder.
+     */
+    @PluginBuilderFactory
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    /**
+     * Deprecated, use {@link #newBuilder()} instead.
+     * @param properties
+     * @param defaultPattern
+     * @param alwaysWriteExceptions
+     * @param noConsoleNoAnsi
+     * @param configuration
+     * @return a new MarkerPatternSelector.
+     * @deprecated Use {@link #newBuilder()} instead.
+     */
+    @Deprecated
+    public static LevelPatternSelector createSelector(
+            final PatternMatch[] properties,
+            final String defaultPattern,
+            final boolean alwaysWriteExceptions,
+            final boolean noConsoleNoAnsi,
+            final Configuration configuration) {
+        final Builder builder = newBuilder();
+        builder.setProperties(properties);
+        builder.setDefaultPattern(defaultPattern);
+        builder.setAlwaysWriteExceptions(alwaysWriteExceptions);
+        builder.setNoConsoleNoAnsi(noConsoleNoAnsi);
+        builder.setConfiguration(configuration);
+        return builder.build();
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        boolean first = true;
+        for (final Map.Entry<String, String> entry : patternMap.entrySet()) {
+            if (!first) {
+                sb.append(", ");
+            }
+            sb.append("key=\"").append(entry.getKey()).append("\", pattern=\"").append(entry.getValue()).append("\"");
+            first = false;
+        }
+        if (!first) {
+            sb.append(", ");
+        }
+        sb.append("default=\"").append(defaultPattern).append("\"");
+        return sb.toString();
+    }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java
index 45b33b7..a332f1e 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java
@@ -38,7 +38,7 @@
     LoggerContext ctx = LoggerContext.getContext();
 
     @Test
-    public void testPatternSelector() throws Exception {
+    public void testMarkerPatternSelector() throws Exception {
         final PatternMatch[] patterns = new PatternMatch[1];
         patterns[0] = new PatternMatch("FLOW", "%d %-5p [%t]: ====== %C{1}.%M:%L %m ======%n");
         // @formatter:off
@@ -59,7 +59,7 @@
                 .setIncludeLocation(true)
                 .setMessage(new SimpleMessage("entry")).build();
         final String result1 = new FauxLogger().formatEvent(event1, layout);
-        final String expectSuffix1 = String.format("====== PatternSelectorTest.testPatternSelector:61 entry ======%n");
+        final String expectSuffix1 = String.format("====== PatternSelectorTest.testMarkerPatternSelector:61 entry ======%n");
         assertTrue("Unexpected result: " + result1, result1.endsWith(expectSuffix1));
         final LogEvent event2 = Log4jLogEvent.newBuilder() //
                 .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
@@ -70,4 +70,27 @@
         assertTrue("Unexpected result: " + result2, result2.endsWith(expectSuffix2));
     }
 
+    @Test
+    public void testLevelPatternSelector() throws Exception {
+        final PatternMatch[] patterns = new PatternMatch[1];
+        patterns[0] = new PatternMatch("TRACE", "%d %-5p [%t]: ====== %C{1}.%M:%L %m ======%n");
+        final PatternSelector selector = LevelPatternSelector.createSelector(patterns, "%d %-5p [%t]: %m%n", true, true, ctx.getConfiguration());
+        final PatternLayout layout = PatternLayout.newBuilder().setPatternSelector(selector)
+                .setConfiguration(ctx.getConfiguration()).build();
+        final LogEvent event1 = Log4jLogEvent.newBuilder() //
+                .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.layout.PatternSelectorTest$FauxLogger")
+                .setLevel(Level.TRACE) //
+                .setIncludeLocation(true)
+                .setMessage(new SimpleMessage("entry")).build();
+        final String result1 = new FauxLogger().formatEvent(event1, layout);
+        final String expectSuffix1 = String.format("====== PatternSelectorTest.testLevelPatternSelector:85 entry ======%n");
+        assertTrue("Unexpected result: " + result1, result1.endsWith(expectSuffix1));
+        final LogEvent event2 = Log4jLogEvent.newBuilder() //
+                .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
+                .setLevel(Level.INFO) //
+                .setMessage(new SimpleMessage("Hello, world 1!")).build();
+        final String result2 = new String(layout.toByteArray(event2));
+        final String expectSuffix2 = String.format("Hello, world 1!%n");
+        assertTrue("Unexpected result: " + result2, result2.endsWith(expectSuffix2));
+    }
 }
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index e830296..24a45ba 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -160,6 +160,9 @@
       </action>
     </release>
     <release version="2.13.0" date="2019-MM-DD" description="GA Release 2.13.0">
+      <action issue="LOG4J2-2731" dev="rgoers" type="add">
+        Add a Level Patttern Selector.
+      </action>
       <action issue="LOG4J2-2727" dev="rogers" type="fix" due-to="Clément Mathieu">
         Add setKey method to Kafka Appender Builder.
       </action>
diff --git a/src/site/asciidoc/manual/layouts.adoc b/src/site/asciidoc/manual/layouts.adoc
index 888b5b5..04386c0 100644
--- a/src/site/asciidoc/manual/layouts.adoc
+++ b/src/site/asciidoc/manual/layouts.adoc
@@ -1665,6 +1665,23 @@
 and a set of PatternMatch elements that identify the various patterns
 that can be selected.
 
+[#LevelPatternSelector]
+==== LevelPatternSelector
+
+The LevelPatternSelector selects patterns based on the log level of
+the log event. If the Level in the log event is equal to (ignoring case)
+ the name specified on the PatternMatch key attribute, then
+the pattern specified on that PatternMatch element will be used.
+
+[source,xml]
+----
+<PatternLayout>
+  <MarkerPatternSelector defaultPattern="[%-5level] %c{1.} %msg%n">
+    <PatternMatch key="FLOW" pattern="[%-5level] %c{1.} ====== %C{1.}.%M:%L %msg ======%n"/>
+  </MarkerPatternSelector>
+</PatternLayout>
+----
+
 [#MarkerPatternSelector]
 ==== MarkerPatternSelector