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

vy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git

commit c846b21dcbf9c98d2a96526c3be253a038e3a79e
Author: Volkan Yazıcı <volkan.yaz...@gmail.com>
AuthorDate: Fri Jun 19 10:24:08 2020 +0200

    #335 Add fallbackKey to MessageResolver.
---
 .../json/template/resolver/MessageResolver.java    | 116 +++++++++++++--------
 .../json/template/JsonTemplateLayoutTest.java      |  50 +++++++++
 src/site/asciidoc/manual/json-template-layout.adoc |  23 +++-
 3 files changed, 142 insertions(+), 47 deletions(-)

diff --git 
a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MessageResolver.java
 
b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MessageResolver.java
index 54daefe..53dc7d9 100644
--- 
a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MessageResolver.java
+++ 
b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MessageResolver.java
@@ -18,6 +18,7 @@ package 
org.apache.logging.log4j.layout.json.template.resolver;
 
 import org.apache.logging.log4j.core.LogEvent;
 import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+import org.apache.logging.log4j.message.MapMessage;
 import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.MultiformatMessage;
 import org.apache.logging.log4j.message.ObjectMessage;
@@ -30,8 +31,9 @@ import org.apache.logging.log4j.util.StringBuilderFormattable;
  * <h3>Configuration</h3>
  *
  * <pre>
- * config      = [ stringified ]
+ * config      = [ stringified ] , [ fallbackKey ]
  * stringified = "stringified" -> boolean
+ * fallbackKey = "fallbackKey" -> string
  * </pre>
  *
  * <h3>Examples</h3>
@@ -54,6 +56,28 @@ import 
org.apache.logging.log4j.util.StringBuilderFormattable;
  *   "$resolver": "message"
  * }
  * </pre>
