| /* |
| * 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.jackson.yaml.layout; |
| |
| import java.nio.charset.StandardCharsets; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.logging.log4j.Level; |
| import org.apache.logging.log4j.ThreadContext; |
| import org.apache.logging.log4j.core.categories.Layouts; |
| import org.apache.logging.log4j.core.Appender; |
| import org.apache.logging.log4j.core.BasicConfigurationFactory; |
| import org.apache.logging.log4j.core.Logger; |
| import org.apache.logging.log4j.core.LoggerContext; |
| import org.apache.logging.log4j.core.config.Configuration; |
| import org.apache.logging.log4j.core.config.ConfigurationFactory; |
| import org.apache.logging.log4j.core.impl.Log4jLogEvent; |
| import org.apache.logging.log4j.core.impl.MutableLogEvent; |
| import org.apache.logging.log4j.core.layout.LogEventFixtures; |
| import org.apache.logging.log4j.core.lookup.JavaLookup; |
| import org.apache.logging.log4j.core.util.KeyValuePair; |
| import org.apache.logging.log4j.jackson.AbstractJacksonLayout; |
| import org.apache.logging.log4j.jackson.yaml.Log4jYamlObjectMapper; |
| import org.apache.logging.log4j.message.SimpleMessage; |
| import org.apache.logging.log4j.spi.AbstractLogger; |
| import org.apache.logging.log4j.test.appender.ListAppender; |
| import org.apache.logging.log4j.util.Strings; |
| import org.junit.AfterClass; |
| import org.junit.Assert; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| import org.junit.experimental.categories.Category; |
| |
| import static org.hamcrest.CoreMatchers.*; |
| import static org.junit.Assert.*; |
| |
| /** |
| * Tests the YamlLayout class. |
| */ |
| @Category(Layouts.Yaml.class) |
| public class YamlLayoutTest { |
| static ConfigurationFactory cf = new BasicConfigurationFactory(); |
| |
| @AfterClass |
| public static void cleanupClass() { |
| ConfigurationFactory.removeConfigurationFactory(cf); |
| ThreadContext.clearAll(); |
| } |
| |
| @BeforeClass |
| public static void setupClass() { |
| ThreadContext.clearAll(); |
| ConfigurationFactory.setConfigurationFactory(cf); |
| final LoggerContext ctx = LoggerContext.getContext(); |
| ctx.reconfigure(); |
| } |
| |
| LoggerContext ctx = LoggerContext.getContext(); |
| |
| Logger rootLogger = this.ctx.getRootLogger(); |
| |
| private void checkAt(final String expected, final int lineIndex, final List<String> list) { |
| final String trimedLine = list.get(lineIndex).trim(); |
| assertEquals("Incorrect line index " + lineIndex + ": " + Strings.dquote(trimedLine), trimedLine, expected); |
| } |
| |
| private void checkContains(final String expected, final List<String> list) { |
| for (final String string : list) { |
| final String trimedLine = string.trim(); |
| if (trimedLine.equals(expected)) { |
| return; |
| } |
| } |
| Assert.fail("Cannot find " + expected + " in " + list); |
| } |
| |
| private void checkMapEntry(final String key, final String value, final boolean compact, final String str) { |
| // "name":"value" |
| //final String expected = String.format("- key: \"%s\"\n value: \"%s\"", key, value); |
| final String expected = String.format("%s: \"%s\"", key, value); |
| assertThat(str, containsString(expected)); |
| } |
| |
| private void checkProperty(final String key, final String value, final boolean compact, final String str, |
| final boolean isValue) { |
| final String propSep = this.toPropertySeparator(compact, isValue); |
| // {"key":"MDC.B","value":"B_Value"} |
| final String expected = String.format("%s%s\"%s\"", key, propSep, value); |
| assertThat(str, containsString(expected)); |
| } |
| |
| private void checkPropertyName(final String name, final boolean compact, final String str, final boolean isValue) { |
| final String propSep = this.toPropertySeparator(compact, isValue); |
| assertThat(str, containsString(name + propSep)); |
| } |
| |
| private void checkPropertyNameAbsent(final String name, final boolean compact, final String str, final boolean isValue) { |
| final String propSep = this.toPropertySeparator(compact, isValue); |
| assertThat(str, not(containsString(name + propSep))); |
| } |
| |
| private String prepareYAMLForStacktraceTests(final boolean stacktraceAsString) { |
| final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); |
| // @formatter:off |
| final AbstractJacksonLayout layout = YamlLayout.newBuilder() |
| .setIncludeStacktrace(true) |
| .setStacktraceAsString(stacktraceAsString) |
| .build(); |
| // @formatter:on |
| return layout.toSerializable(expected); |
| } |
| |
| @Test |
| public void testAdditionalFields() throws Exception { |
| final AbstractJacksonLayout layout = YamlLayout.newBuilder() |
| .setLocationInfo(false) |
| .setProperties(false) |
| .setIncludeStacktrace(false) |
| .setAdditionalFields(new KeyValuePair[] { |
| new KeyValuePair("KEY1", "VALUE1"), |
| new KeyValuePair("KEY2", "${java:runtime}"), }) |
| .setCharset(StandardCharsets.UTF_8) |
| .setConfiguration(ctx.getConfiguration()) |
| .build(); |
| final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); |
| assertThat(str, containsString("KEY1: \"VALUE1\"")); |
| assertThat(str, containsString("KEY2: \"" + new JavaLookup().getRuntime() + "\"")); |
| } |
| |
| @Test |
| public void testMutableLogEvent() throws Exception { |
| final AbstractJacksonLayout layout = YamlLayout.newBuilder() |
| .setLocationInfo(false) |
| .setProperties(false) |
| .setIncludeStacktrace(false) |
| .setAdditionalFields(new KeyValuePair[] { |
| new KeyValuePair("KEY1", "VALUE1"), |
| new KeyValuePair("KEY2", "${java:runtime}"), }) |
| .setCharset(StandardCharsets.UTF_8) |
| .setConfiguration(ctx.getConfiguration()) |
| .build(); |
| Log4jLogEvent logEvent = LogEventFixtures.createLogEvent(); |
| final MutableLogEvent mutableEvent = new MutableLogEvent(); |
| mutableEvent.initFrom(logEvent); |
| final String strLogEvent = layout.toSerializable(logEvent); |
| final String strMutableEvent = layout.toSerializable(mutableEvent); |
| assertEquals(strMutableEvent, strLogEvent, strMutableEvent); |
| } |
| |
| private void testAllFeatures(final boolean includeSource, final boolean compact, final boolean eventEol, |
| final boolean includeContext, final boolean contextMapAslist, final boolean includeStacktrace) throws Exception { |
| final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); |
| final AbstractJacksonLayout layout = YamlLayout.newBuilder() |
| .setLocationInfo(includeSource) |
| .setProperties(includeContext) |
| .setIncludeStacktrace(includeStacktrace) |
| .setCharset(StandardCharsets.UTF_8) |
| .build(); |
| final String str = layout.toSerializable(expected); |
| // System.out.println(str); |
| // Just check for \n since \r might or might not be there. |
| assertEquals(str, !compact || eventEol, str.contains("\n")); |
| assertEquals(str, includeSource, str.contains("source")); |
| assertEquals(str, includeContext, str.contains("contextMap")); |
| final Log4jLogEvent actual = new Log4jYamlObjectMapper(contextMapAslist, includeStacktrace,false).readValue(str, Log4jLogEvent.class); |
| LogEventFixtures.assertEqualLogEvents(expected, actual, includeSource, includeContext, includeStacktrace); |
| if (includeContext) { |
| this.checkMapEntry("MDC.A", "A_Value", compact, str); |
| this.checkMapEntry("MDC.B", "B_Value", compact, str); |
| } |
| // |
| assertNull(actual.getThrown()); |
| // make sure the names we want are used |
| this.checkPropertyName("instant", compact, str, false); |
| this.checkPropertyName("thread", compact, str, true); // and not threadName |
| this.checkPropertyName("level", compact, str, true); |
| this.checkPropertyName("loggerName", compact, str, true); |
| this.checkPropertyName("marker", compact, str, false); |
| this.checkPropertyName("name", compact, str, true); |
| this.checkPropertyName("parents", compact, str, false); |
| this.checkPropertyName("message", compact, str, true); |
| this.checkPropertyName("thrown", compact, str, false); |
| this.checkPropertyName("cause", compact, str, false); |
| this.checkPropertyName("commonElementCount", compact, str, true); |
| this.checkPropertyName("localizedMessage", compact, str, true); |
| if (includeStacktrace) { |
| this.checkPropertyName("extendedStackTrace", compact, str, false); |
| this.checkPropertyName("class", compact, str, true); |
| this.checkPropertyName("method", compact, str, true); |
| this.checkPropertyName("file", compact, str, true); |
| this.checkPropertyName("line", compact, str, true); |
| this.checkPropertyName("exact", compact, str, true); |
| this.checkPropertyName("location", compact, str, true); |
| this.checkPropertyName("version", compact, str, true); |
| } else { |
| this.checkPropertyNameAbsent("extendedStackTrace", compact, str, false); |
| } |
| this.checkPropertyName("suppressed", compact, str, false); |
| this.checkPropertyName("loggerFqcn", compact, str, true); |
| this.checkPropertyName("endOfBatch", compact, str, true); |
| if (includeContext) { |
| this.checkPropertyName("contextMap", compact, str, false); |
| } else { |
| this.checkPropertyNameAbsent("contextMap", compact, str, false); |
| } |
| this.checkPropertyName("contextStack", compact, str, false); |
| if (includeSource) { |
| this.checkPropertyName("source", compact, str, false); |
| } else { |
| this.checkPropertyNameAbsent("source", compact, str, false); |
| } |
| // check some attrs |
| this.checkProperty("loggerFqcn", "f.q.c.n", compact, str, true); |
| this.checkProperty("loggerName", "a.B", compact, str, true); |
| } |
| |
| @Test |
| public void testContentType() { |
| final AbstractJacksonLayout layout = YamlLayout.createDefaultLayout(); |
| assertEquals("application/yaml; charset=UTF-8", layout.getContentType()); |
| } |
| |
| @Test |
| public void testDefaultCharset() { |
| final AbstractJacksonLayout layout = YamlLayout.createDefaultLayout(); |
| assertEquals(StandardCharsets.UTF_8, layout.getCharset()); |
| } |
| |
| @Test |
| public void testEscapeLayout() throws Exception { |
| final Map<String, Appender> appenders = this.rootLogger.getAppenders(); |
| for (final Appender appender : appenders.values()) { |
| this.rootLogger.removeAppender(appender); |
| } |
| final Configuration configuration = rootLogger.getContext().getConfiguration(); |
| // set up appender |
| final AbstractJacksonLayout layout = YamlLayout.newBuilder() |
| .setLocationInfo(true) |
| .setProperties(true) |
| .setIncludeStacktrace(true) |
| .setConfiguration(configuration) |
| .build(); |
| |
| final ListAppender appender = new ListAppender("List", null, layout, true, false); |
| appender.start(); |
| |
| // set appender on root and set level to debug |
| this.rootLogger.addAppender(appender); |
| this.rootLogger.setLevel(Level.DEBUG); |
| |
| // output starting message |
| this.rootLogger.debug("Here is a quote ' and then a double quote \""); |
| |
| appender.stop(); |
| |
| final List<String> list = appender.getMessages(); |
| |
| this.checkAt("---", 0, list); |
| this.checkContains("level: \"DEBUG\"", list); |
| this.checkContains("message: \"Here is a quote ' and then a double quote \\\"\"", list); |
| this.checkContains("loggerFqcn: \"" + AbstractLogger.class.getName() + "\"", list); |
| for (final Appender app : appenders.values()) { |
| this.rootLogger.addAppender(app); |
| } |
| } |
| |
| @Test |
| public void testExcludeStacktrace() throws Exception { |
| this.testAllFeatures(false, false, false, false, false, false); |
| } |
| |
| @Test |
| public void testIncludeNullDelimiterFalse() throws Exception { |
| final AbstractJacksonLayout layout = YamlLayout.newBuilder() |
| .setIncludeNullDelimiter(false) |
| .build(); |
| final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); |
| assertThat(str, not(endsWith("\0"))); |
| } |
| |
| @Test |
| public void testIncludeNullDelimiterTrue() throws Exception { |
| final AbstractJacksonLayout layout = YamlLayout.newBuilder() |
| .setIncludeNullDelimiter(true) |
| .build(); |
| final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); |
| assertThat(str, endsWith("\0")); |
| } |
| |
| /** |
| * Test case for MDC conversion pattern. |
| */ |
| @Test |
| public void testLayout() throws Exception { |
| final Map<String, Appender> appenders = this.rootLogger.getAppenders(); |
| for (final Appender appender : appenders.values()) { |
| this.rootLogger.removeAppender(appender); |
| } |
| final Configuration configuration = rootLogger.getContext().getConfiguration(); |
| // set up appender |
| // Use [[ and ]] to test header and footer (instead of [ and ]) |
| final AbstractJacksonLayout layout = YamlLayout.newBuilder().setConfiguration(configuration).setLocationInfo(true).setProperties(true) |
| .setHeader("[[".getBytes(StandardCharsets.UTF_8)).setFooter("]]".getBytes(StandardCharsets.UTF_8)) |
| .setIncludeStacktrace(true).build(); |
| final ListAppender appender = new ListAppender("List", null, layout, true, false); |
| appender.start(); |
| |
| // set appender on root and set level to debug |
| this.rootLogger.addAppender(appender); |
| this.rootLogger.setLevel(Level.DEBUG); |
| |
| // output starting message |
| this.rootLogger.debug("starting mdc pattern test"); |
| |
| this.rootLogger.debug("empty mdc"); |
| |
| ThreadContext.put("key1", "value1"); |
| ThreadContext.put("key2", "value2"); |
| |
| this.rootLogger.debug("filled mdc"); |
| |
| ThreadContext.remove("key1"); |
| ThreadContext.remove("key2"); |
| |
| this.rootLogger.error("finished mdc pattern test", new NullPointerException("test")); |
| |
| appender.stop(); |
| |
| final List<String> list = appender.getMessages(); |
| |
| this.checkAt("---", 0, list); |
| this.checkContains("loggerFqcn: \"" + AbstractLogger.class.getName() + "\"", list); |
| this.checkContains("level: \"DEBUG\"", list); |
| this.checkContains("message: \"starting mdc pattern test\"", list); |
| for (final Appender app : appenders.values()) { |
| this.rootLogger.addAppender(app); |
| } |
| } |
| |
| @Test |
| public void testLayoutLoggerName() throws Exception { |
| final AbstractJacksonLayout layout = YamlLayout.newBuilder() |
| .setLocationInfo(false) |
| .setProperties(false) |
| .setIncludeStacktrace(true) |
| .setCharset(StandardCharsets.UTF_8) |
| .build(); |
| final Log4jLogEvent expected = Log4jLogEvent.newBuilder() // |
| .setLoggerName("a.B") // |
| .setLoggerFqcn("f.q.c.n") // |
| .setLevel(Level.DEBUG) // |
| .setMessage(new SimpleMessage("M")) // |
| .setThreadName("threadName") // |
| .setTimeMillis(1).build(); |
| final String str = layout.toSerializable(expected); |
| assertThat(str, containsString("loggerName: \"a.B\"")); |
| final Log4jLogEvent actual = new Log4jYamlObjectMapper(false, true, false).readValue(str, Log4jLogEvent.class); |
| assertEquals(expected.getLoggerName(), actual.getLoggerName()); |
| assertEquals(expected, actual); |
| } |
| |
| @Test |
| public void testLocationOffCompactOffMdcOff() throws Exception { |
| this.testAllFeatures(false, false, false, false, false, true); |
| } |
| |
| @Test |
| public void testLocationOnCompactOffEventEolOffMdcOn() throws Exception { |
| this.testAllFeatures(true, false, false, true, false, true); |
| } |
| |
| @Test |
| public void testStacktraceAsNonString() throws Exception { |
| final String str = prepareYAMLForStacktraceTests(false); |
| assertThat(str, containsString("extendedStackTrace:\n - ")); |
| } |
| |
| @Test |
| public void testStacktraceAsString() throws Exception { |
| final String str = prepareYAMLForStacktraceTests(true); |
| assertThat(str, containsString("extendedStackTrace: \"java.lang.NullPointerException")); |
| } |
| |
| private String toPropertySeparator(final boolean compact, final boolean value) { |
| return value ? ": " : ":"; |
| } |
| } |