View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.layout;
18  
19  import java.nio.charset.Charset;
20  import java.nio.charset.StandardCharsets;
21  import java.util.HashMap;
22  import java.util.Map;
23  
24  import org.apache.logging.log4j.core.Layout;
25  import org.apache.logging.log4j.core.config.Configuration;
26  import org.apache.logging.log4j.core.config.DefaultConfiguration;
27  import org.apache.logging.log4j.core.config.Node;
28  import org.apache.logging.log4j.core.config.plugins.Plugin;
29  import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
30  import org.apache.logging.log4j.util.Strings;
31  
32  /**
33   * Appends a series of YAML events as strings serialized as bytes.
34   *
35   * <h3>Encoding</h3>
36   * <p>
37   * Appenders using this layout should have their {@code charset} set to {@code UTF-8} or {@code UTF-16}, otherwise
38   * events containing non ASCII characters could result in corrupted log files.
39   * </p>
40   */
41  @Plugin(name = "YamlLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
42  public final class YamlLayout extends AbstractJacksonLayout {
43  
44      private static final String DEFAULT_FOOTER = Strings.EMPTY;
45  
46      private static final String DEFAULT_HEADER = Strings.EMPTY;
47  
48      static final String CONTENT_TYPE = "application/yaml";
49  
50      public static class Builder<B extends Builder<B>> extends AbstractJacksonLayout.Builder<B>
51          implements org.apache.logging.log4j.core.util.Builder<YamlLayout> {
52  
53          public Builder() {
54              super();
55              setCharset(StandardCharsets.UTF_8);
56          }
57  
58          @Override
59          public YamlLayout build() {
60              final String headerPattern = toStringOrNull(getHeader());
61              final String footerPattern = toStringOrNull(getFooter());
62              return new YamlLayout(getConfiguration(), isLocationInfo(), isProperties(), isComplete(),
63                      isCompact(), getEventEol(), headerPattern, footerPattern, getCharset(),
64                      isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter());
65          }
66      }
67  
68      /**
69       * @deprecated Use {@link #newBuilder()} instead
70       */
71      @Deprecated
72      protected YamlLayout(final Configuration config, final boolean locationInfo, final boolean properties,
73              final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern,
74              final String footerPattern, final Charset charset, final boolean includeStacktrace) {
75          super(config, new JacksonFactory.YAML(includeStacktrace, false).newWriter(locationInfo, properties, compact), charset, compact,
76                  complete, eventEol,
77                  PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(),
78                  PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build(),
79                  false);
80      }
81  
82      private YamlLayout(final Configuration config, final boolean locationInfo, final boolean properties,
83                         final boolean complete, final boolean compact, final boolean eventEol,
84                         final String headerPattern, final String footerPattern, final Charset charset,
85                         final boolean includeStacktrace, final boolean stacktraceAsString,
86                         final boolean includeNullDelimiter) {
87          super(config, new JacksonFactory.YAML(includeStacktrace, stacktraceAsString).newWriter(locationInfo, properties, compact), charset, compact,
88                  complete, eventEol,
89                  PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(),
90                  PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build(),
91                  includeNullDelimiter);
92      }
93  
94      /**
95       * Returns appropriate YAML header.
96       *
97       * @return a byte array containing the header, opening the YAML array.
98       */
99      @Override
100     public byte[] getHeader() {
101         if (!this.complete) {
102             return null;
103         }
104         final StringBuilder buf = new StringBuilder();
105         final String str = serializeToString(getHeaderSerializer());
106         if (str != null) {
107             buf.append(str);
108         }
109         buf.append(this.eol);
110         return getBytes(buf.toString());
111     }
112 
113     /**
114      * Returns appropriate YAML footer.
115      *
116      * @return a byte array containing the footer, closing the YAML array.
117      */
118     @Override
119     public byte[] getFooter() {
120         if (!this.complete) {
121             return null;
122         }
123         final StringBuilder buf = new StringBuilder();
124         buf.append(this.eol);
125         final String str = serializeToString(getFooterSerializer());
126         if (str != null) {
127             buf.append(str);
128         }
129         buf.append(this.eol);
130         return getBytes(buf.toString());
131     }
132 
133     @Override
134     public Map<String, String> getContentFormat() {
135         final Map<String, String> result = new HashMap<>();
136         result.put("version", "2.0");
137         return result;
138     }
139 
140     /**
141      * @return The content type.
142      */
143     @Override
144     public String getContentType() {
145         return CONTENT_TYPE + "; charset=" + this.getCharset();
146     }
147 
148     /**
149      * Creates a YAML Layout.
150      * 
151      * @param config
152      *            The plugin configuration.
153      * @param locationInfo
154      *            If "true", includes the location information in the generated YAML.
155      * @param properties
156      *            If "true", includes the thread context map in the generated YAML.
157      * @param headerPattern
158      *            The header pattern, defaults to {@code ""} if null.
159      * @param footerPattern
160      *            The header pattern, defaults to {@code ""} if null.
161      * @param charset
162      *            The character set to use, if {@code null}, uses "UTF-8".
163      * @param includeStacktrace
164      *            If "true", includes the stacktrace of any Throwable in the generated YAML, defaults to "true".
165      * @return A YAML Layout.
166      *
167      * @deprecated Use {@link #newBuilder()} instead
168      */
169     @Deprecated
170     public static AbstractJacksonLayout createLayout(
171             final Configuration config,
172             final boolean locationInfo,
173             final boolean properties,
174             final String headerPattern,
175             final String footerPattern,
176             final Charset charset,
177             final boolean includeStacktrace) {
178         return new YamlLayout(config, locationInfo, properties, false, false, true, headerPattern, footerPattern,
179                 charset, includeStacktrace, false, false);
180     }
181 
182     @PluginBuilderFactory
183     public static <B extends Builder<B>> B newBuilder() {
184         return new Builder<B>().asBuilder();
185     }
186 
187     /**
188      * Creates a YAML Layout using the default settings. Useful for testing.
189      *
190      * @return A YAML Layout.
191      */
192     public static AbstractJacksonLayout createDefaultLayout() {
193         return new YamlLayout(new DefaultConfiguration(), false, false, false, false, false, DEFAULT_HEADER,
194                 DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false, false);
195     }
196 }