From c68c05311986f393f13d2aa59a4a49f94cf7ee67 Mon Sep 17 00:00:00 2001 From: Imri Zvik Date: Thu, 25 May 2017 17:32:51 +0300 Subject: [PATCH] LOG4J2-1923 Allow having the stacktrace as a string in JSONLayout, XMLLayout and YAMLLayout --- .../log4j/core/impl/ThrowableProxy.java | 10 +++ .../log4j/core/jackson/Initializers.java | 8 +- .../log4j/core/jackson/Log4jJsonModule.java | 8 +- .../core/jackson/Log4jJsonObjectMapper.java | 6 +- .../log4j/core/jackson/Log4jXmlModule.java | 6 +- .../core/jackson/Log4jXmlObjectMapper.java | 6 +- .../log4j/core/jackson/Log4jYamlModule.java | 8 +- .../core/jackson/Log4jYamlObjectMapper.java | 6 +- ...wableProxyWithStacktraceAsStringMixIn.java | 77 +++++++++++++++++++ .../core/layout/AbstractJacksonLayout.java | 12 +++ .../log4j/core/layout/JacksonFactory.java | 20 +++-- .../logging/log4j/core/layout/JsonLayout.java | 15 ++-- .../logging/log4j/core/layout/XmlLayout.java | 19 +++-- .../logging/log4j/core/layout/YamlLayout.java | 15 ++-- .../logging/log4j/MarkerMixInXmlTest.java | 2 +- .../core/jackson/LevelMixInJsonTest.java | 2 +- .../jackson/StackTraceElementMixInTest.java | 2 +- .../log4j/core/layout/JsonLayoutTest.java | 28 ++++++- .../log4j/core/layout/XmlLayoutTest.java | 6 +- .../log4j/core/layout/YamlLayoutTest.java | 12 +-- src/site/xdoc/manual/layouts.xml.vm | 15 ++++ 21 files changed, 222 insertions(+), 61 deletions(-) create mode 100644 log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ThrowableProxyWithStacktraceAsStringMixIn.java diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java index 8b80be09cd7..4cbac54fe4f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java @@ -424,6 +424,16 @@ public ExtendedStackTraceElement[] getExtendedStackTrace() { return this.extendedStackTrace; } + /** + * Format the stack trace including packaging information. + * + * @return The formatted stack trace including packaging information. + * @param suffix + */ + public String getExtendedStackTraceAsString() { + return this.getExtendedStackTraceAsString(null, PlainTextRenderer.getInstance(), ""); + } + /** * Format the stack trace including packaging information. * diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Initializers.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Initializers.java index dc1f81a75af..b7387e5fddb 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Initializers.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Initializers.java @@ -39,7 +39,7 @@ class Initializers { */ static class SetupContextInitializer { - void setupModule(final SetupContext context, final boolean includeStacktrace) { + void setupModule(final SetupContext context, final boolean includeStacktrace, final boolean stacktraceAsString) { // JRE classes: we cannot edit those with Jackson annotations context.setMixInAnnotations(StackTraceElement.class, StackTraceElementMixIn.class); // Log4j API classes: we do not want to edit those with Jackson annotations because the API module should not depend on Jackson. @@ -49,7 +49,7 @@ void setupModule(final SetupContext context, final boolean includeStacktrace) { // Log4j Core classes: we do not want to bring in Jackson at runtime if we do not have to. context.setMixInAnnotations(ExtendedStackTraceElement.class, ExtendedStackTraceElementMixIn.class); context.setMixInAnnotations(ThrowableProxy.class, - includeStacktrace ? ThrowableProxyMixIn.class : ThrowableProxyWithoutStacktraceMixIn.class); + includeStacktrace ? (stacktraceAsString ? ThrowableProxyWithStacktraceAsStringMixIn.class : ThrowableProxyMixIn.class ) : ThrowableProxyWithoutStacktraceMixIn.class); } } @@ -60,7 +60,7 @@ void setupModule(final SetupContext context, final boolean includeStacktrace) { */ static class SetupContextJsonInitializer { - void setupModule(final SetupContext context, final boolean includeStacktrace) { + void setupModule(final SetupContext context, final boolean includeStacktrace, final boolean stacktraceAsString) { // JRE classes: we cannot edit those with Jackson annotations context.setMixInAnnotations(StackTraceElement.class, StackTraceElementMixIn.class); // Log4j API classes: we do not want to edit those with Jackson annotations because the API module should not depend on Jackson. @@ -70,7 +70,7 @@ void setupModule(final SetupContext context, final boolean includeStacktrace) { // Log4j Core classes: we do not want to bring in Jackson at runtime if we do not have to. context.setMixInAnnotations(ExtendedStackTraceElement.class, ExtendedStackTraceElementMixIn.class); context.setMixInAnnotations(ThrowableProxy.class, - includeStacktrace ? ThrowableProxyMixIn.class : ThrowableProxyWithoutStacktraceMixIn.class); + includeStacktrace ? (stacktraceAsString ? ThrowableProxyWithStacktraceAsStringMixIn.class : ThrowableProxyMixIn.class ) : ThrowableProxyWithoutStacktraceMixIn.class); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonModule.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonModule.java index 48bd1374bca..e6aa49e1c75 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonModule.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonModule.java @@ -33,11 +33,13 @@ class Log4jJsonModule extends SimpleModule { private static final long serialVersionUID = 1L; private final boolean encodeThreadContextAsList; private final boolean includeStacktrace; + private final boolean stacktraceAsString; - Log4jJsonModule(final boolean encodeThreadContextAsList, final boolean includeStacktrace) { + Log4jJsonModule(final boolean encodeThreadContextAsList, final boolean includeStacktrace, final boolean stacktraceAsString) { super(Log4jJsonModule.class.getName(), new Version(2, 0, 0, null, null, null)); this.encodeThreadContextAsList = encodeThreadContextAsList; this.includeStacktrace = includeStacktrace; + this.stacktraceAsString = stacktraceAsString; // MUST init here. // Calling this from setupModule is too late! //noinspection ThisEscapedInObjectConstruction @@ -49,9 +51,9 @@ public void setupModule(final SetupContext context) { // Calling super is a MUST! super.setupModule(context); if (encodeThreadContextAsList) { - new SetupContextInitializer().setupModule(context, includeStacktrace); + new SetupContextInitializer().setupModule(context, includeStacktrace, stacktraceAsString); } else { - new SetupContextJsonInitializer().setupModule(context, includeStacktrace); + new SetupContextJsonInitializer().setupModule(context, includeStacktrace, stacktraceAsString); } } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonObjectMapper.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonObjectMapper.java index 36b130db327..858c9e7d07a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonObjectMapper.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jJsonObjectMapper.java @@ -33,14 +33,14 @@ public class Log4jJsonObjectMapper extends ObjectMapper { * Create a new instance using the {@link Log4jJsonModule}. */ public Log4jJsonObjectMapper() { - this(false, true); + this(false, true, false); } /** * Create a new instance using the {@link Log4jJsonModule}. */ - public Log4jJsonObjectMapper(final boolean encodeThreadContextAsList, final boolean includeStacktrace) { - this.registerModule(new Log4jJsonModule(encodeThreadContextAsList, includeStacktrace)); + public Log4jJsonObjectMapper(final boolean encodeThreadContextAsList, final boolean includeStacktrace, final boolean stacktraceAsString) { + this.registerModule(new Log4jJsonModule(encodeThreadContextAsList, includeStacktrace, stacktraceAsString)); this.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jXmlModule.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jXmlModule.java index 449e00189c8..24292eda04d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jXmlModule.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jXmlModule.java @@ -30,10 +30,12 @@ final class Log4jXmlModule extends JacksonXmlModule { private static final long serialVersionUID = 1L; private final boolean includeStacktrace; + private final boolean stacktraceAsString; - Log4jXmlModule(final boolean includeStacktrace) { + Log4jXmlModule(final boolean includeStacktrace, final boolean stacktraceAsString) { super(); this.includeStacktrace = includeStacktrace; + this.stacktraceAsString = stacktraceAsString; // MUST init here. // Calling this from setupModule is too late! new SimpleModuleInitializer().initialize(this); @@ -43,6 +45,6 @@ final class Log4jXmlModule extends JacksonXmlModule { public void setupModule(final SetupContext context) { // Calling super is a MUST! super.setupModule(context); - new SetupContextInitializer().setupModule(context, includeStacktrace); + new SetupContextInitializer().setupModule(context, includeStacktrace, stacktraceAsString); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jXmlObjectMapper.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jXmlObjectMapper.java index 77d8ce4ab7d..b390a3f279d 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jXmlObjectMapper.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jXmlObjectMapper.java @@ -34,14 +34,14 @@ public class Log4jXmlObjectMapper extends XmlMapper { * Create a new instance using the {@link Log4jXmlModule}. */ public Log4jXmlObjectMapper() { - this(true); + this(true, false); } /** * Create a new instance using the {@link Log4jXmlModule}. */ - public Log4jXmlObjectMapper(final boolean includeStacktrace) { - super(new Log4jXmlModule(includeStacktrace)); + public Log4jXmlObjectMapper(final boolean includeStacktrace, final boolean stacktraceAsString) { + super(new Log4jXmlModule(includeStacktrace, stacktraceAsString)); this.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlModule.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlModule.java index 2623ade195c..39ea514bb6a 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlModule.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlModule.java @@ -33,11 +33,13 @@ final class Log4jYamlModule extends SimpleModule { private static final long serialVersionUID = 1L; private final boolean encodeThreadContextAsList; private final boolean includeStacktrace; + private final boolean stacktraceAsString; - Log4jYamlModule(final boolean encodeThreadContextAsList, final boolean includeStacktrace) { + Log4jYamlModule(final boolean encodeThreadContextAsList, final boolean includeStacktrace, final boolean stacktraceAsString) { super(Log4jYamlModule.class.getName(), new Version(2, 0, 0, null, null, null)); this.encodeThreadContextAsList = encodeThreadContextAsList; this.includeStacktrace = includeStacktrace; + this.stacktraceAsString = stacktraceAsString; // MUST init here. // Calling this from setupModule is too late! //noinspection ThisEscapedInObjectConstruction @@ -49,9 +51,9 @@ public void setupModule(final SetupContext context) { // Calling super is a MUST! super.setupModule(context); if (encodeThreadContextAsList) { - new SetupContextInitializer().setupModule(context, includeStacktrace); + new SetupContextInitializer().setupModule(context, includeStacktrace, stacktraceAsString); } else { - new SetupContextJsonInitializer().setupModule(context, includeStacktrace); + new SetupContextJsonInitializer().setupModule(context, includeStacktrace, stacktraceAsString); } } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlObjectMapper.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlObjectMapper.java index 2ccb567e4f0..0b80591d6b1 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlObjectMapper.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/Log4jYamlObjectMapper.java @@ -34,14 +34,14 @@ public class Log4jYamlObjectMapper extends YAMLMapper { * Create a new instance using the {@link Log4jYamlModule}. */ public Log4jYamlObjectMapper() { - this(false, true); + this(false, true, false); } /** * Create a new instance using the {@link Log4jYamlModule}. */ - public Log4jYamlObjectMapper(final boolean encodeThreadContextAsList, final boolean includeStacktrace) { - this.registerModule(new Log4jYamlModule(encodeThreadContextAsList, includeStacktrace)); + public Log4jYamlObjectMapper(final boolean encodeThreadContextAsList, final boolean includeStacktrace, final boolean stacktraceAsString) { + this.registerModule(new Log4jYamlModule(encodeThreadContextAsList, includeStacktrace, stacktraceAsString)); this.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ThrowableProxyWithStacktraceAsStringMixIn.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ThrowableProxyWithStacktraceAsStringMixIn.java new file mode 100644 index 00000000000..b6f55868cc2 --- /dev/null +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/jackson/ThrowableProxyWithStacktraceAsStringMixIn.java @@ -0,0 +1,77 @@ +/* + * 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.core.jackson; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import org.apache.logging.log4j.core.impl.ExtendedStackTraceElement; +import org.apache.logging.log4j.core.impl.ThrowableProxy; + +/** + * Mix-in for {@link org.apache.logging.log4j.core.impl.ThrowableProxy}. + */ +abstract class ThrowableProxyWithStacktraceAsStringMixIn { + + @JsonProperty(JsonConstants.ELT_CAUSE) + @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_CAUSE) + private ThrowableProxyWithStacktraceAsStringMixIn causeProxy; + + @JsonProperty + @JacksonXmlProperty(isAttribute = true) + private int commonElementCount; + + @JsonIgnore + private ExtendedStackTraceElement[] extendedStackTrace; + + @JsonProperty + @JacksonXmlProperty(isAttribute = true) + private String localizedMessage; + + @JsonProperty + @JacksonXmlProperty(isAttribute = true) + private String message; + + @JsonProperty + @JacksonXmlProperty(isAttribute = true) + private String name; + + @JsonIgnore + private transient Throwable throwable; + + @JsonIgnore + public abstract String getCauseStackTraceAsString(); + + @JsonProperty(JsonConstants.ELT_EXTENDED_STACK_TRACE) + public abstract String getExtendedStackTraceAsString(); + + @JsonIgnore + public abstract StackTraceElement[] getStackTrace(); + + @JsonProperty(JsonConstants.ELT_SUPPRESSED) + @JacksonXmlElementWrapper(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_SUPPRESSED) + @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_SUPPRESSED_ITEM) + public abstract ThrowableProxy[] getSuppressedProxies(); + + @JsonIgnore + public abstract String getSuppressedStackTrace(); + + @JsonIgnore + public abstract Throwable getThrowable(); + +} diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java index 5d0252f1d3b..16dedf07c11 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractJacksonLayout.java @@ -56,6 +56,9 @@ public static abstract class Builder> extends AbstractStrin @PluginBuilderAttribute private boolean includeStacktrace = true; + @PluginBuilderAttribute + private boolean stacktraceAsString = false; + protected String toStringOrNull(final byte[] header) { return header == null ? null : new String(header, Charset.defaultCharset()); } @@ -88,6 +91,10 @@ public boolean isIncludeStacktrace() { return includeStacktrace; } + public boolean isStacktraceAsString() { + return stacktraceAsString; + } + public B setEventEol(final boolean eventEol) { this.eventEol = eventEol; return asBuilder(); @@ -122,6 +129,11 @@ public B setIncludeStacktrace(boolean includeStacktrace) { this.includeStacktrace = includeStacktrace; return asBuilder(); } + + public B setStacktraceAsString(boolean stacktraceAsString) { + this.stacktraceAsString = stacktraceAsString; + return asBuilder(); + } } protected final String eol; diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JacksonFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JacksonFactory.java index a62bf94193b..41b1ebec84f 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JacksonFactory.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JacksonFactory.java @@ -44,10 +44,12 @@ static class JSON extends JacksonFactory { private final boolean encodeThreadContextAsList; private final boolean includeStacktrace; + private final boolean stacktraceAsString; - public JSON(final boolean encodeThreadContextAsList, final boolean includeStacktrace) { + public JSON(final boolean encodeThreadContextAsList, final boolean includeStacktrace, final boolean stacktraceAsString) { this.encodeThreadContextAsList = encodeThreadContextAsList; this.includeStacktrace = includeStacktrace; + this.stacktraceAsString = stacktraceAsString; } @Override @@ -72,7 +74,7 @@ protected PrettyPrinter newCompactPrinter() { @Override protected ObjectMapper newObjectMapper() { - return new Log4jJsonObjectMapper(encodeThreadContextAsList, includeStacktrace); + return new Log4jJsonObjectMapper(encodeThreadContextAsList, includeStacktrace, stacktraceAsString); } @Override @@ -86,9 +88,12 @@ static class XML extends JacksonFactory { static final int DEFAULT_INDENT = 1; private final boolean includeStacktrace; + private final boolean stacktraceAsString; - public XML(final boolean includeStacktrace) { + + public XML(final boolean includeStacktrace, final boolean stacktraceAsString) { this.includeStacktrace = includeStacktrace; + this.stacktraceAsString = stacktraceAsString; } @Override @@ -114,7 +119,7 @@ protected PrettyPrinter newCompactPrinter() { @Override protected ObjectMapper newObjectMapper() { - return new Log4jXmlObjectMapper(includeStacktrace); + return new Log4jXmlObjectMapper(includeStacktrace, stacktraceAsString); } @Override @@ -126,9 +131,12 @@ protected PrettyPrinter newPrettyPrinter() { static class YAML extends JacksonFactory { private final boolean includeStacktrace; + private final boolean stacktraceAsString; + - public YAML(final boolean includeStacktrace) { + public YAML(final boolean includeStacktrace, final boolean stacktraceAsString) { this.includeStacktrace = includeStacktrace; + this.stacktraceAsString = stacktraceAsString; } @Override @@ -153,7 +161,7 @@ protected PrettyPrinter newCompactPrinter() { @Override protected ObjectMapper newObjectMapper() { - return new Log4jYamlObjectMapper(false, includeStacktrace); + return new Log4jYamlObjectMapper(false, includeStacktrace, stacktraceAsString); } @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java index 3b968b4d3d5..bd458c5bea5 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/JsonLayout.java @@ -85,7 +85,7 @@ public JsonLayout build() { final String headerPattern = toStringOrNull(getHeader()); final String footerPattern = toStringOrNull(getFooter()); return new JsonLayout(getConfiguration(), isLocationInfo(), isProperties(), encodeThreadContextAsList, isComplete(), - isCompact(), getEventEol(), headerPattern, footerPattern, getCharset(), isIncludeStacktrace()); + isCompact(), getEventEol(), headerPattern, footerPattern, getCharset(), isIncludeStacktrace(), isStacktraceAsString()); } public boolean isPropertiesAsList() { @@ -101,8 +101,8 @@ public B setPropertiesAsList(boolean propertiesAsList) { protected JsonLayout(final Configuration config, final boolean locationInfo, final boolean properties, final boolean encodeThreadContextAsList, final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern, - final String footerPattern, final Charset charset, final boolean includeStacktrace) { - super(config, new JacksonFactory.JSON(encodeThreadContextAsList, includeStacktrace).newWriter( + final String footerPattern, final Charset charset, final boolean includeStacktrace, final boolean stacktraceAsString) { + super(config, new JacksonFactory.JSON(encodeThreadContextAsList, includeStacktrace, stacktraceAsString).newWriter( locationInfo, properties, compact), charset, compact, complete, eventEol, PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(), @@ -191,6 +191,8 @@ public String getContentType() { * The character set to use, if {@code null}, uses "UTF-8". * @param includeStacktrace * If "true", includes the stacktrace of any Throwable in the generated JSON, defaults to "true". + * @param stacktraceAsString + * If "true", the stacktrace will be rendered as string, and not nested object, defaults to "false". * @return A JSON Layout. * * @deprecated Use {@link #newBuilder()} instead @@ -208,12 +210,13 @@ public static JsonLayout createLayout( @PluginAttribute(value = "header", defaultString = DEFAULT_HEADER) final String headerPattern, @PluginAttribute(value = "footer", defaultString = DEFAULT_FOOTER) final String footerPattern, @PluginAttribute(value = "charset", defaultString = "UTF-8") final Charset charset, - @PluginAttribute(value = "includeStacktrace", defaultBoolean = true) final boolean includeStacktrace + @PluginAttribute(value = "includeStacktrace", defaultBoolean = true) final boolean includeStacktrace, + @PluginAttribute(value = "stacktraceAsString", defaultBoolean = false) final boolean stacktraceAsString // @formatter:on ) { final boolean encodeThreadContextAsList = properties && propertiesAsList; return new JsonLayout(config, locationInfo, properties, encodeThreadContextAsList, complete, compact, eventEol, - headerPattern, footerPattern, charset, includeStacktrace); + headerPattern, footerPattern, charset, includeStacktrace, stacktraceAsString); } @PluginBuilderFactory @@ -228,7 +231,7 @@ public static > B newBuilder() { */ public static JsonLayout createDefaultLayout() { return new JsonLayout(new DefaultConfiguration(), false, false, false, false, false, false, - DEFAULT_HEADER, DEFAULT_FOOTER, StandardCharsets.UTF_8, true); + DEFAULT_HEADER, DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false); } @Override diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java index 49de181c189..49641f197d9 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/XmlLayout.java @@ -69,7 +69,7 @@ public Builder() { @Override public XmlLayout build() { return new XmlLayout(getConfiguration(), isLocationInfo(), isProperties(), isComplete(), - isCompact(), getCharset(), isIncludeStacktrace()); + isCompact(), getCharset(), isIncludeStacktrace(), isStacktraceAsString()); } } @@ -78,14 +78,14 @@ public XmlLayout build() { */ @Deprecated protected XmlLayout(final boolean locationInfo, final boolean properties, final boolean complete, - final boolean compact, final Charset charset, final boolean includeStacktrace) { - this(null, locationInfo, properties, complete, compact, charset, includeStacktrace); + final boolean compact, final Charset charset, final boolean includeStacktrace, final boolean stacktraceAsString) { + this(null, locationInfo, properties, complete, compact, charset, includeStacktrace, stacktraceAsString); } private XmlLayout(final Configuration config, final boolean locationInfo, final boolean properties, final boolean complete, final boolean compact, final Charset charset, - final boolean includeStacktrace) { - super(config, new JacksonFactory.XML(includeStacktrace).newWriter( + final boolean includeStacktrace, final boolean stacktraceAsString) { + super(config, new JacksonFactory.XML(includeStacktrace, stacktraceAsString).newWriter( locationInfo, properties, compact), charset, compact, complete, false, null, null); } @@ -165,6 +165,7 @@ public String getContentType() { * @param charset The character set to use, if {@code null}, uses "UTF-8". * @param includeStacktrace * If "true", includes the stacktrace of any Throwable in the generated XML, defaults to "true". + * @param stacktraceAsString If "true", the stacktrace will be rendered as string, and not nested object, defaults to "false". * @return An XML Layout. * * @deprecated Use {@link #newBuilder()} instead @@ -177,10 +178,12 @@ public static XmlLayout createLayout( @PluginAttribute(value = "complete") final boolean complete, @PluginAttribute(value = "compact") final boolean compact, @PluginAttribute(value = "charset", defaultString = "UTF-8") final Charset charset, - @PluginAttribute(value = "includeStacktrace", defaultBoolean = true) final boolean includeStacktrace) + @PluginAttribute(value = "includeStacktrace", defaultBoolean = true) final boolean includeStacktrace, + @PluginAttribute(value = "stacktraceAsString", defaultBoolean = false) final boolean stacktraceAsString + ) // @formatter:on { - return new XmlLayout(null, locationInfo, properties, complete, compact, charset, includeStacktrace); + return new XmlLayout(null, locationInfo, properties, complete, compact, charset, includeStacktrace, stacktraceAsString); } @PluginBuilderFactory @@ -194,6 +197,6 @@ public static > B newBuilder() { * @return an XML Layout. */ public static XmlLayout createDefaultLayout() { - return new XmlLayout(null, false, false, false, false, StandardCharsets.UTF_8, true); + return new XmlLayout(null, false, false, false, false, StandardCharsets.UTF_8, true, false); } } diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java index bf8786b8796..40188b389d3 100644 --- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java +++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/YamlLayout.java @@ -62,14 +62,14 @@ public YamlLayout build() { final String headerPattern = toStringOrNull(getHeader()); final String footerPattern = toStringOrNull(getFooter()); return new YamlLayout(getConfiguration(), isLocationInfo(), isProperties(), isComplete(), - isCompact(), getEventEol(), headerPattern, footerPattern, getCharset(), isIncludeStacktrace()); + isCompact(), getEventEol(), headerPattern, footerPattern, getCharset(), isIncludeStacktrace(), isStacktraceAsString()); } } protected YamlLayout(final Configuration config, final boolean locationInfo, final boolean properties, final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern, - final String footerPattern, final Charset charset, final boolean includeStacktrace) { - super(config, new JacksonFactory.YAML(includeStacktrace).newWriter(locationInfo, properties, compact), charset, compact, + final String footerPattern, final Charset charset, final boolean includeStacktrace, final boolean stacktraceAsString) { + super(config, new JacksonFactory.YAML(includeStacktrace, stacktraceAsString).newWriter(locationInfo, properties, compact), charset, compact, complete, eventEol, PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(), PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build()); @@ -146,6 +146,8 @@ public String getContentType() { * The character set to use, if {@code null}, uses "UTF-8". * @param includeStacktrace * If "true", includes the stacktrace of any Throwable in the generated YAML, defaults to "true". + * @param stacktraceAsString + * If "true", the stacktrace will be rendered as string, and not nested object, defaults to "false". * @return A YAML Layout. * * @deprecated Use {@link #newBuilder()} instead @@ -159,11 +161,12 @@ public static AbstractJacksonLayout createLayout( @PluginAttribute(value = "header", defaultString = DEFAULT_HEADER) final String headerPattern, @PluginAttribute(value = "footer", defaultString = DEFAULT_FOOTER) final String footerPattern, @PluginAttribute(value = "charset", defaultString = "UTF-8") final Charset charset, - @PluginAttribute(value = "includeStacktrace", defaultBoolean = true) final boolean includeStacktrace + @PluginAttribute(value = "includeStacktrace", defaultBoolean = true) final boolean includeStacktrace, + @PluginAttribute(value = "stacktraceAsString", defaultBoolean = false) final boolean stacktraceAsString // @formatter:on ) { return new YamlLayout(config, locationInfo, properties, false, false, true, headerPattern, footerPattern, - charset, includeStacktrace); + charset, includeStacktrace, stacktraceAsString); } @PluginBuilderFactory @@ -178,6 +181,6 @@ public static > B newBuilder() { */ public static AbstractJacksonLayout createDefaultLayout() { return new YamlLayout(new DefaultConfiguration(), false, false, false, false, false, DEFAULT_HEADER, - DEFAULT_FOOTER, StandardCharsets.UTF_8, true); + DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false); } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/MarkerMixInXmlTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/MarkerMixInXmlTest.java index 3b808115b77..963d3f6d068 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/MarkerMixInXmlTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/MarkerMixInXmlTest.java @@ -28,7 +28,7 @@ public class MarkerMixInXmlTest extends MarkerMixInTest { @Override protected ObjectMapper newObjectMapper() { - return new Log4jXmlObjectMapper(true); + return new Log4jXmlObjectMapper(true, false); } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInJsonTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInJsonTest.java index 8b6e23d48ef..87eab6bb714 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInJsonTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/LevelMixInJsonTest.java @@ -26,7 +26,7 @@ public class LevelMixInJsonTest extends LevelMixInTest { @Override protected ObjectMapper newObjectMapper() { - return new Log4jJsonObjectMapper(false, true); + return new Log4jJsonObjectMapper(false, true, false); } } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixInTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixInTest.java index 38d943b3a8e..aec001cf207 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixInTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/jackson/StackTraceElementMixInTest.java @@ -81,7 +81,7 @@ public void testFromJsonWithSimpleModule() throws Exception { public void testFromJsonWithLog4jModule() throws Exception { final ObjectMapper mapper = new ObjectMapper(); final boolean encodeThreadContextAsList = false; - final SimpleModule module = new Log4jJsonModule(encodeThreadContextAsList, true); + final SimpleModule module = new Log4jJsonModule(encodeThreadContextAsList, true, false); module.addDeserializer(StackTraceElement.class, new Log4jStackTraceElementDeserializer()); mapper.registerModule(module); final StackTraceElement expected = new StackTraceElement("package.SomeClass", "someMethod", "SomeClass.java", 123); diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java index b458054f71e..e1da716a9b4 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/JsonLayoutTest.java @@ -142,7 +142,7 @@ private void testAllFeatures(final boolean locationInfo, final boolean compact, assertEquals(str, !compact || eventEol, str.contains("\n")); assertEquals(str, locationInfo, str.contains("source")); assertEquals(str, includeContext, str.contains("contextMap")); - final Log4jLogEvent actual = new Log4jJsonObjectMapper(contextMapAslist, includeStacktrace).readValue(str, Log4jLogEvent.class); + final Log4jLogEvent actual = new Log4jJsonObjectMapper(contextMapAslist, includeStacktrace, false).readValue(str, Log4jLogEvent.class); LogEventFixtures.assertEqualLogEvents(expected, actual, locationInfo, includeContext, includeStacktrace); if (includeContext) { this.checkMapEntry("MDC.A", "A_Value", compact, str, contextMapAslist); @@ -340,7 +340,7 @@ public void testLayoutLoggerName() throws Exception { // @formatter:on final String str = layout.toSerializable(expected); assertTrue(str, str.contains("\"loggerName\":\"a.B\"")); - final Log4jLogEvent actual = new Log4jJsonObjectMapper(propertiesAsList, true).readValue(str, Log4jLogEvent.class); + final Log4jLogEvent actual = new Log4jJsonObjectMapper(propertiesAsList, true, false).readValue(str, Log4jLogEvent.class); assertEquals(expected.getLoggerName(), actual.getLoggerName()); assertEquals(expected, actual); } @@ -370,6 +370,30 @@ public void testExcludeStacktrace() throws Exception { this.testAllFeatures(false, false, false, false, false, false); } + @Test + public void testStacktraceAsString() throws Exception { + final String str = prepareJSONForStacktraceTests(true); + assertTrue(str, str.contains("\"extendedStackTrace\":\"")); + } + + @Test + public void testStacktraceAsMap() throws Exception { + final String str = prepareJSONForStacktraceTests(false); + assertTrue(str, str.contains("\"extendedStackTrace\":[")); + } + + private String prepareJSONForStacktraceTests(final boolean stacktraceAsString) { + final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); + // @formatter:off + final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setCompact(true) + .setIncludeStacktrace(true) + .setStacktraceAsString(stacktraceAsString) + .build(); + // @formatter:off + return layout.toSerializable(expected); + } + private String toPropertySeparator(final boolean compact) { return compact ? ":" : " : "; } diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java index 0e6595c430b..bf367e5f30f 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/XmlLayoutTest.java @@ -128,7 +128,7 @@ private void checkElementNameAbsent(final String name, final boolean compact, fi private void testAllFeatures(final boolean includeSource, final boolean compact, final boolean includeContext, final boolean includeStacktrace) throws IOException, JsonParseException, JsonMappingException { final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); - final XmlLayout layout = XmlLayout.createLayout(includeSource, includeContext, false, compact, StandardCharsets.UTF_8, includeStacktrace); + final XmlLayout layout = XmlLayout.createLayout(includeSource, includeContext, false, compact, StandardCharsets.UTF_8, includeStacktrace, false); final String str = layout.toSerializable(expected); // System.out.println(str); assertEquals(str, !compact, str.contains("\n")); @@ -222,7 +222,7 @@ public void testLayout() throws Exception { this.rootLogger.removeAppender(appender); } // set up appender - final XmlLayout layout = XmlLayout.createLayout(true, true, true, false, null, true); + final XmlLayout layout = XmlLayout.createLayout(true, true, true, false, null, true, false); final ListAppender appender = new ListAppender("List", null, layout, true, false); appender.start(); @@ -271,7 +271,7 @@ public void testLayout() throws Exception { @Test public void testLayoutLoggerName() { - final XmlLayout layout = XmlLayout.createLayout(false, true, true, false, null, true); + final XmlLayout layout = XmlLayout.createLayout(false, true, true, false, null, true, false); final Log4jLogEvent event = Log4jLogEvent.newBuilder() // .setLoggerName("a.B") // .setLoggerFqcn("f.q.c.n") // diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/YamlLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/YamlLayoutTest.java index 97493a4728b..2e8c1d1169e 100644 --- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/YamlLayoutTest.java +++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/YamlLayoutTest.java @@ -116,14 +116,14 @@ private void testAllFeatures(final boolean includeSource, final boolean compact, final boolean includeContext, final boolean contextMapAslist, final boolean includeStacktrace) throws Exception { final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); final AbstractJacksonLayout layout = YamlLayout.createLayout(null, includeSource, includeContext, null, null, - StandardCharsets.UTF_8, includeStacktrace); + StandardCharsets.UTF_8, includeStacktrace, false); 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).readValue(str, Log4jLogEvent.class); + 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); @@ -195,7 +195,7 @@ public void testEscapeLayout() throws Exception { } final Configuration configuration = rootLogger.getContext().getConfiguration(); // set up appender - final AbstractJacksonLayout layout = YamlLayout.createLayout(configuration, true, true, null, null, null, true); + final AbstractJacksonLayout layout = YamlLayout.createLayout(configuration, true, true, null, null, null, true, false); final ListAppender appender = new ListAppender("List", null, layout, true, false); appender.start(); @@ -231,7 +231,7 @@ public void testLayout() throws Exception { final Configuration configuration = rootLogger.getContext().getConfiguration(); // set up appender // Use [[ and ]] to test header and footer (instead of [ and ]) - final AbstractJacksonLayout layout = YamlLayout.createLayout(configuration, true, true, "[[", "]]", null, true); + final AbstractJacksonLayout layout = YamlLayout.createLayout(configuration, true, true, "[[", "]]", null, true, false); final ListAppender appender = new ListAppender("List", null, layout, true, false); appender.start(); @@ -270,7 +270,7 @@ public void testLayout() throws Exception { @Test public void testLayoutLoggerName() throws Exception { final AbstractJacksonLayout layout = YamlLayout.createLayout(null, false, false, null, null, - StandardCharsets.UTF_8, true); + StandardCharsets.UTF_8, true, false); final Log4jLogEvent expected = Log4jLogEvent.newBuilder() // .setLoggerName("a.B") // .setLoggerFqcn("f.q.c.n") // @@ -280,7 +280,7 @@ public void testLayoutLoggerName() throws Exception { .setTimeMillis(1).build(); final String str = layout.toSerializable(expected); assertTrue(str, str.contains("loggerName: \"a.B\"")); - final Log4jLogEvent actual = new Log4jYamlObjectMapper(false, true).readValue(str, Log4jLogEvent.class); + final Log4jLogEvent actual = new Log4jYamlObjectMapper(false, true, false).readValue(str, Log4jLogEvent.class); assertEquals(expected.getLoggerName(), actual.getLoggerName()); assertEquals(expected, actual); } diff --git a/src/site/xdoc/manual/layouts.xml.vm b/src/site/xdoc/manual/layouts.xml.vm index 72a3e5fff2b..8f99e99df72 100644 --- a/src/site/xdoc/manual/layouts.xml.vm +++ b/src/site/xdoc/manual/layouts.xml.vm @@ -449,6 +449,11 @@ logger.debug("one={}, two={}, three={}", 1, 2, 3); boolean If true, include full stacktrace of any logged #javadoc('java/lang', 'Throwable') (optional, default to true). + + stacktraceAsString + boolean + Whether to format the stackstrace as a string, and not a nested object (optional, defaults to false). + JsonLayout Parameters

@@ -2126,6 +2131,11 @@ at org.apache.logging.log4j.core.pattern.ExtendedThrowableTest.testException(Ext boolean If true, include full stacktrace of any logged #javadoc('java/lang', 'Throwable') (optional, default to true). + + stacktraceAsString + boolean + Whether to format the stackstrace as a string, and not a nested object (optional, defaults to false). + XmlLayout Parameters

@@ -2210,6 +2220,11 @@ source: boolean If true, include full stacktrace of any logged #javadoc('java/lang', 'Throwable') (optional, default to true). + + stacktraceAsString + boolean + Whether to format the stackstrace as a string, and not a nested object (optional, defaults to false). + YamlLayout Parameters