blob: 6d723b0dfd2a1c2d4e20005f30d45cb3a83ce9c8 [file] [log] [blame]
/*
* 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.layout;
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.Node;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
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.dataformat.xml.annotation.JacksonXmlRootElement;
/**
* Appends a series of {@code event} elements as defined in the <a href="log4j.dtd">log4j.dtd</a>.
*
* <h3>Complete well-formed XML vs. fragment XML</h3>
* <p>
* If you configure {@code complete="true"}, the appender outputs a well-formed XML document where the default namespace
* is the log4j namespace {@value XmlConstants#XML_NAMESPACE}. By default, with {@code complete="false"}, you should
* include the output as an <em>external entity</em> in a separate file to form a well-formed XML document.
* </p>
* <p>
* If {@code complete="false"}, the appender does not write the XML processing instruction and the root element.
* </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 XML</h3>
* <p>
* By default, the XML layout is not compact (compact = not "pretty") with {@code compact="false"}, which means the
* appender uses end-of-line characters and indents lines to format the XML. If {@code compact="true"}, then no
* end-of-line or indentation is used. Message content may contain, of course, end-of-lines.
* </p>
* <h3>Additional Fields</h3>
* <p>
* This property allows addition of custom fields into generated JSON.
* {@code <XmlLayout><KeyValuePair key="foo" value="bar"/></XmlLayout>} inserts {@code <foo>bar</foo>} directly into XML
* output. Supports Lookup expressions.
* </p>
*/
@Plugin(name = "XmlLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
public final class XmlLayout extends AbstractJacksonLayout {
public static class Builder<B extends Builder<B>> extends AbstractJacksonLayout.Builder<B>
implements org.apache.logging.log4j.core.util.Builder<XmlLayout> {
public Builder() {
super();
setCharset(StandardCharsets.UTF_8);
}
@Override
public XmlLayout build() {
return new XmlLayout(getConfiguration(), isLocationInfo(), isProperties(), isComplete(), isCompact(),
getCharset(), isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter(),
getAdditionalFields());
}
}
@JacksonXmlRootElement(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_EVENT)
public static class XmlLogEventWithAdditionalFields extends LogEventWithAdditionalFields {
public XmlLogEventWithAdditionalFields(final Object logEvent, final Map<String, String> additionalFields) {
super(logEvent, additionalFields);
}
}
private static final String ROOT_TAG = "Events";
/**
* Creates an XML Layout using the default settings.
*
* @return an XML Layout.
*/
public static XmlLayout createDefaultLayout() {
return new XmlLayout(null, false, false, false, false, StandardCharsets.UTF_8, true, false, false, null);
}
/**
* Creates an XML Layout.
*
* @param locationInfo
* If "true", includes the location information in the generated XML.
* @param properties
* If "true", includes the thread context map in the generated XML.
* @param complete
* If "true", includes the XML header and footer, defaults to "false".
* @param compact
* If "true", does not use end-of-lines and indentation, defaults to "false".
* @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".
* @return An XML Layout.
*
* @deprecated Use {@link #newBuilder()} instead
*/
@Deprecated
public static XmlLayout createLayout(final boolean locationInfo, final boolean properties, final boolean complete,
final boolean compact, final Charset charset, final boolean includeStacktrace) {
return new XmlLayout(null, locationInfo, properties, complete, compact, charset, includeStacktrace, false,
false, null);
}
@PluginBuilderFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
/**
* @deprecated Use {@link #newBuilder()} instead
*/
@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, false, false, null);
}
private XmlLayout(final Configuration config, final boolean locationInfo, final boolean properties,
final boolean complete, final boolean compact, final Charset charset, final boolean includeStacktrace,
final boolean stacktraceAsString, final boolean includeNullDelimiter,
final KeyValuePair[] additionalFields) {
super(config,
new XmlJacksonFactory(includeStacktrace, stacktraceAsString).newWriter(locationInfo, properties,
compact),
charset, compact, complete, false, null, null, includeNullDelimiter, additionalFields);
}
@Override
protected LogEventWithAdditionalFields createLogEventWithAdditionalFields(final LogEvent event,
final Map<String, String> additionalFieldsMap) {
return new XmlLogEventWithAdditionalFields(event, additionalFieldsMap);
}
/**
* Gets this XmlLayout's content format. Specified by:
* <ul>
* <li>Key: "dtd" Value: "log4j-events.dtd"</li>
* <li>Key: "version" Value: "2.0"</li>
* </ul>
*
* @return Map of content format keys supporting XmlLayout
*/
@Override
public Map<String, String> getContentFormat() {
final Map<String, String> result = new HashMap<>();
// result.put("dtd", "log4j-events.dtd");
result.put("xsd", "log4j-events.xsd");
result.put("version", "2.0");
return result;
}
/**
* @return The content type.
*/
@Override
public String getContentType() {
return "text/xml; charset=" + this.getCharset();
}
/**
* Returns appropriate XML footer.
*
* @return a byte array containing the footer, closing the XML root element.
*/
@Override
public byte[] getFooter() {
if (!complete) {
return null;
}
return getBytes("</" + ROOT_TAG + '>' + this.eol);
}
/**
* Returns appropriate XML headers.
* <ol>
* <li>XML processing instruction</li>
* <li>XML root element</li>
* </ol>
*
* @return a byte array containing the header.
*/
@Override
public byte[] getHeader() {
if (!complete) {
return null;
}
final StringBuilder buf = new StringBuilder();
buf.append("<?xml version=\"1.0\" encoding=\"");
buf.append(this.getCharset().name());
buf.append("\"?>");
buf.append(this.eol);
// Make the log4j namespace the default namespace, no need to use more space with a namespace prefix.
buf.append('<');
buf.append(ROOT_TAG);
buf.append(" xmlns=\"" + XmlConstants.XML_NAMESPACE + "\">");
buf.append(this.eol);
return buf.toString().getBytes(this.getCharset());
}
}