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 setCharset(StandardCharsets.UTF_8); 062 } 063 064 @Override 065 public YamlLayout build() { 066 final String headerPattern = toStringOrNull(getHeader()); 067 final String footerPattern = toStringOrNull(getFooter()); 068 return new YamlLayout(getConfiguration(), isLocationInfo(), isProperties(), isComplete(), 069 isCompact(), getEventEol(), getEndOfLine(), headerPattern, footerPattern, getCharset(), 070 isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter(), 071 isIncludeTimeMillis(), getAdditionalFields()); 072 } 073 } 074 075 /** 076 * @deprecated Use {@link #newBuilder()} instead 077 */ 078 @Deprecated 079 protected YamlLayout(final Configuration config, final boolean locationInfo, final boolean properties, 080 final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern, 081 final String footerPattern, final Charset charset, final boolean includeStacktrace) { 082 super(config, new JacksonFactory.YAML(includeStacktrace, false).newWriter(locationInfo, properties, compact), 083 charset, compact, complete, eventEol, null, 084 PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(), 085 PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build(), 086 false, null); 087 } 088 089 private YamlLayout(final Configuration config, final boolean locationInfo, final boolean properties, 090 final boolean complete, final boolean compact, final boolean eventEol, final String endOfLine, 091 final String headerPattern, final String footerPattern, final Charset charset, 092 final boolean includeStacktrace, final boolean stacktraceAsString, 093 final boolean includeNullDelimiter, final boolean includeTimeMillis, 094 final KeyValuePair[] additionalFields) { 095 super(config, new JacksonFactory.YAML(includeStacktrace, stacktraceAsString) 096 .newWriter(locationInfo, properties, compact, includeTimeMillis), 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, 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, false, null); 205 } 206}