001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.layout;
018
019import java.nio.charset.Charset;
020import java.nio.charset.StandardCharsets;
021import java.util.HashMap;
022import java.util.Map;
023
024import org.apache.logging.log4j.core.Layout;
025import org.apache.logging.log4j.core.config.Configuration;
026import org.apache.logging.log4j.core.config.Node;
027import org.apache.logging.log4j.core.config.plugins.Plugin;
028import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
029import org.apache.logging.log4j.core.jackson.XmlConstants;
030
031/**
032 * Appends a series of {@code event} elements as defined in the <a href="log4j.dtd">log4j.dtd</a>.
033 *
034 * <h3>Complete well-formed XML vs. fragment XML</h3>
035 * <p>
036 * If you configure {@code complete="true"}, the appender outputs a well-formed XML document where the default namespace
037 * is the log4j namespace {@value XmlConstants#XML_NAMESPACE}. By default, with {@code complete="false"}, you should
038 * include the output as an <em>external entity</em> in a separate file to form a well-formed XML document.
039 * </p>
040 * <p>
041 * If {@code complete="false"}, the appender does not write the XML processing instruction and the root element.
042 * </p>
043 * <h3>Encoding</h3>
044 * <p>
045 * Appenders using this layout should have their {@code charset} set to {@code UTF-8} or {@code UTF-16}, otherwise
046 * events containing non-ASCII characters could result in corrupted log files.
047 * </p>
048 * <h3>Pretty vs. compact XML</h3>
049 * <p>
050 * By default, the XML layout is not compact (compact = not "pretty") with {@code compact="false"}, which means the
051 * appender uses end-of-line characters and indents lines to format the XML. If {@code compact="true"}, then no
052 * end-of-line or indentation is used. Message content may contain, of course, end-of-lines.
053 * </p>
054 */
055@Plugin(name = "XmlLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
056public final class XmlLayout extends AbstractJacksonLayout {
057
058    private static final String ROOT_TAG = "Events";
059
060    public static class Builder<B extends Builder<B>> extends AbstractJacksonLayout.Builder<B>
061        implements org.apache.logging.log4j.core.util.Builder<XmlLayout> {
062
063        public Builder() {
064            super();
065            setCharset(StandardCharsets.UTF_8);
066        }
067
068        @Override
069        public XmlLayout build() {
070            return new XmlLayout(getConfiguration(), isLocationInfo(), isProperties(), isComplete(),
071                    isCompact(), getCharset(), isIncludeStacktrace(), isStacktraceAsString(),
072                    isIncludeNullDelimiter());
073        }
074    }
075
076    /**
077     * @deprecated Use {@link #newBuilder()} instead
078     */
079    @Deprecated
080    protected XmlLayout(final boolean locationInfo, final boolean properties, final boolean complete,
081                        final boolean compact, final Charset charset, final boolean includeStacktrace) {
082        this(null, locationInfo, properties, complete, compact, charset, includeStacktrace, false, false);
083    }
084
085    private XmlLayout(final Configuration config, final boolean locationInfo, final boolean properties,
086                      final boolean complete, final boolean compact, final Charset charset,
087                      final boolean includeStacktrace, final boolean stacktraceAsString,
088                      final boolean includeNullDelimiter) {
089        super(config, new JacksonFactory.XML(includeStacktrace, stacktraceAsString).newWriter(
090            locationInfo, properties, compact), charset, compact, complete, false, null, null, includeNullDelimiter);
091    }
092
093    /**
094     * Returns appropriate XML headers.
095     * <ol>
096     * <li>XML processing instruction</li>
097     * <li>XML root element</li>
098     * </ol>
099     *
100     * @return a byte array containing the header.
101     */
102    @Override
103    public byte[] getHeader() {
104        if (!complete) {
105            return null;
106        }
107        final StringBuilder buf = new StringBuilder();
108        buf.append("<?xml version=\"1.0\" encoding=\"");
109        buf.append(this.getCharset().name());
110        buf.append("\"?>");
111        buf.append(this.eol);
112        // Make the log4j namespace the default namespace, no need to use more space with a namespace prefix.
113        buf.append('<');
114        buf.append(ROOT_TAG);
115        buf.append(" xmlns=\"" + XmlConstants.XML_NAMESPACE + "\">");
116        buf.append(this.eol);
117        return buf.toString().getBytes(this.getCharset());
118    }
119
120    /**
121     * Returns appropriate XML footer.
122     *
123     * @return a byte array containing the footer, closing the XML root element.
124     */
125    @Override
126    public byte[] getFooter() {
127        if (!complete) {
128            return null;
129        }
130        return getBytes("</" + ROOT_TAG + '>' + this.eol);
131    }
132
133    /**
134     * Gets this XmlLayout's content format. Specified by:
135     * <ul>
136     * <li>Key: "dtd" Value: "log4j-events.dtd"</li>
137     * <li>Key: "version" Value: "2.0"</li>
138     * </ul>
139     * 
140     * @return Map of content format keys supporting XmlLayout
141     */
142    @Override
143    public Map<String, String> getContentFormat() {
144        final Map<String, String> result = new HashMap<>();
145        // result.put("dtd", "log4j-events.dtd");
146        result.put("xsd", "log4j-events.xsd");
147        result.put("version", "2.0");
148        return result;
149    }
150
151    /**
152     * @return The content type.
153     */
154    @Override
155    public String getContentType() {
156        return "text/xml; charset=" + this.getCharset();
157    }
158
159    /**
160     * Creates an XML Layout.
161     *
162     * @param locationInfo If "true", includes the location information in the generated XML.
163     * @param properties If "true", includes the thread context map in the generated XML.
164     * @param complete If "true", includes the XML header and footer, defaults to "false".
165     * @param compact If "true", does not use end-of-lines and indentation, defaults to "false".
166     * @param charset The character set to use, if {@code null}, uses "UTF-8".
167     * @param includeStacktrace
168     *            If "true", includes the stacktrace of any Throwable in the generated XML, defaults to "true".
169     * @return An XML Layout.
170     *
171     * @deprecated Use {@link #newBuilder()} instead
172     */
173    @Deprecated
174    public static XmlLayout createLayout(
175            final boolean locationInfo,
176            final boolean properties,
177            final boolean complete,
178            final boolean compact,
179            final Charset charset,
180            final boolean includeStacktrace) {
181        return new XmlLayout(null, locationInfo, properties, complete, compact, charset, includeStacktrace, false,
182                false);
183    }
184
185    @PluginBuilderFactory
186    public static <B extends Builder<B>> B newBuilder() {
187        return new Builder<B>().asBuilder();
188    }
189
190    /**
191     * Creates an XML Layout using the default settings.
192     *
193     * @return an XML Layout.
194     */
195    public static XmlLayout createDefaultLayout() {
196        return new XmlLayout(null, false, false, false, false, StandardCharsets.UTF_8, true, false, false);
197    }
198}