http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/layout/JsonLayout.java ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/layout/JsonLayout.java b/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/layout/JsonLayout.java new file mode 100644 index 0000000..224cfb4 --- /dev/null +++ b/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/layout/JsonLayout.java @@ -0,0 +1,327 @@ +/* + * 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.json.layout; + +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.apache.logging.log4j.core.util.KeyValuePair; +import org.apache.logging.log4j.jackson.AbstractJacksonLayout; +import org.apache.logging.log4j.jackson.XmlConstants; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.annotation.JsonUnwrapped; + +/** + * Appends a series of JSON events as strings serialized as bytes. + * + * <h3>Complete well-formed JSON vs. fragment JSON</h3> + * <p> + * If you configure {@code complete="true"}, the appender outputs a well-formed JSON document. By default, with + * {@code complete="false"}, you should include the output as an <em>external file</em> in a separate file to form a + * well-formed JSON document. + * </p> + * <p> + * If {@code complete="false"}, the appender does not write the JSON open array character "[" at the start + * of the document, "]" and the end, nor comma "," between records. + * </p> + * <h3>Encoding</h3> + * <p> + * Appenders using this layout should have their {@code charset} set to {@code UTF-8} or {@code UTF-16}, otherwise + * events containing non ASCII characters could result in corrupted log files. + * </p> + * <h3>Pretty vs. compact JSON</h3> + * <p> + * By default, the JSON layout is not compact (a.k.a. "pretty") with {@code compact="false"}, which means the + * appender uses end-of-line characters and indents lines to format the text. If {@code compact="true"}, then no + * end-of-line or indentation is used. Message content may contain, of course, escaped end-of-lines. + * </p> + * <h3>Additional Fields</h3> + * <p> + * This property allows addition of custom fields into generated JSON. + * {@code <JsonLayout><KeyValuePair key="foo" value="bar"/></JsonLayout>} inserts {@code "foo":"bar"} directly + * into JSON output. Supports Lookup expressions. + * </p> + */ +@Plugin(name = "JsonLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) +public final class JsonLayout extends AbstractJacksonLayout { + + public static class Builder<B extends Builder<B>> extends AbstractJacksonLayout.Builder<B> + implements org.apache.logging.log4j.core.util.Builder<JsonLayout> { + + @PluginBuilderAttribute + private boolean propertiesAsList; + + @PluginBuilderAttribute + private boolean objectMessageAsJsonObject; + + @PluginElement("AdditionalField") + private KeyValuePair[] additionalFields; + + public Builder() { + super(); + setCharset(StandardCharsets.UTF_8); + } + + @Override + public JsonLayout build() { + final boolean encodeThreadContextAsList = isProperties() && propertiesAsList; + final String headerPattern = toStringOrNull(getHeader()); + final String footerPattern = toStringOrNull(getFooter()); + return new JsonLayout(getConfiguration(), isLocationInfo(), isProperties(), encodeThreadContextAsList, + isComplete(), isCompact(), getEventEol(), headerPattern, footerPattern, getCharset(), + isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter(), + getAdditionalFields(), getObjectMessageAsJsonObject()); + } + + @Override + public KeyValuePair[] getAdditionalFields() { + return additionalFields; + } + + public boolean getObjectMessageAsJsonObject() { + return objectMessageAsJsonObject; + } + + public boolean isPropertiesAsList() { + return propertiesAsList; + } + + @Override + public B setAdditionalFields(final KeyValuePair[] additionalFields) { + this.additionalFields = additionalFields; + return asBuilder(); + } + + public B setObjectMessageAsJsonObject(final boolean objectMessageAsJsonObject) { + this.objectMessageAsJsonObject = objectMessageAsJsonObject; + return asBuilder(); + } + + public B setPropertiesAsList(final boolean propertiesAsList) { + this.propertiesAsList = propertiesAsList; + return asBuilder(); + } + } + + @JsonRootName(XmlConstants.ELT_EVENT) + public static class JsonLogEventWithAdditionalFields extends LogEventWithAdditionalFields { + + public JsonLogEventWithAdditionalFields(final Object logEvent, final Map<String, String> additionalFields) { + super(logEvent, additionalFields); + } + + @Override + @JsonAnyGetter + public Map<String, String> getAdditionalFields() { + return super.getAdditionalFields(); + } + + @Override + @JsonUnwrapped + public Object getLogEvent() { + return super.getLogEvent(); + } + } + + private static final String DEFAULT_FOOTER = "]"; + + private static final String DEFAULT_HEADER = "["; + + + static final String CONTENT_TYPE = "application/json"; + + /** + * Creates a JSON Layout using the default settings. Useful for testing. + * + * @return A JSON Layout. + */ + public static JsonLayout createDefaultLayout() { + return new JsonLayout(new DefaultConfiguration(), false, false, false, false, false, false, + DEFAULT_HEADER, DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false, false, null, false); + } + + /** + * Creates a JSON Layout. + * @param config + * The plugin configuration. + * @param locationInfo + * If "true", includes the location information in the generated JSON. + * @param properties + * If "true", includes the thread context map in the generated JSON. + * @param propertiesAsList + * If true, the thread context map is included as a list of map entry objects, where each entry has + * a "key" attribute (whose value is the key) and a "value" attribute (whose value is the value). + * Defaults to false, in which case the thread context map is included as a simple map of key-value + * pairs. + * @param complete + * If "true", includes the JSON header and footer, and comma between records. + * @param compact + * If "true", does not use end-of-lines and indentation, defaults to "false". + * @param eventEol + * If "true", forces an EOL after each log event (even if compact is "true"), defaults to "false". This + * allows one even per line, even in compact mode. + * @param headerPattern + * The header pattern, defaults to {@code "["} if null. + * @param footerPattern + * The header pattern, defaults to {@code "]"} if null. + * @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 JSON, defaults to "true". + * @return A JSON Layout. + * + * @deprecated Use {@link #newBuilder()} instead + */ + @Deprecated + public static JsonLayout createLayout( + final Configuration config, + final boolean locationInfo, + final boolean properties, + final boolean propertiesAsList, + final boolean complete, + final boolean compact, + final boolean eventEol, + final String headerPattern, + final String footerPattern, + final Charset charset, + final boolean includeStacktrace) { + final boolean encodeThreadContextAsList = properties && propertiesAsList; + return new JsonLayout(config, locationInfo, properties, encodeThreadContextAsList, complete, compact, eventEol, + headerPattern, footerPattern, charset, includeStacktrace, false, false, null, false); + } + + @PluginBuilderFactory + public static <B extends Builder<B>> B newBuilder() { + return new Builder<B>().asBuilder(); + } + + /** + * @deprecated Use {@link #newBuilder()} instead + */ + @Deprecated + 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 JsonJacksonFactory(encodeThreadContextAsList, includeStacktrace, false, false).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(), + false, null); + } + + private 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, final boolean stacktraceAsString, + final boolean includeNullDelimiter, + final KeyValuePair[] additionalFields, final boolean objectMessageAsJsonObject) { + super(config, new JsonJacksonFactory(encodeThreadContextAsList, includeStacktrace, stacktraceAsString, objectMessageAsJsonObject).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(), + includeNullDelimiter, + additionalFields); + } + + @Override + protected LogEventWithAdditionalFields createLogEventWithAdditionalFields(final LogEvent event, + final Map<String, String> additionalFieldsMap) { + return new JsonLogEventWithAdditionalFields(event, additionalFieldsMap); + } + + @Override + public Map<String, String> getContentFormat() { + final Map<String, String> result = new HashMap<>(); + result.put("version", "2.0"); + return result; + } + + /** + * @return The content type. + */ + @Override + public String getContentType() { + return CONTENT_TYPE + "; charset=" + this.getCharset(); + } + + /** + * Returns appropriate JSON footer. + * + * @return a byte array containing the footer, closing the JSON array. + */ + @Override + public byte[] getFooter() { + if (!this.complete) { + return null; + } + final StringBuilder buf = new StringBuilder(); + buf.append(this.eol); + final String str = serializeToString(getFooterSerializer()); + if (str != null) { + buf.append(str); + } + buf.append(this.eol); + return getBytes(buf.toString()); + } + + /** + * Returns appropriate JSON header. + * + * @return a byte array containing the header, opening the JSON array. + */ + @Override + public byte[] getHeader() { + if (!this.complete) { + return null; + } + final StringBuilder buf = new StringBuilder(); + final String str = serializeToString(getHeaderSerializer()); + if (str != null) { + buf.append(str); + } + buf.append(this.eol); + return getBytes(buf.toString()); + } + + @Override + public void toSerializable(final LogEvent event, final Writer writer) throws IOException { + if (complete && eventCount > 0) { + writer.append(", "); + } + super.toSerializable(event, writer); + } +}
http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/parser/JsonLogEventParser.java ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/parser/JsonLogEventParser.java b/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/parser/JsonLogEventParser.java new file mode 100644 index 0000000..28279d1 --- /dev/null +++ b/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/parser/JsonLogEventParser.java @@ -0,0 +1,32 @@ +/* + * 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.json.parser; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.parser.AbstractJacksonLogEventParser; +import org.apache.logging.log4j.jackson.json.Log4jJsonObjectMapper; + +/** + * Parses the output from XmlLayout layout into instances of {@link LogEvent}. + */ +public class JsonLogEventParser extends AbstractJacksonLogEventParser { + + public JsonLogEventParser() { + super(new Log4jJsonObjectMapper()); + } + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-json/src/site/manual/index.md ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-json/src/site/manual/index.md b/log4j-layout-jackson-json/src/site/manual/index.md new file mode 100644 index 0000000..6cadb78 --- /dev/null +++ b/log4j-layout-jackson-json/src/site/manual/index.md @@ -0,0 +1,33 @@ +<!-- vim: set syn=markdown : --> +<!-- + 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. +--> + +# Apache Log4j Layout for Jackson JSON module + +As of Log4j 3.0.0, the layout based on Jackson JSON has moved from the existing module logj-core to the new modules log4j-layout-jackson-json. + +## Requirements + +This module was introduced in Log4j 2.11.0 and requires Jackson. + +Some features may require optional +[dependencies](../runtime-dependencies.html). These dependencies are specified in the +documentation for those features. + +Some Log4j features require external dependencies. +See the [Dependency Tree](dependencies.html#Dependency_Tree) +for the exact list of JAR files needed for these features. http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-json/src/site/site.xml ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-json/src/site/site.xml b/log4j-layout-jackson-json/src/site/site.xml new file mode 100644 index 0000000..6d4cddc --- /dev/null +++ b/log4j-layout-jackson-json/src/site/site.xml @@ -0,0 +1,52 @@ +<!-- + 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. + +--> +<project name="Log4j Core" + xmlns="http://maven.apache.org/DECORATION/1.4.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd"> + <body> + <links> + <item name="Apache" href="http://www.apache.org/" /> + <item name="Logging Services" href="http://logging.apache.org/"/> + <item name="Log4j" href="../index.html"/> + </links> + + <!-- Component-specific reports --> + <menu ref="reports"/> + + <!-- Overall Project Info --> + <menu name="Log4j Project Information" img="icon-info-sign"> + <item name="Dependencies" href="../dependencies.html" /> + <item name="Dependency Convergence" href="../dependency-convergence.html" /> + <item name="Dependency Management" href="../dependency-management.html" /> + <item name="Project Team" href="../team-list.html" /> + <item name="Mailing Lists" href="../mail-lists.html" /> + <item name="Issue Tracking" href="../issue-tracking.html" /> + <item name="Project License" href="../license.html" /> + <item name="Source Repository" href="../source-repository.html" /> + <item name="Project Summary" href="../project-summary.html" /> + </menu> + + <menu name="Log4j Project Reports" img="icon-cog"> + <item name="Changes Report" href="../changes-report.html" /> + <item name="JIRA Report" href="../jira-report.html" /> + <item name="Surefire Report" href="../surefire-report.html" /> + <item name="RAT Report" href="../rat-report.html" /> + </menu> + </body> +</project> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/JacksonIssue429MyNamesTest.java ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/JacksonIssue429MyNamesTest.java b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/JacksonIssue429MyNamesTest.java new file mode 100644 index 0000000..acc15c2 --- /dev/null +++ b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/JacksonIssue429MyNamesTest.java @@ -0,0 +1,130 @@ +/* + * 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.json; + +import java.io.IOException; + +import org.apache.logging.log4j.categories.Layouts; +import org.apache.logging.log4j.util.Strings; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; + +@Category(Layouts.Json.class) +public class JacksonIssue429MyNamesTest { + + @SuppressWarnings("serial") + static class MyStackTraceElementDeserializer extends StdScalarDeserializer<StackTraceElement> { + private static final long serialVersionUID = 1L; + + public final static MyStackTraceElementDeserializer instance = new MyStackTraceElementDeserializer(); + + public MyStackTraceElementDeserializer() { + super(StackTraceElement.class); + } + + @Override + public StackTraceElement deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException, + JsonProcessingException { + JsonToken t = jp.getCurrentToken(); + // Must get an Object + if (t == JsonToken.START_OBJECT) { + String className = Strings.EMPTY, methodName = Strings.EMPTY, fileName = Strings.EMPTY; + int lineNumber = -1; + + while ((t = jp.nextValue()) != JsonToken.END_OBJECT) { + final String propName = jp.getCurrentName(); + if ("class".equals(propName)) { + className = jp.getText(); + } else if ("file".equals(propName)) { + fileName = jp.getText(); + } else if ("line".equals(propName)) { + if (t.isNumeric()) { + lineNumber = jp.getIntValue(); + } else { + throw JsonMappingException.from(jp, "Non-numeric token (" + t + + ") for property 'lineNumber'"); + } + } else if ("method".equals(propName)) { + methodName = jp.getText(); + } else if ("nativeMethod".equals(propName)) { + // no setter, not passed via constructor: ignore + } else { + handleUnknownProperty(jp, ctxt, _valueClass, propName); + } + } + return new StackTraceElement(className, methodName, fileName, lineNumber); + } + throw ctxt.mappingException(_valueClass, t); + } + } + + static class StackTraceBean { + public final static int NUM = 13; + + @JsonProperty("Location") + @JsonDeserialize(using = MyStackTraceElementDeserializer.class) + private StackTraceElement location; + } + + private final static ObjectMapper SHARED_MAPPER = new ObjectMapper(); + + private final ObjectMapper MAPPER = objectMapper(); + + protected String aposToQuotes(final String json) { + return json.replace("'", "\""); + } + + protected ObjectMapper objectMapper() { + return SHARED_MAPPER; + } + + @Test + public void testStackTraceElementWithCustom() throws Exception { + // first, via bean that contains StackTraceElement + final StackTraceBean bean = MAPPER + .readValue( + aposToQuotes("{'Location':{'class':'package.SomeClass','method':'someMethod','file':'SomeClass.java','line':13}}"), + StackTraceBean.class); + Assert.assertNotNull(bean); + Assert.assertNotNull(bean.location); + Assert.assertEquals(StackTraceBean.NUM, bean.location.getLineNumber()); + + // and then directly, iff registered + final ObjectMapper mapper = new ObjectMapper(); + final SimpleModule module = new SimpleModule(); + module.addDeserializer(StackTraceElement.class, new MyStackTraceElementDeserializer()); + mapper.registerModule(module); + + final StackTraceElement elem = mapper.readValue( + aposToQuotes("{'class':'package.SomeClass','method':'someMethod','file':'SomeClass.java','line':13}"), + StackTraceElement.class); + Assert.assertNotNull(elem); + Assert.assertEquals(StackTraceBean.NUM, elem.getLineNumber()); + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/JacksonIssue429Test.java ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/JacksonIssue429Test.java b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/JacksonIssue429Test.java new file mode 100644 index 0000000..93d1d53 --- /dev/null +++ b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/JacksonIssue429Test.java @@ -0,0 +1,95 @@ +/* + * 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.json; + +import java.io.IOException; + +import org.apache.logging.log4j.categories.Layouts; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; + +@Category(Layouts.Json.class) +public class JacksonIssue429Test { + + @SuppressWarnings("serial") + static class Jackson429StackTraceElementDeserializer extends StdDeserializer<StackTraceElement> { + private static final long serialVersionUID = 1L; + + public Jackson429StackTraceElementDeserializer() { + super(StackTraceElement.class); + } + + @Override + public StackTraceElement deserialize(final JsonParser jp, final DeserializationContext ctxt) throws IOException, + JsonProcessingException { + jp.skipChildren(); + return new StackTraceElement("a", "b", "b", StackTraceBean.NUM); + } + + } + + static class StackTraceBean { + public final static int NUM = 13; + + @JsonProperty("Location") + @JsonDeserialize(using = Jackson429StackTraceElementDeserializer.class) + private StackTraceElement location; + } + + private final static ObjectMapper SHARED_MAPPER = new ObjectMapper(); + + private final ObjectMapper MAPPER = objectMapper(); + + protected String aposToQuotes(final String json) { + return json.replace("'", "\""); + } + + protected ObjectMapper objectMapper() { + return SHARED_MAPPER; + } + + @Test + public void testStackTraceElementWithCustom() throws Exception { + // first, via bean that contains StackTraceElement + final StackTraceBean bean = MAPPER.readValue(aposToQuotes("{'Location':'foobar'}"), StackTraceBean.class); + Assert.assertNotNull(bean); + Assert.assertNotNull(bean.location); + Assert.assertEquals(StackTraceBean.NUM, bean.location.getLineNumber()); + + // and then directly, iff registered + final ObjectMapper mapper = new ObjectMapper(); + final SimpleModule module = new SimpleModule(); + module.addDeserializer(StackTraceElement.class, new Jackson429StackTraceElementDeserializer()); + mapper.registerModule(module); + + final StackTraceElement elem = mapper.readValue( + aposToQuotes("{'class':'package.SomeClass','method':'someMethod','file':'SomeClass.java','line':123}"), + StackTraceElement.class); + Assert.assertNotNull(elem); + Assert.assertEquals(StackTraceBean.NUM, elem.getLineNumber()); + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/LevelMixInJsonTest.java ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/LevelMixInJsonTest.java b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/LevelMixInJsonTest.java new file mode 100644 index 0000000..7f75148 --- /dev/null +++ b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/LevelMixInJsonTest.java @@ -0,0 +1,34 @@ +/* + * 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.json; + +import org.apache.logging.log4j.categories.Layouts; +import org.apache.logging.log4j.jackson.LevelMixInTest; +import org.junit.experimental.categories.Category; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@Category(Layouts.Json.class) +public class LevelMixInJsonTest extends LevelMixInTest { + + @Override + protected ObjectMapper newObjectMapper() { + return new Log4jJsonObjectMapper(false, true, false, false); + } + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/MarkerMixInJsonTest.java ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/MarkerMixInJsonTest.java b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/MarkerMixInJsonTest.java new file mode 100644 index 0000000..db9588b --- /dev/null +++ b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/MarkerMixInJsonTest.java @@ -0,0 +1,34 @@ +/* +* 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.json; + +import org.apache.logging.log4j.categories.Layouts; +import org.apache.logging.log4j.jackson.AbstractMarkerMixInTest; +import org.junit.experimental.categories.Category; + +import com.fasterxml.jackson.databind.ObjectMapper; + +@Category(Layouts.Json.class) +public class MarkerMixInJsonTest extends AbstractMarkerMixInTest { + + @Override + protected ObjectMapper newObjectMapper() { + return new Log4jJsonObjectMapper(); + } + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/StackTraceElementJsonMixInTest.java ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/StackTraceElementJsonMixInTest.java b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/StackTraceElementJsonMixInTest.java new file mode 100644 index 0000000..fc0b459 --- /dev/null +++ b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/StackTraceElementJsonMixInTest.java @@ -0,0 +1,83 @@ +/* + * 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.json; + +import java.io.IOException; + +import org.apache.logging.log4j.categories.Layouts; +import org.apache.logging.log4j.jackson.Log4jStackTraceElementDeserializer; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; + +@Category(Layouts.Json.class) +public class StackTraceElementJsonMixInTest { + + protected String aposToQuotes(final String json) { + return json.replace("'", "\""); + } + + /** + * @param mapper + * @throws JsonProcessingException + * @throws IOException + * @throws JsonParseException + * @throws JsonMappingException + */ + private void roundtrip(final ObjectMapper mapper) throws JsonProcessingException, IOException, JsonParseException, JsonMappingException { + final StackTraceElement expected = new StackTraceElement("package.SomeClass", "someMethod", "SomeClass.java", 123); + final String s = mapper.writeValueAsString(expected); + final StackTraceElement actual = mapper.readValue(s, StackTraceElement.class); + Assert.assertEquals(expected, actual); + } + + @Test + public void testFromJsonWithLog4jModule() throws Exception { + final ObjectMapper mapper = new ObjectMapper(); + final boolean encodeThreadContextAsList = false; + final SimpleModule module = new Log4jJsonModule(encodeThreadContextAsList, true, false, false); + module.addDeserializer(StackTraceElement.class, new Log4jStackTraceElementDeserializer()); + mapper.registerModule(module); + final StackTraceElement expected = new StackTraceElement("package.SomeClass", "someMethod", "SomeClass.java", 123); + final String s = this.aposToQuotes("{'class':'package.SomeClass','method':'someMethod','file':'SomeClass.java','line':123}"); + final StackTraceElement actual = mapper.readValue(s, StackTraceElement.class); + Assert.assertEquals(expected, actual); + } + + @Test + public void testFromJsonWithSimpleModule() throws Exception { + final ObjectMapper mapper = new ObjectMapper(); + final SimpleModule module = new SimpleModule(); + module.addDeserializer(StackTraceElement.class, new Log4jStackTraceElementDeserializer()); + mapper.registerModule(module); + final StackTraceElement expected = new StackTraceElement("package.SomeClass", "someMethod", "SomeClass.java", 123); + final String s = this.aposToQuotes("{'class':'package.SomeClass','method':'someMethod','file':'SomeClass.java','line':123}"); + final StackTraceElement actual = mapper.readValue(s, StackTraceElement.class); + Assert.assertEquals(expected, actual); + } + + @Test + public void testLog4jJsonObjectMapper() throws Exception { + this.roundtrip(new Log4jJsonObjectMapper()); + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/ConcurrentLoggingWithJsonLayoutTest.java ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/ConcurrentLoggingWithJsonLayoutTest.java b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/ConcurrentLoggingWithJsonLayoutTest.java new file mode 100644 index 0000000..5d6f02d --- /dev/null +++ b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/ConcurrentLoggingWithJsonLayoutTest.java @@ -0,0 +1,117 @@ +/* + * 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.json.layout; + +import static org.hamcrest.CoreMatchers.endsWith; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; + +import java.io.File; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.junit.LoggerContextRule; +import org.junit.AfterClass; +import org.junit.ClassRule; +import org.junit.Test; + +/** + * Test for LOG4J2-1769. + * + * @since 2.8 + */ +public class ConcurrentLoggingWithJsonLayoutTest { + + private class LoggingThread extends Thread { + private final Set<Thread> threads; + private final Logger log; + + LoggingThread(final Set<Thread> threads, final Logger log) { + this.threads = threads; + this.log = log; + } + + @Override + public void run() { + log.info(threads.size()); + try { + for (int i = 0; i < 64; i++) { + log.info("First message."); + log.info("Second message."); + } + } finally { + threads.remove(this); + } + } + } + @ClassRule + public static LoggerContextRule context = new LoggerContextRule("log4j2-json-layout.xml"); + + private static final String PATH = "target/test-json-layout.log"; + + @AfterClass + public static void after() { + new File(PATH).delete(); + } + + @Test + public void testConcurrentLogging() throws Throwable { + final Logger log = context.getLogger(ConcurrentLoggingWithJsonLayoutTest.class); + final Set<Thread> threads = Collections.synchronizedSet(new HashSet<Thread>()); + final List<Throwable> thrown = Collections.synchronizedList(new ArrayList<Throwable>()); + + for (int x = 0; x < Runtime.getRuntime().availableProcessors() * 2; x++) { + final Thread t = new LoggingThread(threads, log); + threads.add(t); + + // Appender is configured with ignoreExceptions="false"; + // any exceptions are propagated to the caller, so we can catch them here. + t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(final Thread t, final Throwable e) { + thrown.add(e); + } + }); + t.start(); + } + + while (!threads.isEmpty()) { + log.info("not done going to sleep..."); + Thread.sleep(10); + } + + // if any error occurred, fail this test + if (!thrown.isEmpty()) { + throw thrown.get(0); + } + + // simple test to ensure content is not corrupted + if (new File(PATH).exists()) { + final List<String> lines = Files.readAllLines(new File(PATH).toPath(), Charset.defaultCharset()); + for (final String line : lines) { + assertThat(line, startsWith("{\"thread\":")); + assertThat(line, endsWith("\"threadPriority\":5}")); + } + } + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java new file mode 100644 index 0000000..f8afb51 --- /dev/null +++ b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java @@ -0,0 +1,489 @@ +/* + * 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.json.layout; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.nio.charset.Charset; +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.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.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.json.Log4jJsonObjectMapper; +import org.apache.logging.log4j.message.ObjectMessage; +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; + +/** + * Tests the JsonLayout class. + */ +@Category(Layouts.Json.class) +public class JsonLayoutTest { + private static class TestClass { + private int value; + + public int getValue() { + return value; + } + + public void setValue(final int value) { + this.value = value; + } + } + + static ConfigurationFactory cf = new BasicConfigurationFactory(); + + private static final String DQUOTE = "\""; + + @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(); + assertTrue("Incorrect line index " + lineIndex + ": " + Strings.dquote(trimedLine), trimedLine.equals(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, + final boolean contextMapAslist) { + this.toPropertySeparator(compact); + if (contextMapAslist) { + // {"key":"KEY", "value":"VALUE"} + final String expected = String.format("{\"key\":\"%s\",\"value\":\"%s\"}", key, value); + assertTrue("Cannot find contextMapAslist " + expected + " in " + str, str.contains(expected)); + } else { + // "KEY":"VALUE" + final String expected = String.format("\"%s\":\"%s\"", key, value); + assertTrue("Cannot find contextMap " + expected + " in " + str, str.contains(expected)); + } + } + + private void checkProperty(final String key, final String value, final boolean compact, final String str) { + final String propSep = this.toPropertySeparator(compact); + // {"key":"MDC.B","value":"B_Value"} + final String expected = String.format("\"%s\"%s\"%s\"", key, propSep, value); + assertTrue("Cannot find " + expected + " in " + str, str.contains(expected)); + } + + private void checkPropertyName(final String name, final boolean compact, final String str) { + final String propSep = this.toPropertySeparator(compact); + assertTrue(str, str.contains(DQUOTE + name + DQUOTE + propSep)); + } + + private void checkPropertyNameAbsent(final String name, final boolean compact, final String str) { + final String propSep = this.toPropertySeparator(compact); + assertFalse(str, str.contains(DQUOTE + name + DQUOTE + propSep)); + } + + private String prepareJsonForObjectMessageAsJsonObjectTests(final int value, final boolean objectMessageAsJsonObject) { + final TestClass testClass = new TestClass(); + testClass.setValue(value); + // @formatter:off + final Log4jLogEvent expected = Log4jLogEvent.newBuilder() + .setLoggerName("a.B") + .setLoggerFqcn("f.q.c.n") + .setLevel(Level.DEBUG) + .setMessage(new ObjectMessage(testClass)) + .setThreadName("threadName") + .setTimeMillis(1).build(); + // @formatter:off + final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setCompact(true) + .setObjectMessageAsJsonObject(objectMessageAsJsonObject) + .build(); + // @formatter:off + return layout.toSerializable(expected); + } + + 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); + } + + @Test + public void testAdditionalFields() throws Exception { + final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setLocationInfo(false) + .setProperties(false) + .setComplete(false) + .setCompact(true) + .setEventEol(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()); + assertTrue(str, str.contains("\"KEY1\":\"VALUE1\"")); + assertTrue(str, str.contains("\"KEY2\":\"" + new JavaLookup().getRuntime() + "\"")); + } + + private void testAllFeatures(final boolean locationInfo, final boolean compact, final boolean eventEol, + final boolean includeContext, final boolean contextMapAslist, final boolean includeStacktrace) + throws Exception { + final Log4jLogEvent expected = LogEventFixtures.createLogEvent(); + // @formatter:off + final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setLocationInfo(locationInfo) + .setProperties(includeContext) + .setPropertiesAsList(contextMapAslist) + .setComplete(false) + .setCompact(compact) + .setEventEol(eventEol) + .setCharset(StandardCharsets.UTF_8) + .setIncludeStacktrace(includeStacktrace) + .build(); + // @formatter:off + final String str = layout.toSerializable(expected); + this.toPropertySeparator(compact); + // Just check for \n since \r might or might not be there. + 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, false, false).readValue(str, Log4jLogEvent.class); + LogEventFixtures.assertEqualLogEvents(expected, actual, locationInfo, includeContext, includeStacktrace); + if (includeContext) { + this.checkMapEntry("MDC.A", "A_Value", compact, str, contextMapAslist); + this.checkMapEntry("MDC.B", "B_Value", compact, str, contextMapAslist); + } + // + assertNull(actual.getThrown()); + // make sure the names we want are used + this.checkPropertyName("instant", compact, str); + this.checkPropertyName("thread", compact, str); // and not threadName + this.checkPropertyName("level", compact, str); + this.checkPropertyName("loggerName", compact, str); + this.checkPropertyName("marker", compact, str); + this.checkPropertyName("name", compact, str); + this.checkPropertyName("parents", compact, str); + this.checkPropertyName("message", compact, str); + this.checkPropertyName("thrown", compact, str); + this.checkPropertyName("cause", compact, str); + this.checkPropertyName("commonElementCount", compact, str); + this.checkPropertyName("localizedMessage", compact, str); + if (includeStacktrace) { + this.checkPropertyName("extendedStackTrace", compact, str); + this.checkPropertyName("class", compact, str); + this.checkPropertyName("method", compact, str); + this.checkPropertyName("file", compact, str); + this.checkPropertyName("line", compact, str); + this.checkPropertyName("exact", compact, str); + this.checkPropertyName("location", compact, str); + this.checkPropertyName("version", compact, str); + } else { + this.checkPropertyNameAbsent("extendedStackTrace", compact, str); + } + this.checkPropertyName("suppressed", compact, str); + this.checkPropertyName("loggerFqcn", compact, str); + this.checkPropertyName("endOfBatch", compact, str); + if (includeContext) { + this.checkPropertyName("contextMap", compact, str); + } else { + this.checkPropertyNameAbsent("contextMap", compact, str); + } + this.checkPropertyName("contextStack", compact, str); + if (locationInfo) { + this.checkPropertyName("source", compact, str); + } else { + this.checkPropertyNameAbsent("source", compact, str); + } + // check some attrs + this.checkProperty("loggerFqcn", "f.q.c.n", compact, str); + this.checkProperty("loggerName", "a.B", compact, str); + } + + @Test + public void testContentType() { + final AbstractJacksonLayout layout = JsonLayout.createDefaultLayout(); + assertEquals("application/json; charset=UTF-8", layout.getContentType()); + } + + @Test + public void testDefaultCharset() { + final AbstractJacksonLayout layout = JsonLayout.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 boolean propertiesAsList = false; + // @formatter:off + final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setConfiguration(configuration) + .setLocationInfo(true) + .setProperties(true) + .setPropertiesAsList(propertiesAsList) + .setComplete(true) + .setCompact(false) + .setEventEol(false) + .setIncludeStacktrace(true) + .build(); + // @formatter:on + 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.checkAt("{", 1, 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 = JsonLayout.newBuilder() + .setCompact(true) + .setIncludeNullDelimiter(false) + .build(); + final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); + assertFalse(str.endsWith("\0")); + } + + @Test + public void testIncludeNullDelimiterTrue() throws Exception { + final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setCompact(true) + .setIncludeNullDelimiter(true) + .build(); + final String str = layout.toSerializable(LogEventFixtures.createLogEvent()); + assertTrue(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 boolean propertiesAsList = false; + // @formatter:off + final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setConfiguration(configuration) + .setLocationInfo(true) + .setProperties(true) + .setPropertiesAsList(propertiesAsList) + .setComplete(true) + .setCompact(false) + .setEventEol(false) + .setHeader("[[".getBytes(Charset.defaultCharset())) + .setFooter("]]".getBytes(Charset.defaultCharset())) + .setIncludeStacktrace(true) + .build(); + // @formatter:on + 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.checkAt("{", 1, 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 boolean propertiesAsList = false; + // @formatter:off + final AbstractJacksonLayout layout = JsonLayout.newBuilder() + .setLocationInfo(false) + .setProperties(false) + .setPropertiesAsList(propertiesAsList) + .setComplete(false) + .setCompact(true) + .setEventEol(false) + .setCharset(StandardCharsets.UTF_8) + .setIncludeStacktrace(true) + .build(); + // @formatter:on + // @formatter:off + 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(); + // @formatter:on + final String str = layout.toSerializable(expected); + assertTrue(str, str.contains("\"loggerName\":\"a.B\"")); + final Log4jLogEvent actual = new Log4jJsonObjectMapper(propertiesAsList, true, false, 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 testLocationOnCompactOnEventEolOnMdcOn() throws Exception { + this.testAllFeatures(true, true, true, true, false, true); + } + + @Test + public void testLocationOnCompactOnEventEolOnMdcOnMdcAsList() throws Exception { + this.testAllFeatures(true, true, true, true, true, true); + } + + @Test + public void testLocationOnCompactOnMdcOn() throws Exception { + this.testAllFeatures(true, true, false, true, false, true); + } + + @Test + public void testObjectMessageAsJsonObject() { + final String str = prepareJsonForObjectMessageAsJsonObjectTests(1234, true); + assertTrue(str, str.contains("\"message\":{\"value\":1234}")); + } + + @Test + public void testObjectMessageAsJsonString() { + final String str = prepareJsonForObjectMessageAsJsonObjectTests(1234, false); + assertTrue(str, str.contains("\"message\":\"" + this.getClass().getCanonicalName() + "$TestClass@")); + } + + @Test + public void testStacktraceAsNonString() throws Exception { + final String str = prepareJsonForStacktraceTests(false); + assertTrue(str, str.contains("\"extendedStackTrace\":[")); + } + + @Test + public void testStacktraceAsString() throws Exception { + final String str = prepareJsonForStacktraceTests(true); + assertTrue(str, str.contains("\"extendedStackTrace\":\"java.lang.NullPointerException")); + } + + private String toPropertySeparator(final boolean compact) { + return compact ? ":" : " : "; + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/ThrowableProxyJsonTest.java ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/ThrowableProxyJsonTest.java b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/ThrowableProxyJsonTest.java new file mode 100644 index 0000000..c5b0355 --- /dev/null +++ b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/ThrowableProxyJsonTest.java @@ -0,0 +1,33 @@ +/* + * 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.json.layout; + +import java.io.IOException; + +import org.apache.logging.log4j.jackson.ThrowableProxyJacksonTest; +import org.apache.logging.log4j.jackson.json.Log4jJsonObjectMapper; +import org.junit.Test; + +public class ThrowableProxyJsonTest extends ThrowableProxyJacksonTest { + + @Test + public void testIoContainerAsJson() throws IOException { + testIoContainer(new Log4jJsonObjectMapper()); + } + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/parser/JsonLogEventParserTest.java ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/parser/JsonLogEventParserTest.java b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/parser/JsonLogEventParserTest.java new file mode 100644 index 0000000..a6b3cc7 --- /dev/null +++ b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/parser/JsonLogEventParserTest.java @@ -0,0 +1,132 @@ +/* + * 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.json.parser; + +import java.nio.charset.StandardCharsets; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.parser.AbstractLogEventParserTest; +import org.apache.logging.log4j.core.parser.ParseException; +import org.junit.Before; +import org.junit.Test; + +public class JsonLogEventParserTest extends AbstractLogEventParserTest { + + private static final String JSON = "{\n" + + " \"timeMillis\" : 1493121664118,\n" + + " \"instant\":{\"epochSecond\":1493121664,\"nanoOfSecond\":118000000},\n" + + " \"thread\" : \"main\",\n" + + " \"threadId\" : 1,\n" + + " \"threadPriority\" : 5,\n" + + " \"level\" : \"INFO\",\n" + + " \"loggerName\" : \"HelloWorld\",\n" + + " \"marker\" : {\n" + + " \"name\" : \"child\",\n" + + " \"parents\" : [ {\n" + + " \"name\" : \"parent\",\n" + + " \"parents\" : [ {\n" + + " \"name\" : \"grandparent\"\n" + + " } ]\n" + + " } ]\n" + + " },\n" + + " \"message\" : \"Hello, world!\",\n" + + " \"thrown\" : {\n" + + " \"commonElementCount\" : 0,\n" + + " \"message\" : \"error message\",\n" + + " \"name\" : \"java.lang.RuntimeException\",\n" + + " \"extendedStackTrace\" : [ {\n" + + " \"class\" : \"logtest.Main\",\n" + + " \"method\" : \"main\",\n" + + " \"file\" : \"Main.java\",\n" + + " \"line\" : 29,\n" + + " \"exact\" : true,\n" + + " \"location\" : \"classes/\",\n" + + " \"version\" : \"?\"\n" + + " } ]\n" + + " },\n" + + " \"contextStack\" : [ \"one\", \"two\" ],\n" + + " \"loggerFqcn\" : \"org.apache.logging.log4j.spi.AbstractLogger\",\n" + + " \"endOfBatch\" : false,\n" + + " \"contextMap\" : {\n" + + " \"bar\" : \"BAR\",\n" + + " \"foo\" : \"FOO\"\n" + + " },\n" + + " \"source\" : {\n" + + " \"class\" : \"logtest.Main\",\n" + + " \"method\" : \"main\",\n" + + " \"file\" : \"Main.java\",\n" + + " \"line\" : 29\n" + + " }\n" + + "}"; + + private JsonLogEventParser parser; + + @Before + public void setup() { + parser = new JsonLogEventParser(); + } + + @Test + public void testByteArray() throws ParseException { + final LogEvent logEvent = parser.parseFrom(JSON.getBytes(StandardCharsets.UTF_8)); + assertLogEvent(logEvent); + } + + @Test + public void testByteArrayOffsetLength() throws ParseException { + final byte[] bytes = ("abc" + JSON + "def").getBytes(StandardCharsets.UTF_8); + final LogEvent logEvent = parser.parseFrom(bytes, 3, bytes.length - 6); + assertLogEvent(logEvent); + } + + @Test + public void testEmptyObject() throws ParseException { + parser.parseFrom("{}"); + } + + @Test + public void testString() throws ParseException { + final LogEvent logEvent = parser.parseFrom(JSON); + assertLogEvent(logEvent); + } + + @Test(expected = ParseException.class) + public void testStringEmpty() throws ParseException { + parser.parseFrom(""); + } + + @Test + public void testStringIgnoreInvalidProperty() throws ParseException { + parser.parseFrom("{\"foo\":\"bar\"}"); + } + + @Test(expected = ParseException.class) + public void testStringInvalidJson() throws ParseException { + parser.parseFrom("foobar"); + } + + @Test(expected = ParseException.class) + public void testStringJsonArray() throws ParseException { + parser.parseFrom("[]"); + } + + @Test(expected = ParseException.class) + public void testStringWrongPropertyType() throws ParseException { + parser.parseFrom("{\"threadId\":\"foobar\"}"); + } + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-json/src/test/resources/log4j2-json-layout.xml ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-json/src/test/resources/log4j2-json-layout.xml b/log4j-layout-jackson-json/src/test/resources/log4j2-json-layout.xml new file mode 100644 index 0000000..c7af4e9 --- /dev/null +++ b/log4j-layout-jackson-json/src/test/resources/log4j2-json-layout.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. + +--> +<Configuration status="INFO" packages=""> + <Appenders> + <File name="stdout" ignoreExceptions="false" fileName="target/test-xml-layout.log" append="false"> + <JsonLayout compact="true" eventEol="true" /> + </File> + </Appenders> + + <Loggers> + <Root level="INFO"> + <AppenderRef ref="stdout" /> + </Root> + </Loggers> +</Configuration> http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/Log4jXmlModule.java ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/Log4jXmlModule.java b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/Log4jXmlModule.java new file mode 100644 index 0000000..0980f94 --- /dev/null +++ b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/Log4jXmlModule.java @@ -0,0 +1,49 @@ +/* + * 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.xml; + +import org.apache.logging.log4j.core.jackson.SimpleModuleInitializer; + +import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule; + +/** + * <p> + * <em>Consider this class private.</em> + * </p> + */ +final class Log4jXmlModule extends JacksonXmlModule { + + private static final long serialVersionUID = 1L; + private final boolean includeStacktrace; + private final boolean stacktraceAsString; + + 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, false); + } + + @Override + public void setupModule(final SetupContext context) { + // Calling super is a MUST! + super.setupModule(context); + new XmlSetupContextInitializer().setupModule(context, includeStacktrace, stacktraceAsString); + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/Log4jXmlObjectMapper.java ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/Log4jXmlObjectMapper.java b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/Log4jXmlObjectMapper.java new file mode 100644 index 0000000..632b83f --- /dev/null +++ b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/Log4jXmlObjectMapper.java @@ -0,0 +1,48 @@ +/* + * 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.xml; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; + +/** + * A Jackson XML {@link ObjectMapper} initialized for Log4j. + * <p> + * <em>Consider this class private.</em> + * </p> + */ +public class Log4jXmlObjectMapper extends XmlMapper { + + private static final long serialVersionUID = 1L; + + /** + * Create a new instance using the {@link Log4jXmlModule}. + */ + public Log4jXmlObjectMapper() { + this(true, false); + } + + /** + * Create a new instance using the {@link Log4jXmlModule}. + */ + public Log4jXmlObjectMapper(final boolean includeStacktrace, final boolean stacktraceAsString) { + super(new Log4jXmlModule(includeStacktrace, stacktraceAsString)); + this.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + } + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlInstantMixIn.java ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlInstantMixIn.java b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlInstantMixIn.java new file mode 100644 index 0000000..0e18154 --- /dev/null +++ b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlInstantMixIn.java @@ -0,0 +1,56 @@ +/* + * 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.xml; + +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.jackson.InstantMixIn; +import org.apache.logging.log4j.core.time.Instant; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +/** + * Jackson mix-in for {@link Instant}. + * <p> + * <em>Consider this class private.</em> + * </p> + * + * @see Marker + */ +abstract class XmlInstantMixIn extends InstantMixIn { + + @JsonCreator + protected XmlInstantMixIn( + // @formatter:off + @JsonProperty(ATTR_EPOCH_SECOND) final long epochSecond, + @JsonProperty(ATTR_NANO_OF_SECOND) final int nanoOfSecond) + // @formatter:on + { + super(epochSecond, nanoOfSecond); + } + + @Override + @JacksonXmlProperty(localName = ATTR_EPOCH_SECOND, isAttribute = true) + protected abstract long getEpochSecond(); + + @Override + @JacksonXmlProperty(localName = ATTR_NANO_OF_SECOND, isAttribute = true) + protected abstract int getNanoOfSecond(); + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/0eb5212e/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlLogEventWithContextListMixIn.java ---------------------------------------------------------------------- diff --git a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlLogEventWithContextListMixIn.java b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlLogEventWithContextListMixIn.java new file mode 100644 index 0000000..771f955 --- /dev/null +++ b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlLogEventWithContextListMixIn.java @@ -0,0 +1,50 @@ +/* + * 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.xml; + +import org.apache.logging.log4j.core.jackson.AbstractXmlLogEventMixIn; +import org.apache.logging.log4j.core.jackson.ContextDataAsEntryListDeserializer; +import org.apache.logging.log4j.core.jackson.ContextDataAsEntryListSerializer; +import org.apache.logging.log4j.core.jackson.XmlConstants; +import org.apache.logging.log4j.util.ReadOnlyStringMap; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +/** + * <pre> + * AbstractLogEventMixIn +*ââ XmlLogEventMixIn +*âââââ XmlLogEventWithContextListMixIn +*âââââ XmlLogEventWithContextMapMixIn +*ââ JsonLogEventMixIn +*âââââ JsonLogEventWithContextListMixIn +*âââââ JsonLogEventWithContextMapMixIn + * </pre> + */ +public abstract class XmlLogEventWithContextListMixIn extends AbstractXmlLogEventMixIn { + + private static final long serialVersionUID = 1L; + + @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_CONTEXT_MAP) + @JsonSerialize(using = ContextDataAsEntryListSerializer.class) + @JsonDeserialize(using = ContextDataAsEntryListDeserializer.class) + @Override + public abstract ReadOnlyStringMap getContextData(); + +}
