| /* |
| * 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.layout.json.template; |
| |
| import com.fasterxml.jackson.databind.JsonNode; |
| import com.fasterxml.jackson.databind.MappingIterator; |
| import com.fasterxml.jackson.databind.ObjectMapper; |
| import org.apache.logging.log4j.Level; |
| import org.apache.logging.log4j.Marker; |
| import org.apache.logging.log4j.MarkerManager; |
| import org.apache.logging.log4j.core.Layout; |
| import org.apache.logging.log4j.core.LogEvent; |
| import org.apache.logging.log4j.core.appender.SocketAppender; |
| import org.apache.logging.log4j.core.config.Configuration; |
| import org.apache.logging.log4j.core.config.DefaultConfiguration; |
| import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory; |
| import org.apache.logging.log4j.core.impl.Log4jLogEvent; |
| import org.apache.logging.log4j.core.layout.ByteBufferDestination; |
| import org.apache.logging.log4j.core.lookup.MainMapLookup; |
| import org.apache.logging.log4j.core.net.Severity; |
| import org.apache.logging.log4j.core.time.MutableInstant; |
| import org.apache.logging.log4j.layout.json.template.JsonTemplateLayout.EventTemplateAdditionalField; |
| import org.apache.logging.log4j.layout.json.template.JsonTemplateLayout.EventTemplateAdditionalFields; |
| 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; |
| import org.apache.logging.log4j.test.AvailablePortFinder; |
| import org.apache.logging.log4j.util.SortedArrayStringMap; |
| import org.apache.logging.log4j.util.StringMap; |
| import org.apache.logging.log4j.util.Strings; |
| import org.assertj.core.api.Assertions; |
| import org.junit.Test; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.EOFException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintStream; |
| import java.io.UnsupportedEncodingException; |
| import java.math.BigDecimal; |
| import java.net.ServerSocket; |
| import java.net.Socket; |
| import java.nio.ByteBuffer; |
| import java.nio.charset.Charset; |
| import java.text.SimpleDateFormat; |
| import java.time.Instant; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ArrayBlockingQueue; |
| import java.util.concurrent.BlockingQueue; |
| import java.util.concurrent.TimeUnit; |
| import java.util.function.Consumer; |
| import java.util.stream.Collectors; |
| import java.util.stream.IntStream; |
| |
| import static org.assertj.core.api.Assertions.assertThat; |
| |
| @SuppressWarnings("DoubleBraceInitialization") |
| public class JsonTemplateLayoutTest { |
| |
| private static final Configuration CONFIGURATION = new DefaultConfiguration(); |
| |
| private static final List<LogEvent> LOG_EVENTS = LogEventFixture.createFullLogEvents(5); |
| |
| private static final JsonWriter JSON_WRITER = JsonWriter |
| .newBuilder() |
| .setMaxStringLength(10_000) |
| .setTruncatedStringSuffix("…") |
| .build(); |
| |
| private static final ObjectMapper OBJECT_MAPPER = JacksonFixture.getObjectMapper(); |
| |
| private static final String LOGGER_NAME = JsonTemplateLayoutTest.class.getSimpleName(); |
| |
| @Test |
| public void test_serialized_event() throws IOException { |
| final String lookupTestKey = "lookup_test_key"; |
| final String lookupTestVal = |
| String.format("lookup_test_value_%d", (int) (1000 * Math.random())); |
| System.setProperty(lookupTestKey, lookupTestVal); |
| for (final LogEvent logEvent : LOG_EVENTS) { |
| checkLogEvent(logEvent, lookupTestKey, lookupTestVal); |
| } |
| } |
| |
| private void checkLogEvent( |
| final LogEvent logEvent, |
| @SuppressWarnings("SameParameterValue") |
| final String lookupTestKey, |
| final String lookupTestVal) throws IOException { |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplateUri("classpath:testJsonTemplateLayout.json") |
| .setStackTraceEnabled(true) |
| .setLocationInfoEnabled(true) |
| .build(); |
| final String serializedLogEvent = layout.toSerializable(logEvent); |
| final JsonNode rootNode = OBJECT_MAPPER.readValue(serializedLogEvent, JsonNode.class); |
| checkConstants(rootNode); |
| checkBasicFields(logEvent, rootNode); |
| checkSource(logEvent, rootNode); |
| checkException(layout.getCharset(), logEvent, rootNode); |
| checkLookupTest(lookupTestKey, lookupTestVal, rootNode); |
| } |
| |
| private static void checkConstants(final JsonNode rootNode) { |
| assertThat(point(rootNode, "@version").asInt()).isEqualTo(1); |
| } |
| |
| private static void checkBasicFields(final LogEvent logEvent, final JsonNode rootNode) { |
| assertThat(point(rootNode, "message").asText()) |
| .isEqualTo(logEvent.getMessage().getFormattedMessage()); |
| assertThat(point(rootNode, "level").asText()) |
| .isEqualTo(logEvent.getLevel().name()); |
| assertThat(point(rootNode, "logger_fqcn").asText()) |
| .isEqualTo(logEvent.getLoggerFqcn()); |
| assertThat(point(rootNode, "logger_name").asText()) |
| .isEqualTo(logEvent.getLoggerName()); |
| assertThat(point(rootNode, "thread_id").asLong()) |
| .isEqualTo(logEvent.getThreadId()); |
| assertThat(point(rootNode, "thread_name").asText()) |
| .isEqualTo(logEvent.getThreadName()); |
| assertThat(point(rootNode, "thread_priority").asInt()) |
| .isEqualTo(logEvent.getThreadPriority()); |
| assertThat(point(rootNode, "end_of_batch").asBoolean()) |
| .isEqualTo(logEvent.isEndOfBatch()); |
| } |
| |
| private static void checkSource(final LogEvent logEvent, final JsonNode rootNode) { |
| assertThat(point(rootNode, "class").asText()).isEqualTo(logEvent.getSource().getClassName()); |
| assertThat(point(rootNode, "file").asText()).isEqualTo(logEvent.getSource().getFileName()); |
| assertThat(point(rootNode, "line_number").asInt()).isEqualTo(logEvent.getSource().getLineNumber()); |
| } |
| |
| private static void checkException( |
| final Charset charset, |
| final LogEvent logEvent, |
| final JsonNode rootNode) { |
| final Throwable thrown = logEvent.getThrown(); |
| if (thrown != null) { |
| assertThat(point(rootNode, "exception_class").asText()).isEqualTo(thrown.getClass().getCanonicalName()); |
| assertThat(point(rootNode, "exception_message").asText()).isEqualTo(thrown.getMessage()); |
| final String stackTrace = serializeStackTrace(charset, thrown); |
| assertThat(point(rootNode, "stacktrace").asText()).isEqualTo(stackTrace); |
| } |
| } |
| |
| private static String serializeStackTrace( |
| final Charset charset, |
| final Throwable exception) { |
| final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
| final String charsetName = charset.name(); |
| try (final PrintStream printStream = |
| new PrintStream(outputStream, false, charsetName)) { |
| exception.printStackTrace(printStream); |
| return outputStream.toString(charsetName); |
| } catch (final UnsupportedEncodingException error) { |
| throw new RuntimeException("failed converting the stack trace to string", error); |
| } |
| } |
| |
| private static void checkLookupTest( |
| final String lookupTestKey, |
| final String lookupTestVal, |
| final JsonNode rootNode) { |
| assertThat(point(rootNode, lookupTestKey).asText()).isEqualTo(lookupTestVal); |
| } |
| |
| private static JsonNode point(final JsonNode node, final Object... fields) { |
| final String pointer = createJsonPointer(fields); |
| return node.at(pointer); |
| } |
| |
| private static String createJsonPointer(final Object... fields) { |
| final StringBuilder jsonPathBuilder = new StringBuilder(); |
| for (final Object field : fields) { |
| jsonPathBuilder.append("/").append(field); |
| } |
| return jsonPathBuilder.toString(); |
| } |
| |
| @Test |
| public void test_inline_template() throws Exception { |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("Hello, World"); |
| final String timestamp = "2017-09-28T17:13:29.098+02:00"; |
| final long timeMillis = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX") |
| .parse(timestamp) |
| .getTime(); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.INFO) |
| .setMessage(message) |
| .setTimeMillis(timeMillis) |
| .build(); |
| |
| // Create the event template. |
| final String timestampFieldName = "@timestamp"; |
| final String staticFieldName = "staticFieldName"; |
| final String staticFieldValue = "staticFieldValue"; |
| final String eventTemplate = writeJson(Map( |
| timestampFieldName, Map( |
| "$resolver", "timestamp", |
| "pattern", Map("timeZone", "Europe/Amsterdam")), |
| staticFieldName, staticFieldValue)); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getString(timestampFieldName)).isEqualTo(timestamp); |
| assertThat(accessor.getString(staticFieldName)).isEqualTo(staticFieldValue); |
| }); |
| |
| } |
| |
| @Test |
| public void test_log4j_deferred_runtime_resolver_for_MapMessage() { |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map( |
| "mapValue3", Map("$resolver", "message"), |
| "mapValue1", "${map:key1}", |
| "mapValue2", "${map:key2}", |
| "nestedLookupEmptyValue", "${map:noExist:-${map:noExist2:-${map:noExist3:-}}}", |
| "nestedLookupStaticValue", "${map:noExist:-${map:noExist2:-${map:noExist3:-Static Value}}}")); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Create the log event with a MapMessage. |
| final StringMapMessage mapMessage = new StringMapMessage() |
| .with("key1", "val1") |
| .with("key2", "val2") |
| .with("key3", Collections.singletonMap("foo", "bar")); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.INFO) |
| .setMessage(mapMessage) |
| .setTimeMillis(System.currentTimeMillis()) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getString("mapValue1")).isEqualTo("val1"); |
| assertThat(accessor.getString("mapValue2")).isEqualTo("val2"); |
| assertThat(accessor.getString("nestedLookupEmptyValue")).isEmpty(); |
| assertThat(accessor.getString("nestedLookupStaticValue")).isEqualTo("Static Value"); |
| }); |
| |
| } |
| |
| @Test |
| public void test_MapMessage_serialization() { |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map( |
| "message", Map("$resolver", "message"))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Create the log event with a MapMessage. |
| final StringMapMessage mapMessage = new StringMapMessage() |
| .with("key1", "val1") |
| .with("key2", 0xDEADBEEF) |
| .with("key3", Collections.singletonMap("key3.1", "val3.1")); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.INFO) |
| .setMessage(mapMessage) |
| .setTimeMillis(System.currentTimeMillis()) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getString(new String[]{"message", "key1"})).isEqualTo("val1"); |
| assertThat(accessor.getInteger(new String[]{"message", "key2"})).isEqualTo(0xDEADBEEF); |
| assertThat(accessor.getString(new String[]{"message", "key3", "key3.1"})).isEqualTo("val3.1"); |
| }); |
| |
| } |
| |
| @Test |
| public void test_MapMessage_keyed_access() { |
| |
| // Create the event template. |
| final String key = "list"; |
| final String eventTemplate = writeJson(Map( |
| "typedValue", Map( |
| "$resolver", "map", |
| "key", key), |
| "stringifiedValue", Map( |
| "$resolver", "map", |
| "key", key, |
| "stringified", true))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Create the log event with a MapMessage. |
| final List<Integer> value = Arrays.asList(1, 2); |
| final StringMapMessage mapMessage = new StringMapMessage() |
| .with(key, value); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.INFO) |
| .setMessage(mapMessage) |
| .setTimeMillis(System.currentTimeMillis()) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getObject("typedValue")).isEqualTo(value); |
| assertThat(accessor.getString("stringifiedValue")).isEqualTo(String.valueOf(value)); |
| }); |
| |
| } |
| |
| @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. |
| final SimpleMessage message = new SimpleMessage("Hello, World"); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.INFO) |
| .setMessage(message) |
| .build(); |
| |
| // Create the event template with property. |
| final String propertyName = "propertyName"; |
| final String eventTemplate = writeJson(Map( |
| propertyName, "${" + propertyName + "}")); |
| |
| // Create the layout with property. |
| final String propertyValue = "propertyValue"; |
| final Configuration config = ConfigurationBuilderFactory |
| .newConfigurationBuilder() |
| .addProperty(propertyName, propertyValue) |
| .build(); |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(config) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> |
| assertThat(accessor.getString(propertyName)).isEqualTo(propertyValue)); |
| |
| } |
| |
| @Test |
| public void test_empty_root_cause() { |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("Hello, World!"); |
| final RuntimeException exception = new RuntimeException("failure for test purposes"); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.ERROR) |
| .setMessage(message) |
| .setThrown(exception) |
| .build(); |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map( |
| "ex_class", Map( |
| "$resolver", "exception", |
| "field", "className"), |
| "ex_message", Map( |
| "$resolver", "exception", |
| "field", "message"), |
| "ex_stacktrace", Map( |
| "$resolver", "exception", |
| "field", "stackTrace", |
| "stringified", true), |
| "root_ex_class", Map( |
| "$resolver", "exceptionRootCause", |
| "field", "className"), |
| "root_ex_message", Map( |
| "$resolver", "exceptionRootCause", |
| "field", "message"), |
| "root_ex_stacktrace", Map( |
| "$resolver", "exceptionRootCause", |
| "field", "stackTrace", |
| "stringified", true))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setStackTraceEnabled(true) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getString("ex_class")) |
| .isEqualTo(exception.getClass().getCanonicalName()); |
| assertThat(accessor.getString("ex_message")) |
| .isEqualTo(exception.getMessage()); |
| assertThat(accessor.getString("ex_stacktrace")) |
| .startsWith(exception.getClass().getCanonicalName() + ": " + exception.getMessage()); |
| assertThat(accessor.getString("root_ex_class")) |
| .isEqualTo(accessor.getString("ex_class")); |
| assertThat(accessor.getString("root_ex_message")) |
| .isEqualTo(accessor.getString("ex_message")); |
| assertThat(accessor.getString("root_ex_stacktrace")) |
| .isEqualTo(accessor.getString("ex_stacktrace")); |
| }); |
| |
| } |
| |
| @Test |
| public void test_root_cause() { |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("Hello, World!"); |
| final RuntimeException exceptionCause = new RuntimeException("failure cause for test purposes"); |
| final RuntimeException exception = new RuntimeException("failure for test purposes", exceptionCause); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.ERROR) |
| .setMessage(message) |
| .setThrown(exception) |
| .build(); |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map( |
| "ex_class", Map( |
| "$resolver", "exception", |
| "field", "className"), |
| "ex_message", Map( |
| "$resolver", "exception", |
| "field", "message"), |
| "ex_stacktrace", Map( |
| "$resolver", "exception", |
| "field", "stackTrace", |
| "stringified", true), |
| "root_ex_class", Map( |
| "$resolver", "exceptionRootCause", |
| "field", "className"), |
| "root_ex_message", Map( |
| "$resolver", "exceptionRootCause", |
| "field", "message"), |
| "root_ex_stacktrace", Map( |
| "$resolver", "exceptionRootCause", |
| "field", "stackTrace", |
| "stringified", true))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setStackTraceEnabled(true) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getString("ex_class")) |
| .isEqualTo(exception.getClass().getCanonicalName()); |
| assertThat(accessor.getString("ex_message")) |
| .isEqualTo(exception.getMessage()); |
| assertThat(accessor.getString("ex_stacktrace")) |
| .startsWith(exception.getClass().getCanonicalName() + ": " + exception.getMessage()); |
| assertThat(accessor.getString("root_ex_class")) |
| .isEqualTo(exceptionCause.getClass().getCanonicalName()); |
| assertThat(accessor.getString("root_ex_message")) |
| .isEqualTo(exceptionCause.getMessage()); |
| assertThat(accessor.getString("root_ex_stacktrace")) |
| .startsWith(exceptionCause.getClass().getCanonicalName() + ": " + exceptionCause.getMessage()); |
| }); |
| |
| } |
| |
| @Test |
| public void test_marker_name() { |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("Hello, World!"); |
| final String markerName = "test"; |
| final Marker marker = MarkerManager.getMarker(markerName); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.ERROR) |
| .setMessage(message) |
| .setMarker(marker) |
| .build(); |
| |
| // Create the event template. |
| final String messageKey = "message"; |
| final String markerNameKey = "marker"; |
| final String eventTemplate = writeJson(Map( |
| "message", Map("$resolver", "message"), |
| "marker", Map( |
| "$resolver", "marker", |
| "field", "name"))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getString(messageKey)).isEqualTo(message.getFormattedMessage()); |
| assertThat(accessor.getString(markerNameKey)).isEqualTo(markerName); |
| }); |
| |
| } |
| |
| @Test |
| public void test_lineSeparator_suffix() { |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("Hello, World!"); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.INFO) |
| .setMessage(message) |
| .build(); |
| |
| // Check line separators. |
| test_lineSeparator_suffix(logEvent, true); |
| test_lineSeparator_suffix(logEvent, false); |
| |
| } |
| |
| private void test_lineSeparator_suffix( |
| final LogEvent logEvent, |
| final boolean prettyPrintEnabled) { |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplateUri("classpath:LogstashJsonEventLayoutV1.json") |
| .build(); |
| |
| // Check the serialized event. |
| final String serializedLogEvent = layout.toSerializable(logEvent); |
| final String assertionCaption = String.format("testing lineSeperator (prettyPrintEnabled=%s)", prettyPrintEnabled); |
| assertThat(serializedLogEvent).as(assertionCaption).endsWith("}" + System.lineSeparator()); |
| |
| } |
| |
| @Test |
| public void test_main_key_access() { |
| |
| // Set main() arguments. |
| final String kwKey = "--name"; |
| final String kwVal = "aNameValue"; |
| final String positionArg = "position2Value"; |
| final String missingKwKey = "--missing"; |
| final String[] mainArgs = {kwKey, kwVal, positionArg}; |
| MainMapLookup.setMainArguments(mainArgs); |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("Hello, World!"); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.INFO) |
| .setMessage(message) |
| .build(); |
| |
| // Create the template. |
| final String template = writeJson(Map( |
| "name", Map( |
| "$resolver", "main", |
| "key", kwKey), |
| "positionArg", Map( |
| "$resolver", "main", |
| "index", 2), |
| "notFoundArg", Map( |
| "$resolver", "main", |
| "key", missingKwKey))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(template) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getString("name")).isEqualTo(kwVal); |
| assertThat(accessor.getString("positionArg")).isEqualTo(positionArg); |
| assertThat(accessor.exists("notFoundArg")).isFalse(); |
| }); |
| |
| } |
| |
| @Test |
| public void test_mdc_key_access() { |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("Hello, World!"); |
| final StringMap contextData = new SortedArrayStringMap(); |
| final String mdcDirectlyAccessedKey = "mdcKey1"; |
| final String mdcDirectlyAccessedValue = "mdcValue1"; |
| contextData.putValue(mdcDirectlyAccessedKey, mdcDirectlyAccessedValue); |
| final String mdcDirectlyAccessedNullPropertyKey = "mdcKey2"; |
| final String mdcDirectlyAccessedNullPropertyValue = null; |
| // noinspection ConstantConditions |
| contextData.putValue(mdcDirectlyAccessedNullPropertyKey, mdcDirectlyAccessedNullPropertyValue); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.INFO) |
| .setMessage(message) |
| .setContextData(contextData) |
| .build(); |
| |
| // Create the event template. |
| String eventTemplate = writeJson(Map( |
| mdcDirectlyAccessedKey, Map( |
| "$resolver", "mdc", |
| "key", mdcDirectlyAccessedKey), |
| mdcDirectlyAccessedNullPropertyKey, Map( |
| "$resolver", "mdc", |
| "key", mdcDirectlyAccessedNullPropertyKey))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setStackTraceEnabled(true) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getString(mdcDirectlyAccessedKey)).isEqualTo(mdcDirectlyAccessedValue); |
| assertThat(accessor.getString(mdcDirectlyAccessedNullPropertyKey)).isNull(); |
| }); |
| |
| } |
| |
| @Test |
| public void test_mdc_pattern() { |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("Hello, World!"); |
| final StringMap contextData = new SortedArrayStringMap(); |
| final String mdcPatternMatchedKey = "mdcKey1"; |
| final String mdcPatternMatchedValue = "mdcValue1"; |
| contextData.putValue(mdcPatternMatchedKey, mdcPatternMatchedValue); |
| final String mdcPatternMismatchedKey = "mdcKey2"; |
| final String mdcPatternMismatchedValue = "mdcValue2"; |
| contextData.putValue(mdcPatternMismatchedKey, mdcPatternMismatchedValue); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.INFO) |
| .setMessage(message) |
| .setContextData(contextData) |
| .build(); |
| |
| // Create the event template. |
| final String mdcFieldName = "mdc"; |
| final String eventTemplate = writeJson(Map( |
| mdcFieldName, Map( |
| "$resolver", "mdc", |
| "pattern", mdcPatternMatchedKey))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setStackTraceEnabled(true) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getString(new String[]{mdcFieldName, mdcPatternMatchedKey})).isEqualTo(mdcPatternMatchedValue); |
| assertThat(accessor.exists(new String[]{mdcFieldName, mdcPatternMismatchedKey})).isFalse(); |
| }); |
| |
| } |
| |
| @Test |
| public void test_mdc_flatten() { |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("Hello, World!"); |
| final StringMap contextData = new SortedArrayStringMap(); |
| final String mdcPatternMatchedKey = "mdcKey1"; |
| final String mdcPatternMatchedValue = "mdcValue1"; |
| contextData.putValue(mdcPatternMatchedKey, mdcPatternMatchedValue); |
| final String mdcPatternMismatchedKey = "mdcKey2"; |
| final String mdcPatternMismatchedValue = "mdcValue2"; |
| contextData.putValue(mdcPatternMismatchedKey, mdcPatternMismatchedValue); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.INFO) |
| .setMessage(message) |
| .setContextData(contextData) |
| .build(); |
| |
| // Create the event template. |
| final String mdcPrefix = "_mdc."; |
| final String eventTemplate = writeJson(Map( |
| "ignoredFieldName", Map( |
| "$resolver", "mdc", |
| "pattern", mdcPatternMatchedKey, |
| "flatten", Map("prefix", mdcPrefix)))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setStackTraceEnabled(true) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getString(mdcPrefix + mdcPatternMatchedKey)).isEqualTo(mdcPatternMatchedValue); |
| assertThat(accessor.exists(mdcPrefix + mdcPatternMismatchedKey)).isFalse(); |
| }); |
| |
| } |
| |
| @Test |
| public void test_MapResolver() { |
| |
| // Create the log event. |
| final StringMapMessage message = new StringMapMessage().with("key1", "val1"); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.INFO) |
| .setMessage(message) |
| .build(); |
| |
| // Create the event template node with map values. |
| final String eventTemplate = writeJson(Map( |
| "mapValue1", Map( |
| "$resolver", "map", |
| "key", "key1"), |
| "mapValue2", Map( |
| "$resolver", "map", |
| "key", "key?"))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Check serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getString("mapValue1")).isEqualTo("val1"); |
| assertThat(accessor.getString("mapValue2")).isNull(); |
| }); |
| |
| } |
| |
| @Test |
| public void test_StringMapMessage() { |
| |
| // Create the log event. |
| final StringMapMessage message = new StringMapMessage(); |
| message.put("message", "Hello, World!"); |
| message.put("bottle", "Kickapoo Joy Juice"); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.INFO) |
| .setMessage(message) |
| .build(); |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map( |
| "message", Map("$resolver", "message"))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setStackTraceEnabled(true) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getString(new String[]{"message", "message"})).isEqualTo("Hello, World!"); |
| assertThat(accessor.getString(new String[]{"message", "bottle"})).isEqualTo("Kickapoo Joy Juice"); |
| }); |
| |
| } |
| |
| @Test |
| public void test_ObjectMessage() { |
| |
| // Create the log event. |
| final int id = 0xDEADBEEF; |
| final String name = "name-" + id; |
| final Object attachment = new LinkedHashMap<String, Object>() {{ |
| put("id", id); |
| put("name", name); |
| }}; |
| final ObjectMessage message = new ObjectMessage(attachment); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.INFO) |
| .setMessage(message) |
| .build(); |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map( |
| "message", Map("$resolver", "message"))); |
| |
| // Create the layout. |
| JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setStackTraceEnabled(true) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getInteger(new String[]{"message", "id"})).isEqualTo(id); |
| assertThat(accessor.getString(new String[]{"message", "name"})).isEqualTo(name); |
| }); |
| |
| } |
| |
| @Test |
| public void test_StackTraceElement_template() { |
| |
| // Create the stack trace element template. |
| final String classNameFieldName = "className"; |
| final String methodNameFieldName = "methodName"; |
| final String fileNameFieldName = "fileName"; |
| final String lineNumberFieldName = "lineNumber"; |
| final String stackTraceElementTemplate = writeJson(Map( |
| classNameFieldName, Map( |
| "$resolver", "stackTraceElement", |
| "field", "className"), |
| methodNameFieldName, Map( |
| "$resolver", "stackTraceElement", |
| "field", "methodName"), |
| fileNameFieldName, Map( |
| "$resolver", "stackTraceElement", |
| "field", "fileName"), |
| lineNumberFieldName, Map( |
| "$resolver", "stackTraceElement", |
| "field", "lineNumber"))); |
| |
| // Create the event template. |
| final String stackTraceFieldName = "stackTrace"; |
| final String eventTemplate = writeJson(Map( |
| stackTraceFieldName, Map( |
| "$resolver", "exception", |
| "field", "stackTrace"))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setStackTraceEnabled(true) |
| .setStackTraceElementTemplate(stackTraceElementTemplate) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("Hello, World!"); |
| final RuntimeException exceptionCause = new RuntimeException("failure cause for test purposes"); |
| final RuntimeException exception = new RuntimeException("failure for test purposes", exceptionCause); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.ERROR) |
| .setMessage(message) |
| .setThrown(exception) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.exists(stackTraceFieldName)).isTrue(); |
| @SuppressWarnings("unchecked") |
| final List<Map<String, Object>> deserializedStackTraceElements = |
| accessor.getObject(stackTraceFieldName, List.class); |
| final StackTraceElement[] stackTraceElements = exception.getStackTrace(); |
| assertThat(deserializedStackTraceElements.size()).isEqualTo(stackTraceElements.length); |
| for (int stackTraceElementIndex = 0; |
| stackTraceElementIndex < stackTraceElements.length; |
| stackTraceElementIndex++) { |
| final StackTraceElement stackTraceElement = stackTraceElements[stackTraceElementIndex]; |
| final Map<String, Object> deserializedStackTraceElement = deserializedStackTraceElements.get(stackTraceElementIndex); |
| assertThat(deserializedStackTraceElement.size()).isEqualTo(4); |
| assertThat(deserializedStackTraceElement.get(classNameFieldName)) |
| .isEqualTo(stackTraceElement.getClassName()); |
| assertThat(deserializedStackTraceElement.get(methodNameFieldName)) |
| .isEqualTo(stackTraceElement.getMethodName()); |
| assertThat(deserializedStackTraceElement.get(fileNameFieldName)) |
| .isEqualTo(stackTraceElement.getFileName()); |
| assertThat(deserializedStackTraceElement.get(lineNumberFieldName)) |
| .isEqualTo(stackTraceElement.getLineNumber()); |
| } |
| }); |
| |
| } |
| |
| @Test |
| public void test_toSerializable_toByteArray_encode_outputs() { |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplateUri("classpath:LogstashJsonEventLayoutV1.json") |
| .setStackTraceEnabled(true) |
| .build(); |
| |
| // Create the log event. |
| final LogEvent logEvent = LogEventFixture.createFullLogEvents(1).get(0); |
| |
| // Get toSerializable() output. |
| final String toSerializableOutput = layout.toSerializable(logEvent); |
| |
| // Get toByteArrayOutput(). |
| final byte[] toByteArrayOutputBytes = layout.toByteArray(logEvent); |
| final String toByteArrayOutput = new String( |
| toByteArrayOutputBytes, |
| 0, |
| toByteArrayOutputBytes.length, |
| layout.getCharset()); |
| |
| // Get encode() output. |
| final ByteBuffer byteBuffer = ByteBuffer.allocate(512 * 1024); |
| final ByteBufferDestination byteBufferDestination = new ByteBufferDestination() { |
| |
| @Override |
| public ByteBuffer getByteBuffer() { |
| return byteBuffer; |
| } |
| |
| @Override |
| public ByteBuffer drain(final ByteBuffer ignored) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public void writeBytes(final ByteBuffer data) { |
| byteBuffer.put(data); |
| } |
| |
| @Override |
| public void writeBytes(final byte[] buffer, final int offset, final int length) { |
| byteBuffer.put(buffer, offset, length); |
| } |
| |
| }; |
| layout.encode(logEvent, byteBufferDestination); |
| String encodeOutput = new String( |
| byteBuffer.array(), |
| 0, |
| byteBuffer.position(), |
| layout.getCharset()); |
| |
| // Compare outputs. |
| assertThat(toSerializableOutput).isEqualTo(toByteArrayOutput); |
| assertThat(toByteArrayOutput).isEqualTo(encodeOutput); |
| |
| } |
| |
| @Test |
| public void test_maxStringLength() { |
| |
| // Create the log event. |
| final int maxStringLength = 30; |
| final String excessiveMessageString = Strings.repeat("m", maxStringLength) + 'M'; |
| final SimpleMessage message = new SimpleMessage(excessiveMessageString); |
| final Throwable thrown = new RuntimeException(); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.INFO) |
| .setMessage(message) |
| .setThrown(thrown) |
| .build(); |
| |
| // Create the event template node with map values. |
| final String messageKey = "message"; |
| final String excessiveKey = Strings.repeat("k", maxStringLength) + 'K'; |
| final String excessiveValue = Strings.repeat("v", maxStringLength) + 'V'; |
| final String nullValueKey = "nullValueKey"; |
| final String eventTemplate = writeJson(Map( |
| messageKey, Map("$resolver", "message"), |
| excessiveKey, excessiveValue, |
| nullValueKey, Map( |
| "$resolver", "exception", |
| "field", "message"))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .setMaxStringLength(maxStringLength) |
| .build(); |
| |
| // Check serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| final String truncatedStringSuffix = |
| JsonTemplateLayoutDefaults.getTruncatedStringSuffix(); |
| final String truncatedMessageString = |
| excessiveMessageString.substring(0, maxStringLength) + |
| truncatedStringSuffix; |
| assertThat(accessor.getString(messageKey)).isEqualTo(truncatedMessageString); |
| final String truncatedKey = |
| excessiveKey.substring(0, maxStringLength) + |
| truncatedStringSuffix; |
| final String truncatedValue = |
| excessiveValue.substring(0, maxStringLength) + |
| truncatedStringSuffix; |
| assertThat(accessor.getString(truncatedKey)).isEqualTo(truncatedValue); |
| assertThat(accessor.getString(nullValueKey)).isNull(); |
| }); |
| |
| } |
| |
| private static final class NonAsciiUtf8MethodNameContainingException extends RuntimeException {; |
| |
| public static final long serialVersionUID = 0; |
| |
| private static final String NON_ASCII_UTF8_TEXT = "அஆஇฬ๘"; |
| |
| private static final NonAsciiUtf8MethodNameContainingException INSTANCE = |
| createInstance(); |
| |
| private static NonAsciiUtf8MethodNameContainingException createInstance() { |
| try { |
| throwException_அஆஇฬ๘(); |
| throw new IllegalStateException("should not have reached here"); |
| } catch (final NonAsciiUtf8MethodNameContainingException exception) { |
| return exception; |
| } |
| } |
| |
| @SuppressWarnings("NonAsciiCharacters") |
| private static void throwException_அஆஇฬ๘() { |
| throw new NonAsciiUtf8MethodNameContainingException( |
| "exception with non-ASCII UTF-8 method name"); |
| } |
| |
| private NonAsciiUtf8MethodNameContainingException(final String message) { |
| super(message); |
| } |
| |
| } |
| |
| @Test |
| public void test_exception_with_nonAscii_utf8_method_name() { |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("Hello, World!"); |
| final RuntimeException exception = NonAsciiUtf8MethodNameContainingException.INSTANCE; |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(Level.ERROR) |
| .setMessage(message) |
| .setThrown(exception) |
| .build(); |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map( |
| "ex_stacktrace", Map( |
| "$resolver", "exception", |
| "field", "stackTrace", |
| "stringified", true))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setStackTraceEnabled(true) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> |
| assertThat(accessor.getString("ex_stacktrace")) |
| .contains(NonAsciiUtf8MethodNameContainingException.NON_ASCII_UTF8_TEXT)); |
| |
| } |
| |
| @Test |
| public void test_event_template_additional_fields() { |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("Hello, World!"); |
| final RuntimeException exception = NonAsciiUtf8MethodNameContainingException.INSTANCE; |
| final Level level = Level.ERROR; |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(level) |
| .setMessage(message) |
| .setThrown(exception) |
| .build(); |
| |
| // Create the event template. |
| final String eventTemplate = "{}"; |
| |
| // Create the layout. |
| final EventTemplateAdditionalField[] additionalFieldPairs = { |
| EventTemplateAdditionalField |
| .newBuilder() |
| .setKey("number") |
| .setValue("1") |
| .setType(EventTemplateAdditionalField.Type.JSON) |
| .build(), |
| EventTemplateAdditionalField |
| .newBuilder() |
| .setKey("string") |
| .setValue("foo") |
| .build(), |
| EventTemplateAdditionalField |
| .newBuilder() |
| .setKey("level") |
| .setValue("{\"$resolver\": \"level\", \"field\": \"name\"}") |
| .setType(EventTemplateAdditionalField.Type.JSON) |
| .build() |
| }; |
| final EventTemplateAdditionalFields additionalFields = EventTemplateAdditionalFields |
| .newBuilder() |
| .setAdditionalFields(additionalFieldPairs) |
| .build(); |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setStackTraceEnabled(true) |
| .setEventTemplate(eventTemplate) |
| .setEventTemplateAdditionalFields(additionalFields) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getInteger("number")).isEqualTo(1); |
| assertThat(accessor.getString("string")).isEqualTo("foo"); |
| assertThat(accessor.getString("level")).isEqualTo(level.name()); |
| }); |
| |
| } |
| |
| @Test |
| @SuppressWarnings("FloatingPointLiteralPrecision") |
| public void test_timestamp_epoch_resolvers() { |
| |
| final List<Map<String, Object>> testCases = Arrays.asList( |
| Map( |
| "epochSecs", new BigDecimal("1581082727.982123456"), |
| "epochSecsRounded", 1581082727, |
| "epochSecsNanos", 982123456, |
| "epochMillis", new BigDecimal("1581082727982.123456"), |
| "epochMillisRounded", 1581082727982L, |
| "epochMillisNanos", 123456, |
| "epochNanos", 1581082727982123456L), |
| Map( |
| "epochSecs", new BigDecimal("1591177590.005000001"), |
| "epochSecsRounded", 1591177590, |
| "epochSecsNanos", 5000001, |
| "epochMillis", new BigDecimal("1591177590005.000001"), |
| "epochMillisRounded", 1591177590005L, |
| "epochMillisNanos", 1, |
| "epochNanos", 1591177590005000001L)); |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map( |
| "epochSecs", Map( |
| "$resolver", "timestamp", |
| "epoch", Map("unit", "secs")), |
| "epochSecsRounded", Map( |
| "$resolver", "timestamp", |
| "epoch", Map( |
| "unit", "secs", |
| "rounded", true)), |
| "epochSecsNanos", Map( |
| "$resolver", "timestamp", |
| "epoch", Map("unit", "secs.nanos")), |
| "epochMillis", Map( |
| "$resolver", "timestamp", |
| "epoch", Map("unit", "millis")), |
| "epochMillisRounded", Map( |
| "$resolver", "timestamp", |
| "epoch", Map( |
| "unit", "millis", |
| "rounded", true)), |
| "epochMillisNanos", Map( |
| "$resolver", "timestamp", |
| "epoch", Map("unit", "millis.nanos")), |
| "epochNanos", Map( |
| "$resolver", "timestamp", |
| "epoch", Map("unit", "nanos")))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| testCases.forEach(testCase -> { |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("Hello, World!"); |
| final Level level = Level.ERROR; |
| final MutableInstant instant = new MutableInstant(); |
| final Object instantSecsObject = testCase.get("epochSecsRounded"); |
| final long instantSecs = instantSecsObject instanceof Long |
| ? (long) instantSecsObject |
| : (int) instantSecsObject; |
| final int instantSecsNanos = (int) testCase.get("epochSecsNanos"); |
| instant.initFromEpochSecond(instantSecs, instantSecsNanos); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(level) |
| .setMessage(message) |
| .setInstant(instant) |
| .build(); |
| |
| // Verify the test case. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> |
| testCase.forEach((key, expectedValue) -> |
| Assertions |
| .assertThat(accessor.getObject(key)) |
| .describedAs("key=%s", key) |
| .isEqualTo(expectedValue))); |
| |
| }); |
| |
| } |
| |
| @Test |
| public void test_timestamp_pattern_resolver() { |
| |
| // Create log events. |
| final String logEvent1FormattedInstant = "2019-01-02T09:34:11Z"; |
| final LogEvent logEvent1 = createLogEventAtInstant(logEvent1FormattedInstant); |
| final String logEvent2FormattedInstant = "2019-01-02T09:34:12Z"; |
| final LogEvent logEvent2 = createLogEventAtInstant(logEvent2FormattedInstant); |
| @SuppressWarnings("UnnecessaryLocalVariable") |
| final String logEvent3FormattedInstant = logEvent2FormattedInstant; |
| final LogEvent logEvent3 = createLogEventAtInstant(logEvent3FormattedInstant); |
| final String logEvent4FormattedInstant = "2019-01-02T09:34:13Z"; |
| final LogEvent logEvent4 = createLogEventAtInstant(logEvent4FormattedInstant); |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map( |
| "timestamp", Map( |
| "$resolver", "timestamp", |
| "pattern", Map( |
| "format", "yyyy-MM-dd'T'HH:mm:ss'Z'", |
| "timeZone", "UTC")))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Check the serialized 1st event. |
| usingSerializedLogEventAccessor(layout, logEvent1, accessor -> |
| assertThat(accessor.getString("timestamp")) |
| .isEqualTo(logEvent1FormattedInstant)); |
| |
| // Check the serialized 2nd event. |
| usingSerializedLogEventAccessor(layout, logEvent2, accessor -> |
| assertThat(accessor.getString("timestamp")) |
| .isEqualTo(logEvent2FormattedInstant)); |
| |
| // Check the serialized 3rd event. |
| usingSerializedLogEventAccessor(layout, logEvent3, accessor -> |
| assertThat(accessor.getString("timestamp")) |
| .isEqualTo(logEvent3FormattedInstant)); |
| |
| // Check the serialized 4th event. |
| usingSerializedLogEventAccessor(layout, logEvent4, accessor -> |
| assertThat(accessor.getString("timestamp")) |
| .isEqualTo(logEvent4FormattedInstant)); |
| |
| } |
| |
| private static LogEvent createLogEventAtInstant(final String formattedInstant) { |
| final SimpleMessage message = new SimpleMessage("LogEvent at instant " + formattedInstant); |
| final long instantEpochMillis = Instant.parse(formattedInstant).toEpochMilli(); |
| final MutableInstant instant = new MutableInstant(); |
| instant.initFromEpochMilli(instantEpochMillis, 0); |
| return Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setMessage(message) |
| .setInstant(instant) |
| .build(); |
| } |
| |
| @Test |
| public void test_level_severity() { |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map( |
| "severityKeyword", Map( |
| "$resolver", "level", |
| "field", "severity", |
| "severity", Map("field", "keyword")), |
| "severityCode", Map( |
| "$resolver", "level", |
| "field", "severity", |
| "severity", Map("field", "code")))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| for (final Level level : Level.values()) { |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("Hello, World!"); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setLevel(level) |
| .setMessage(message) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| final Severity expectedSeverity = Severity.getSeverity(level); |
| final String expectedSeverityKeyword = expectedSeverity.name(); |
| final int expectedSeverityCode = expectedSeverity.getCode(); |
| assertThat(accessor.getString("severityKeyword")).isEqualTo(expectedSeverityKeyword); |
| assertThat(accessor.getInteger("severityCode")).isEqualTo(expectedSeverityCode); |
| }); |
| |
| } |
| |
| } |
| |
| @Test |
| public void test_exception_resolvers_against_no_exceptions() { |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("Hello, World!"); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setMessage(message) |
| .build(); |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map( |
| "exStackTrace", Map( |
| "$resolver", "exception", |
| "field", "stackTrace"), |
| "exStackTraceString", Map( |
| "$resolver", "exception", |
| "field", "stackTrace", |
| "stringified", true), |
| "exRootCauseStackTrace", Map( |
| "$resolver", "exceptionRootCause", |
| "field", "stackTrace"), |
| "exRootCauseStackTraceString", Map( |
| "$resolver", "exceptionRootCause", |
| "field", "stackTrace", |
| "stringified", true), |
| "requiredFieldTriggeringError", true)); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .setStackTraceEnabled(true) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| assertThat(accessor.getObject("exStackTrace")).isNull(); |
| assertThat(accessor.getObject("exStackTraceString")).isNull(); |
| assertThat(accessor.getObject("exRootCauseStackTrace")).isNull(); |
| assertThat(accessor.getObject("exRootCauseStackTraceString")).isNull(); |
| assertThat(accessor.getBoolean("requiredFieldTriggeringError")).isTrue(); |
| }); |
| |
| } |
| |
| @Test |
| public void test_StackTraceTextResolver_with_maxStringLength() { |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map( |
| "stackTrace", Map( |
| "$resolver", "exception", |
| "field", "stackTrace", |
| "stringified", true))); |
| |
| // Create the layout. |
| final int maxStringLength = eventTemplate.length(); |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .setMaxStringLength(maxStringLength) |
| .setStackTraceEnabled(true) |
| .build(); |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("foo"); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setMessage(message) |
| .setThrown(NonAsciiUtf8MethodNameContainingException.INSTANCE) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| final int expectedLength = maxStringLength + |
| JsonTemplateLayoutDefaults.getTruncatedStringSuffix().length(); |
| assertThat(accessor.getString("stackTrace").length()).isEqualTo(expectedLength); |
| }); |
| |
| } |
| |
| @Test |
| public void test_null_eventDelimiter() { |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map("key", "val")); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .setEventDelimiter("\0") |
| .build(); |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("foo"); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setMessage(message) |
| .setThrown(NonAsciiUtf8MethodNameContainingException.INSTANCE) |
| .build(); |
| |
| // Check the serialized event. |
| final String serializedLogEvent = layout.toSerializable(logEvent); |
| assertThat(serializedLogEvent).isEqualTo(eventTemplate + '\0'); |
| |
| } |
| |
| @Test |
| public void test_against_SocketAppender() throws Exception { |
| |
| // Craft nasty events. |
| final List<LogEvent> logEvents = createNastyLogEvents(); |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map( |
| "message", Map("$resolver", "message"))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Create the server. |
| final int port = AvailablePortFinder.getNextAvailable(); |
| try (final JsonAcceptingTcpServer server = new JsonAcceptingTcpServer(port, 1)) { |
| |
| // Create the appender. |
| final SocketAppender appender = SocketAppender |
| .newBuilder() |
| .setHost("localhost") |
| .setBufferedIo(false) |
| .setPort(port) |
| .setReconnectDelayMillis(100) |
| .setName("test") |
| .setImmediateFail(false) |
| .setIgnoreExceptions(false) |
| .setLayout(layout) |
| .build(); |
| |
| // Start the appender. |
| appender.start(); |
| |
| // Transfer and verify the log events. |
| for (int logEventIndex = 0; logEventIndex < logEvents.size(); logEventIndex++) { |
| |
| // Send the log event. |
| final LogEvent logEvent = logEvents.get(logEventIndex); |
| appender.append(logEvent); |
| appender.getManager().flush(); |
| |
| // Pull the parsed log event. |
| final JsonNode node = server.receivedNodes.poll(3, TimeUnit.SECONDS); |
| assertThat(node) |
| .as("logEventIndex=%d", logEventIndex) |
| .isNotNull(); |
| |
| // Verify the received content. |
| final String expectedMessage = logEvent.getMessage().getFormattedMessage(); |
| final String expectedMessageChars = explainChars(expectedMessage); |
| final String actualMessage = point(node, "message").asText(); |
| final String actualMessageChars = explainChars(actualMessage); |
| assertThat(actualMessageChars) |
| .as("logEventIndex=%d", logEventIndex) |
| .isEqualTo(expectedMessageChars); |
| |
| } |
| |
| // Verify that there were no overflows. |
| assertThat(server.droppedNodeCount).isZero(); |
| |
| } |
| |
| } |
| |
| private static List<LogEvent> createNastyLogEvents() { |
| return createNastyMessages() |
| .stream() |
| .map(message -> Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setMessage(message) |
| .build()) |
| .collect(Collectors.toList()); |
| } |
| |
| private static List<SimpleMessage> createNastyMessages() { |
| |
| // Determine the message count and character offset. |
| final int messageCount = 1024; |
| final int minChar = Character.MIN_VALUE; |
| final int maxChar = Character.MIN_HIGH_SURROGATE - 1; |
| final int totalCharCount = maxChar - minChar + 1; |
| final int charOffset = totalCharCount / messageCount; |
| |
| // Populate messages. |
| List<SimpleMessage> messages = new ArrayList<>(messageCount); |
| for (int messageIndex = 0; messageIndex < messageCount; messageIndex++) { |
| final StringBuilder stringBuilder = new StringBuilder(messageIndex + "@"); |
| for (int charIndex = 0; charIndex < charOffset; charIndex++) { |
| final char c = (char) (minChar + messageIndex * charOffset + charIndex); |
| stringBuilder.append(c); |
| } |
| final String messageString = stringBuilder.toString(); |
| final SimpleMessage message = new SimpleMessage(messageString); |
| messages.add(message); |
| } |
| return messages; |
| |
| } |
| |
| private static final class JsonAcceptingTcpServer extends Thread implements AutoCloseable { |
| |
| private final ServerSocket serverSocket; |
| |
| private final BlockingQueue<JsonNode> receivedNodes; |
| |
| private volatile int droppedNodeCount = 0; |
| |
| private volatile boolean closed = false; |
| |
| private JsonAcceptingTcpServer( |
| final int port, |
| final int capacity) throws IOException { |
| this.serverSocket = new ServerSocket(port); |
| this.receivedNodes = new ArrayBlockingQueue<>(capacity); |
| serverSocket.setReuseAddress(true); |
| serverSocket.setSoTimeout(5_000); |
| setDaemon(true); |
| start(); |
| } |
| |
| @Override |
| public void run() { |
| try { |
| try (final Socket socket = serverSocket.accept()) { |
| final InputStream inputStream = socket.getInputStream(); |
| while (!closed) { |
| final MappingIterator<JsonNode> iterator = JacksonFixture |
| .getObjectMapper() |
| .readerFor(JsonNode.class) |
| .readValues(inputStream); |
| while (iterator.hasNextValue()) { |
| final JsonNode value = iterator.nextValue(); |
| synchronized (this) { |
| final boolean added = receivedNodes.offer(value); |
| if (!added) { |
| droppedNodeCount++; |
| } |
| } |
| } |
| } |
| } |
| } catch (final EOFException ignored) { |
| // Socket is closed. |
| } catch (final Exception error) { |
| if (!closed) { |
| throw new RuntimeException(error); |
| } |
| } |
| } |
| |
| @Override |
| public synchronized void close() throws InterruptedException { |
| if (closed) { |
| throw new IllegalStateException("shutdown has already been invoked"); |
| } |
| closed = true; |
| interrupt(); |
| join(3_000L); |
| } |
| |
| } |
| |
| private static String explainChars(final String input) { |
| return IntStream |
| .range(0, input.length()) |
| .mapToObj(i -> { |
| final char c = input.charAt(i); |
| return String.format("'%c' (%04X)", c, (int) c); |
| }) |
| .collect(Collectors.joining(", ")); |
| } |
| |
| @Test |
| public void test_PatternResolver() { |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map( |
| "message", Map( |
| "$resolver", "pattern", |
| "pattern", "%p:%m"))); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .build(); |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("foo"); |
| final Level level = Level.FATAL; |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setMessage(message) |
| .setLevel(level) |
| .build(); |
| |
| // Check the serialized event. |
| usingSerializedLogEventAccessor(layout, logEvent, accessor -> { |
| final String expectedMessage = String.format( |
| "%s:%s", |
| level, message.getFormattedMessage()); |
| assertThat(accessor.getString("message")).isEqualTo(expectedMessage); |
| }); |
| |
| } |
| |
| @Test |
| public void test_unresolvable_nested_fields_are_skipped() { |
| |
| // Create the event template. |
| final String eventTemplate = writeJson(Map( |
| "exception", Map( |
| "message", Map( |
| "$resolver", "exception", |
| "field", "message"), |
| "className", Map( |
| "$resolver", "exception", |
| "field", "className")), |
| "exceptionRootCause", Map( |
| "message", Map( |
| "$resolver", "exceptionRootCause", |
| "field", "message"), |
| "className", Map( |
| "$resolver", "exceptionRootCause", |
| "field", "className")), |
| "source", Map( |
| "lineNumber", Map( |
| "$resolver", "source", |
| "field", "lineNumber"), |
| "fileName", Map( |
| "$resolver", "source", |
| "field", "fileName")), |
| "emptyMap", Collections.emptyMap(), |
| "emptyList", Collections.emptyList(), |
| "null", null)); |
| |
| // Create the layout. |
| final JsonTemplateLayout layout = JsonTemplateLayout |
| .newBuilder() |
| .setConfiguration(CONFIGURATION) |
| .setEventTemplate(eventTemplate) |
| .setStackTraceEnabled(false) // Disable "exception" and "exceptionRootCause" resolvers. |
| .setLocationInfoEnabled(false) // Disable the "source" resolver. |
| .build(); |
| |
| // Create the log event. |
| final SimpleMessage message = new SimpleMessage("foo"); |
| final Level level = Level.FATAL; |
| final Exception thrown = new RuntimeException("bar"); |
| final LogEvent logEvent = Log4jLogEvent |
| .newBuilder() |
| .setLoggerName(LOGGER_NAME) |
| .setMessage(message) |
| .setLevel(level) |
| .setThrown(thrown) |
| .build(); |
| |
| // Check the serialized event. |
| final String expectedSerializedLogEventJson = |
| "{}" + JsonTemplateLayoutDefaults.getEventDelimiter(); |
| final String actualSerializedLogEventJson = layout.toSerializable(logEvent); |
| Assertions |
| .assertThat(actualSerializedLogEventJson) |
| .isEqualTo(expectedSerializedLogEventJson); |
| |
| } |
| |
| private static String writeJson(final Object value) { |
| final StringBuilder stringBuilder = JSON_WRITER.getStringBuilder(); |
| stringBuilder.setLength(0); |
| try { |
| JSON_WRITER.writeValue(value); |
| return stringBuilder.toString(); |
| } finally { |
| stringBuilder.setLength(0); |
| } |
| } |
| |
| private static void usingSerializedLogEventAccessor( |
| final Layout<String> layout, |
| final LogEvent logEvent, |
| final Consumer<MapAccessor> accessorConsumer) { |
| final String serializedLogEventJson = layout.toSerializable(logEvent); |
| @SuppressWarnings("unchecked") |
| final Map<String, Object> serializedLogEvent = |
| (Map<String, Object>) readJson(serializedLogEventJson); |
| final MapAccessor serializedLogEventAccessor = new MapAccessor(serializedLogEvent); |
| accessorConsumer.accept(serializedLogEventAccessor); |
| } |
| |
| private static Object readJson(final String json) { |
| return JsonReader.read(json); |
| } |
| |
| private static Map<String, Object> Map(final Object... pairs) { |
| final Map<String, Object> map = new LinkedHashMap<>(); |
| if (pairs.length % 2 != 0) { |
| throw new IllegalArgumentException("odd number of arguments"); |
| } |
| for (int i = 0; i < pairs.length; i += 2) { |
| final String key = (String) pairs[i]; |
| final Object value = pairs[i + 1]; |
| map.put(key, value); |
| } |
| return map; |
| } |
| |
| } |