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