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.DefaultConfiguration;
027import org.apache.logging.log4j.core.config.Node;
028import org.apache.logging.log4j.core.config.plugins.Plugin;
029import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
030import org.apache.logging.log4j.core.util.KeyValuePair;
031import org.apache.logging.log4j.util.Strings;
032
033/**
034 * Appends a series of YAML events as strings serialized as bytes.
035 *
036 * <h3>Encoding</h3>
037 * <p>
038 * Appenders using this layout should have their {@code charset} set to {@code UTF-8} or {@code UTF-16}, otherwise
039 * events containing non ASCII characters could result in corrupted log files.
040 * </p>
041 * <h3>Additional Fields</h3>
042 * <p>
043 * This property allows addition of custom fields into generated JSON.
044 * {@code <YamlLayout><KeyValuePair key="foo" value="bar"/></YamlLayout>} inserts {@code foo: "bar"} directly
045 * into YAML output. Supports Lookup expressions.
046 * </p>
047 */
048@Plugin(name = "YamlLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
049public final class YamlLayout extends AbstractJacksonLayout {
050
051    private static final String DEFAULT_FOOTER = Strings.EMPTY;
052
053    private static final String DEFAULT_HEADER = Strings.EMPTY;
054
055    static final String CONTENT_TYPE = "application/yaml";
056
057    public static class Builder<B extends Builder<B>> extends AbstractJacksonLayout.Builder<B>
058        implements org.apache.logging.log4j.core.util.Builder<YamlLayout> {
059
060        public Builder() {
061            super();
062            setCharset(StandardCharsets.UTF_8);
063        }
064
065        @Override
066        public YamlLayout build() {
067            final String headerPattern = toStringOrNull(getHeader());
068            final String footerPattern = toStringOrNull(getFooter());
069            return new YamlLayout(getConfiguration(), isLocationInfo(), isProperties(), isComplete(),
070                    isCompact(), getEventEol(), getEndOfLine(), headerPattern, footerPattern, getCharset(),
071                    isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter(),
072                    getAdditionalFields());
073        }
074    }
075
076    /**
077     * @deprecated Use {@link #newBuilder()} instead
078     */
079    @Deprecated
080    protected YamlLayout(final Configuration config, final boolean locationInfo, final boolean properties,
081            final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern,
082            final String footerPattern, final Charset charset, final boolean includeStacktrace) {
083        super(config, new JacksonFactory.YAML(includeStacktrace, false).newWriter(locationInfo, properties, compact),
084                charset, compact, complete, eventEol, null,
085                PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(),
086                PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build(),
087                false, null);
088    }
089
090    private YamlLayout(final Configuration config, final boolean locationInfo, final boolean properties,
091                       final boolean complete, final boolean compact, final boolean eventEol, final String endOfLine,
092                       final String headerPattern, final String footerPattern, final Charset charset,
093                       final boolean includeStacktrace, final boolean stacktraceAsString,
094                       final boolean includeNullDelimiter,
095                       final KeyValuePair[] additionalFields) {
096        super(config, new JacksonFactory.YAML(includeStacktrace, stacktraceAsString).newWriter(locationInfo, properties, compact),
097                charset, compact, complete, eventEol, endOfLine,
098                PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(),
099                PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build(),
100                includeNullDelimiter,
101                additionalFields);
102    }
103
104    /**
105     * Returns appropriate YAML header.
106     *
107     * @return a byte array containing the header, opening the YAML array.
108     */
109    @Override
110    public byte[] getHeader() {
111        if (!this.complete) {
112            return null;
113        }
114        final StringBuilder buf = new StringBuilder();
115        final String str = serializeToString(getHeaderSerializer());
116        if (str != null) {
117            buf.append(str);
118        }
119        buf.append(this.eol);
120        return getBytes(buf.toString());
121    }
122
123    /**
124     * Returns appropriate YAML footer.
125     *
126     * @return a byte array containing the footer, closing the YAML array.
127     */
128    @Override
129    public byte[] getFooter() {
130        if (!this.complete) {
131            return null;
132        }
133        final StringBuilder buf = new StringBuilder();
134        buf.append(this.eol);
135        final String str = serializeToString(getFooterSerializer());
136        if (str != null) {
137            buf.append(str);
138        }
139        buf.append(this.eol);
140        return getBytes(buf.toString());
141    }
142
143    @Override
144    public Map<String, String> getContentFormat() {
145        final Map<String, String> result = new HashMap<>();
146        result.put("version", "2.0");
147        return result;
148    }
149
150    /**
151     * @return The content type.
152     */
153    @Override
154    public String getContentType() {
155        return CONTENT_TYPE + "; charset=" + this.getCharset();
156    }
157
158    /**
159     * Creates a YAML Layout.
160     *
161     * @param config
162     *            The plugin configuration.
163     * @param locationInfo
164     *            If "true", includes the location information in the generated YAML.
165     * @param properties
166     *            If "true", includes the thread context map in the generated YAML.
167     * @param headerPattern
168     *            The header pattern, defaults to {@code ""} if null.
169     * @param footerPattern
170     *            The header pattern, defaults to {@code ""} if null.
171     * @param charset
172     *            The character set to use, if {@code null}, uses "UTF-8".
173     * @param includeStacktrace
174     *            If "true", includes the stacktrace of any Throwable in the generated YAML, defaults to "true".
175     * @return A YAML Layout.
176     *
177     * @deprecated Use {@link #newBuilder()} instead
178     */
179    @Deprecated
180    public static AbstractJacksonLayout createLayout(
181            final Configuration config,
182            final boolean locationInfo,
183            final boolean properties,
184            final String headerPattern,
185            final String footerPattern,
186            final Charset charset,
187            final boolean includeStacktrace) {
188        return new YamlLayout(config, locationInfo, properties, false, false, true, null, headerPattern, footerPattern,
189                charset, includeStacktrace, false, false, null);
190    }
191
192    @PluginBuilderFactory
193    public static <B extends Builder<B>> B newBuilder() {
194        return new Builder<B>().asBuilder();
195    }
196
197    /**
198     * Creates a YAML Layout using the default settings. Useful for testing.
199     *
200     * @return A YAML Layout.
201     */
202    public static AbstractJacksonLayout createDefaultLayout() {
203        return new YamlLayout(new DefaultConfiguration(), false, false, false, false, false, null, DEFAULT_HEADER,
204                DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false, false, null);
205    }
206}