LOG4J2-3041 - Allow GelfLayout to use PatternSelectors
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java
index 14e71e1..38254f6 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java
@@ -144,6 +144,9 @@
         @PluginBuilderAttribute
         private String messagePattern = null;
 
+        @PluginElement("PatternSelector")
+        private PatternSelector patternSelector = null;
+
         public Builder() {
             super();
             setCharset(StandardCharsets.UTF_8);
@@ -176,12 +179,23 @@
                 checker = ListChecker.NOOP_CHECKER;
             }
             PatternLayout patternLayout = null;
+            if (messagePattern != null && patternSelector != null) {
+                LOGGER.error("A message pattern and PatternSelector cannot both be specified on GelfLayout, "
+                        + "ignoring message pattern");
+                messagePattern = null;
+            }
             if (messagePattern != null) {
                 patternLayout = PatternLayout.newBuilder().setPattern(messagePattern)
                         .setAlwaysWriteExceptions(includeStacktrace)
                         .setConfiguration(getConfiguration())
                         .build();
             }
+            if (patternSelector != null) {
+                patternLayout = PatternLayout.newBuilder().setPatternSelector(patternSelector)
+                        .setAlwaysWriteExceptions(includeStacktrace)
+                        .setConfiguration(getConfiguration())
+                        .build();
+            }
             return new GelfLayout(getConfiguration(), host, additionalFields, compressionType, compressionThreshold,
                     includeStacktrace, includeThreadContext, includeNullDelimiter, includeNewLineDelimiter, checker,
                     patternLayout);
@@ -310,6 +324,16 @@
         }
 
         /**
+         * The PatternSelector to use to format the message.
+         * @param patternSelector the PatternSelector.
+         * @return this builder
+         */
+        public B setPatternSelector(final PatternSelector patternSelector) {
+            this.patternSelector = patternSelector;
+            return asBuilder();
+        }
+
+        /**
          * A comma separated list of thread context keys to include;
          * @param mdcIncludes the list of keys.
          * @return this builder
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest2.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout2Test.java
similarity index 96%
rename from log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest2.java
rename to log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout2Test.java
index 2b3f429..f9f44f7 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest2.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout2Test.java
@@ -30,8 +30,8 @@
 
 import static org.junit.jupiter.api.Assertions.*;
 
-@LoggerContextSource("GelfLayoutTest2.xml")
-public class GelfLayoutTest2 {
+@LoggerContextSource("GelfLayout2Test.xml")
+public class GelfLayout2Test {
 
     @Test
     public void gelfLayout(final LoggerContext context, @Named final ListAppender list) throws IOException {
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest3.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout3Test.java
similarity index 94%
rename from log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest3.java
rename to log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout3Test.java
index fc9a404..6ebd27c 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest3.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayout3Test.java
@@ -32,10 +32,10 @@
 
 import static org.junit.jupiter.api.Assertions.*;
 
-@LoggerContextSource("GelfLayoutTest3.xml")
+@LoggerContextSource("GelfLayout3Test.xml")
 @UsingAnyThreadContext
 @Tag("json")
-public class GelfLayoutTest3 {
+public class GelfLayout3Test {
 
     @Test
     public void gelfLayout(final LoggerContext context, @Named final ListAppender list) throws IOException {
@@ -55,7 +55,7 @@
         assertNull(json.get("_requestId"));
         String message = json.get("full_message").asText();
         assertTrue(message.contains("loginId=rgoers"));
-        assertTrue(message.contains("GelfLayoutTest3"));
+        assertTrue(message.contains("GelfLayout3Test"));
     }
 
 }
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest3.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutPatternSelectorTest.java
similarity index 62%
copy from log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest3.java
copy to log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutPatternSelectorTest.java
index fc9a404..f3f5712 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest3.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutPatternSelectorTest.java
@@ -16,26 +16,32 @@
  */
 package org.apache.logging.log4j.core.layout;
 
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+
 import org.apache.logging.log4j.Logger;
 import org.apache.logging.log4j.ThreadContext;
 import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.junit.LoggerContextSource;
 import org.apache.logging.log4j.junit.Named;
 import org.apache.logging.log4j.junit.UsingAnyThreadContext;
+import org.apache.logging.log4j.spi.AbstractLogger;
 import org.apache.logging.log4j.test.appender.ListAppender;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 
-import java.io.IOException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
-@LoggerContextSource("GelfLayoutTest3.xml")
+@LoggerContextSource("GelfLayoutPatternSelectorTest.xml")
 @UsingAnyThreadContext
 @Tag("json")
