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}