+ *
+ * Given the above configuration, a {@link SimpleMessage} will generate a
+ * <tt>"sample log message"</tt>, whereas a {@link MapMessage} will generate a
+ * <tt>{"action": "login", "sessionId": "87asd97a"}</tt>. Certain indexed log
+ * storage systems (e.g., <a
+ * href="https://www.elastic.co/elasticsearch/";>Elasticsearch</a>) will not
+ * allow both values to coexist due to type mismatch: one is a <tt>string</tt>
+ * while the other is an <tt>object</tt>. Here one can use a
+ * <tt>fallbackKey</tt> to work around the problem:
+ *
+ * <pre>
+ * {
+ *   "$resolver": "message",
+ *   "fallbackKey": "formattedMessage"
+ * }
+ * </pre>
+ *
+ * Using this configuration, a {@link SimpleMessage} will generate a
+ * <tt>{"formattedMessage": "sample log message"}</tt> and a {@link MapMessage}
+ * will generate a <tt>{"action": "login", "sessionId": "87asd97a"}</tt>. Note
+ * that both emitted JSONs are of type <tt>object</tt> and have no
+ * type-conflicting fields.
  */
 final class MessageResolver implements EventResolver {
 
@@ -72,9 +96,14 @@ final class MessageResolver implements EventResolver {
     private static EventResolver createInternalResolver(
             final TemplateResolverConfig config) {
         final boolean stringified = config.getBoolean("stringified", false);
+        final String fallbackKey = config.getString("fallbackKey");
+        if (stringified && fallbackKey != null) {
+            throw new IllegalArgumentException(
+                    "fallbackKey is not allowed when stringified is enable: " 
+ config);
+        }
         return stringified
-                ? MessageResolver::resolveString
-                : MessageResolver::resolveObject;
+                ? createStringResolver(fallbackKey)
+                : createObjectResolver(fallbackKey);
     }
 
     @Override
@@ -84,16 +113,27 @@ final class MessageResolver implements EventResolver {
         internalResolver.resolve(logEvent, jsonWriter);
     }
 
+    private static EventResolver createStringResolver(final String 
fallbackKey) {
+        return (final LogEvent logEvent, final JsonWriter jsonWriter) ->
+                resolveString(fallbackKey, logEvent, jsonWriter);
+    }
+
     private static void resolveString(
+            final String fallbackKey,
             final LogEvent logEvent,
             final JsonWriter jsonWriter) {
         final Message message = logEvent.getMessage();
-        resolveString(message, jsonWriter);
+        resolveString(fallbackKey, message, jsonWriter);
     }
 
     private static void resolveString(
+            final String fallbackKey,
             final Message message,
             final JsonWriter jsonWriter) {
+        if (fallbackKey != null) {
+            jsonWriter.writeObjectStart();
+            jsonWriter.writeObjectKey(fallbackKey);
+        }
         if (message instanceof StringBuilderFormattable) {
             final StringBuilderFormattable formattable =
                     (StringBuilderFormattable) message;
@@ -102,48 +142,35 @@ final class MessageResolver implements EventResolver {
             final String formattedMessage = message.getFormattedMessage();
             jsonWriter.writeString(formattedMessage);
         }
-    }
-
-    private static void resolveObject(
-            final LogEvent logEvent,
-            final JsonWriter jsonWriter) {
-
-        // Try SimpleMessage serializer.
-        final Message message = logEvent.getMessage();
-        if (writeSimpleMessage(jsonWriter, message)) {
-            return;
-        }
-
-        // Try MultiformatMessage serializer.
-        if (writeMultiformatMessage(jsonWriter, message)) {
-            return;
+        if (fallbackKey != null) {
+            jsonWriter.writeObjectEnd();
         }
+    }
 
-        // Try ObjectMessage serializer.
-        if (writeObjectMessage(jsonWriter, message)) {
-            return;
-        }
+    private static EventResolver createObjectResolver(final String 
fallbackKey) {
+        return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
 
-        // Fallback to plain Object write.
-        resolveString(logEvent, jsonWriter);
+            // Skip custom serializers for SimpleMessage.
+            final Message message = logEvent.getMessage();
+            final boolean simple = message instanceof SimpleMessage;
+            if (!simple) {
 
-    }
+                // Try MultiformatMessage serializer.
+                if (writeMultiformatMessage(jsonWriter, message)) {
+                    return;
+                }
 
-    private static boolean writeSimpleMessage(
-            final JsonWriter jsonWriter,
-            final Message message) {
+                // Try ObjectMessage serializer.
+                if (writeObjectMessage(jsonWriter, message)) {
+                    return;
+                }
 
-        // Check type.
-        if (!(message instanceof SimpleMessage)) {
-            return false;
-        }
-        final SimpleMessage simpleMessage = (SimpleMessage) message;
+            }
 
-        // Write message.
-        final String formattedMessage = simpleMessage.getFormattedMessage();
-        jsonWriter.writeString(formattedMessage);
-        return true;
+            // Fallback to plain String serializer.
+            resolveString(fallbackKey, logEvent, jsonWriter);
 
+        };
     }
 
     private static boolean writeMultiformatMessage(
@@ -165,16 +192,13 @@ final class MessageResolver implements EventResolver {
                 break;
             }
         }
-
-        // Write the formatted JSON, if supported.
-        if (jsonSupported) {
-            final String messageJson = 
multiformatMessage.getFormattedMessage(FORMATS);
-            jsonWriter.writeRawString(messageJson);
-            return true;
+        if (!jsonSupported) {
+            return false;
         }
 
-        // Fallback to the default message formatter.
-        resolveString((LogEvent) message, jsonWriter);
+        // Write the formatted JSON.
+        final String messageJson = 
multiformatMessage.getFormattedMessage(FORMATS);
+        jsonWriter.writeRawString(messageJson);
         return true;
 
     }
diff --git 
a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutTest.java
 
b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutTest.java
index 38ef58f..4419b58 100644
--- 
a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutTest.java
+++ 
b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutTest.java
@@ -38,6 +38,7 @@ import 
org.apache.logging.log4j.layout.json.template.JsonTemplateLayout.EventTem
 import org.apache.logging.log4j.layout.json.template.util.JsonReader;
 import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
 import org.apache.logging.log4j.layout.json.template.util.MapAccessor;
+import org.apache.logging.log4j.message.Message;
 import org.apache.logging.log4j.message.ObjectMessage;
 import org.apache.logging.log4j.message.SimpleMessage;
 import org.apache.logging.log4j.message.StringMapMessage;
@@ -321,6 +322,55 @@ public class JsonTemplateLayoutTest {
     }
 
     @Test
+    public void test_message_fallbackKey() {
+
+        // Create the event template.
+        final String eventTemplate = writeJson(Map(
+                "message", Map(
+                        "$resolver", "message",
+                        "fallbackKey", "formattedMessage")));
+
+        // Create the layout.
+        final JsonTemplateLayout layout = JsonTemplateLayout
+                .newBuilder()
+                .setConfiguration(CONFIGURATION)
+                .setEventTemplate(eventTemplate)
+                .build();
+
+        // Create a log event with a MapMessage.
+        final Message mapMessage = new StringMapMessage()
+                .with("key1", "val1");
+        final LogEvent mapMessageLogEvent = Log4jLogEvent
+                .newBuilder()
+                .setLoggerName(LOGGER_NAME)
+                .setLevel(Level.INFO)
+                .setMessage(mapMessage)
+                .setTimeMillis(System.currentTimeMillis())
+                .build();
+
+        // Check the serialized MapMessage.
+        usingSerializedLogEventAccessor(layout, mapMessageLogEvent, accessor ->
+                assertThat(accessor.getString(new String[]{"message", "key1"}))
+                        .isEqualTo("val1"));
+
+        // Create a log event with a SimpleMessage.
+        final Message simpleMessage = new SimpleMessage("simple");
+        final LogEvent simpleMessageLogEvent = Log4jLogEvent
+                .newBuilder()
+                .setLoggerName(LOGGER_NAME)
+                .setLevel(Level.INFO)
+                .setMessage(simpleMessage)
+                .setTimeMillis(System.currentTimeMillis())
+                .build();
+
+        // Check the serialized MapMessage.
+        usingSerializedLogEventAccessor(layout, simpleMessageLogEvent, 
accessor ->
+                assertThat(accessor.getString(new String[]{"message", 
"formattedMessage"}))
+                        .isEqualTo("simple"));
+
+    }
+
+    @Test
     public void test_property_injection() {
 
         // Create the log event.
diff --git a/src/site/asciidoc/manual/json-template-layout.adoc 
b/src/site/asciidoc/manual/json-template-layout.adoc
index f3e22db..9b819d1 100644
--- a/src/site/asciidoc/manual/json-template-layout.adoc
+++ b/src/site/asciidoc/manual/json-template-layout.adoc
@@ -733,8 +733,9 @@ parent such that keys are prefixed with `_`:
 a|
 [source]
 ----
-config      = [ stringified ]
+config      = [ stringified ] , [ fallbackKey ]
 stringified = "stringified" -> boolean
+fallbackKey = "fallbackKey" -> string
 ----
 a| `logEvent.getMessage()`
 | For simple string messages, the resolution is performed without allocations.
@@ -761,6 +762,26 @@ will be retained:
 }
 ----
 
+Given the above configuration, a `SimpleMessage` will generate a `"sample log
+message"`, whereas a `MapMessage` will generate a `{"action": "login",
+"sessionId": "87asd97a"}`. Certain indexed log storage systems (e.g.,
+https://www.elastic.co/elasticsearch/[Elasticsearch]) will not allow both 
values
+to coexist due to type mismatch: one is a `string` while the other is an 
`object`.
+Here one can use a `fallbackKey` to work around the problem:
+
+[source,json]
+----
+{
+  "$resolver": "message",
+  "fallbackKey": "formattedMessage"
+}
+----
+
+Using this configuration, a `SimpleMessage` will generate a
+`{"formattedMessage": "sample log message"}` and a `MapMessage` will generate a
+`{"action": "login", "sessionId": "87asd97a"}`. Note that both emitted JSONs 
are
+of type `object` and have no type-conflicting fields.
+
 | ndc
 a|
 [source]

Reply via email to