-public class GelfLayoutTest3 {
+public class GelfLayoutPatternSelectorTest {
 
     @Test
     public void gelfLayout(final LoggerContext context, @Named final ListAppender list) throws IOException {
@@ -44,9 +50,10 @@
         ThreadContext.put("loginId", "rgoers");
         ThreadContext.put("internalId", "12345");
         logger.info("My Test Message");
-        final String gelf = list.getMessages().get(0);
+        logger.info(AbstractLogger.FLOW_MARKER, "My Test Message");
+        String gelf = list.getMessages().get(0);
         final ObjectMapper mapper = new ObjectMapper();
-        final JsonNode json = mapper.readTree(gelf);
+        JsonNode json = mapper.readTree(gelf);
         assertEquals("My Test Message", json.get("short_message").asText());
         assertEquals("myhost", json.get("host").asText());
         assertNotNull(json.get("_loginId"));
@@ -54,8 +61,21 @@
         assertNull(json.get("_internalId"));
         assertNull(json.get("_requestId"));
         String message = json.get("full_message").asText();
+        assertFalse(message.contains("====="));
         assertTrue(message.contains("loginId=rgoers"));
-        assertTrue(message.contains("GelfLayoutTest3"));
+        assertTrue(message.contains("GelfLayoutPatternSelectorTest"));
+        gelf = list.getMessages().get(1);
+        json = mapper.readTree(gelf);
+        assertEquals("My Test Message", json.get("short_message").asText());
+        assertEquals("myhost", json.get("host").asText());
+        assertNotNull(json.get("_loginId"));
+        assertEquals("rgoers", json.get("_loginId").asText());
+        assertNull(json.get("_internalId"));
+        assertNull(json.get("_requestId"));
+        message = json.get("full_message").asText();
+        assertTrue(message.contains("====="));
+        assertTrue(message.contains("loginId=rgoers"));
+        assertTrue(message.contains("GelfLayoutPatternSelectorTest"));
     }
 
 }
diff --git a/log4j-core/src/test/resources/GelfLayoutTest2.xml b/log4j-core/src/test/resources/GelfLayout2Test.xml
similarity index 100%
rename from log4j-core/src/test/resources/GelfLayoutTest2.xml
rename to log4j-core/src/test/resources/GelfLayout2Test.xml
diff --git a/log4j-core/src/test/resources/GelfLayoutTest3.xml b/log4j-core/src/test/resources/GelfLayout3Test.xml
similarity index 100%
rename from log4j-core/src/test/resources/GelfLayoutTest3.xml
rename to log4j-core/src/test/resources/GelfLayout3Test.xml
diff --git a/log4j-core/src/test/resources/GelfLayoutPatternSelectorTest.xml b/log4j-core/src/test/resources/GelfLayoutPatternSelectorTest.xml
new file mode 100644
index 0000000..7fe50f6
--- /dev/null
+++ b/log4j-core/src/test/resources/GelfLayoutPatternSelectorTest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+
+-->
+<Configuration status="WARN" name="GelfLayoutPatternSelectorTest">
+  <Appenders>
+    <List name="list">
+      <GelfLayout host="myhost" includeThreadContext="true" threadContextIncludes="requestId,loginId">
+        <MarkerPatternSelector defaultPattern="%d [%t] %-5p %X{requestId, loginId} %C{1.}.%M:%L - %m%n">
+          <PatternMatch key="FLOW" pattern="[%-5level] %X{requestId, loginId} ====== %C{1.}.%M:%L %msg ======%n"/>
+        </MarkerPatternSelector>
+        <KeyValuePair key="foo" value="FOO"/>
+        <KeyValuePair key="runtime" value="$${java:runtime}"/>
+      </GelfLayout>
+    </List>
+  </Appenders>
+
+  <Loggers>
+    <Root level="info">
+      <AppenderRef ref="list"/>
+    </Root>
+  </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index f4fb31d..3a01186 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -169,7 +169,9 @@
       </action>
     </release>
     <release version="2.15.0" date="2021-MM-DD" description="GA Release 2.15.0">
-
+      <action issue="LOG4J2-3041" dev="rgoers" type="update">
+        Allow a PatternSelector to be specified on GelfLayout.
+      </action>
     </release>
     <release version="2.14.1" date="2021-03-06" description="GA Release 2.14.1">
       <!-- FIXES -->
diff --git a/src/site/asciidoc/manual/layouts.adoc b/src/site/asciidoc/manual/layouts.adoc
index 44d2580..0ac0d78 100644
--- a/src/site/asciidoc/manual/layouts.adoc
+++ b/src/site/asciidoc/manual/layouts.adoc
@@ -228,9 +228,20 @@
 
 |messagePattern
 |String
-|The pattern to use to format the String. If not supplied only the text derived from the logging
-message will be used. See <<PatternLayout>> for information on the pattern
-strings
+|The pattern to use to format the String. A messagePattern and patternSelector cannot both be
+specified. If both are present the message pattern will be ignored and an error will be logged.
+If not supplied only the text derived from the logging message will be used. See
+link:#PatternLayout[PatternLayout]  for information on the pattern strings.
+
+|patternSelector
+|PatternSelector
+|The PatternSelector to use to format the String. A messagePattern and patternSelector cannot both be
+specified. If both are present the message pattern will be ignored and an error will be logged.
+If not supplied only the text derived from the logging message will be used.
+See link:#PatternSelectors[Pattern Selectors] for information on how to specify a
+PatternSelector.
+See link:#PatternLayout[PatternLayout] for information on the pattern strings.
+
 
 |threadContextExcludes
 |String
@@ -1785,6 +1796,7 @@
 </PatternLayout>
 ----
 
+[#PatternSelectors]
 === Pattern Selectors
 
 The PatternLayout can be configured with a PatternSelector to allow it