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]