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; 021 022import org.apache.logging.log4j.core.LogEvent; 023import org.apache.logging.log4j.core.StringLayout; 024import org.apache.logging.log4j.core.config.Configuration; 025import org.apache.logging.log4j.core.config.LoggerConfig; 026import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; 027import org.apache.logging.log4j.core.config.plugins.PluginElement; 028import org.apache.logging.log4j.core.impl.DefaultLogEventFactory; 029import org.apache.logging.log4j.core.impl.LocationAware; 030import org.apache.logging.log4j.core.util.Constants; 031import org.apache.logging.log4j.core.util.StringEncoder; 032import org.apache.logging.log4j.spi.AbstractLogger; 033import org.apache.logging.log4j.util.PropertiesUtil; 034import org.apache.logging.log4j.util.StringBuilders; 035import org.apache.logging.log4j.util.Strings; 036 037/** 038 * Abstract base class for Layouts that result in a String. 039 * <p> 040 * Since 2.4.1, this class has custom logic to convert ISO-8859-1 or US-ASCII Strings to byte[] arrays to improve 041 * performance: all characters are simply cast to bytes. 042 * </p> 043 */ 044public abstract class AbstractStringLayout extends AbstractLayout<String> implements StringLayout, LocationAware { 045 046 public abstract static class Builder<B extends Builder<B>> extends AbstractLayout.Builder<B> { 047 048 @PluginBuilderAttribute(value = "charset") 049 private Charset charset; 050 051 @PluginElement("footerSerializer") 052 private Serializer footerSerializer; 053 054 @PluginElement("headerSerializer") 055 private Serializer headerSerializer; 056 057 public Charset getCharset() { 058 return charset; 059 } 060 061 public Serializer getFooterSerializer() { 062 return footerSerializer; 063 } 064 065 public Serializer getHeaderSerializer() { 066 return headerSerializer; 067 } 068 069 public B setCharset(final Charset charset) { 070 this.charset = charset; 071 return asBuilder(); 072 } 073 074 public B setFooterSerializer(final Serializer footerSerializer) { 075 this.footerSerializer = footerSerializer; 076 return asBuilder(); 077 } 078 079 public B setHeaderSerializer(final Serializer headerSerializer) { 080 this.headerSerializer = headerSerializer; 081 return asBuilder(); 082 } 083 084 } 085 086 @Override 087 public boolean requiresLocation() { 088 return false; 089 } 090 091 public interface Serializer extends Serializer2 { 092 String toSerializable(final LogEvent event); 093 094 @Override 095 default StringBuilder toSerializable(final LogEvent event, final StringBuilder builder) { 096 builder.append(toSerializable(event)); 097 return builder; 098 } 099 } 100 101 /** 102 * Variation of {@link Serializer} that avoids allocating temporary objects. 103 * As of 2.13 this interface was merged into the Serializer interface. 104 * @since 2.6 105 */ 106 public interface Serializer2 { 107 StringBuilder toSerializable(final LogEvent event, final StringBuilder builder); 108 } 109 110 /** 111 * Default length for new StringBuilder instances: {@value} . 112 */ 113 protected static final int DEFAULT_STRING_BUILDER_SIZE = 1024; 114 115 protected static final int MAX_STRING_BUILDER_SIZE = Math.max(DEFAULT_STRING_BUILDER_SIZE, 116 size("log4j.layoutStringBuilder.maxSize", 2 * 1024)); 117 118 private static final ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>(); 119 120 /** 121 * Returns a {@code StringBuilder} that this Layout implementation can use to write the formatted log event to. 122 * 123 * @return a {@code StringBuilder} 124 */ 125 protected static StringBuilder getStringBuilder() { 126 if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-2368 127 // Recursive logging may clobber the cached StringBuilder. 128 return new StringBuilder(DEFAULT_STRING_BUILDER_SIZE); 129 } 130 StringBuilder result = threadLocal.get(); 131 if (result == null) { 132 result = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE); 133 threadLocal.set(result); 134 } 135 trimToMaxSize(result); 136 result.setLength(0); 137 return result; 138 } 139 140 private static int size(final String property, final int defaultValue) { 141 return PropertiesUtil.getProperties().getIntegerProperty(property, defaultValue); 142 } 143 144 protected static void trimToMaxSize(final StringBuilder stringBuilder) { 145 StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE); 146 } 147 148 private Encoder<StringBuilder> textEncoder; 149 /** 150 * The charset for the formatted message. 151 */ 152 private final Charset charset; 153 154 private final Serializer footerSerializer; 155 156 private final Serializer headerSerializer; 157 158 protected AbstractStringLayout(final Charset charset) { 159 this(charset, (byte[]) null, (byte[]) null); 160 } 161 162 /** 163 * Builds a new layout. 164 * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be 165 * converted from strings to bytes. 166 * @param header the header bytes 167 * @param footer the footer bytes 168 */ 169 protected AbstractStringLayout(final Charset aCharset, final byte[] header, final byte[] footer) { 170 super(null, header, footer); 171 this.headerSerializer = null; 172 this.footerSerializer = null; 173 this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; 174 textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; 175 } 176 177 /** 178 * Builds a new layout. 179 * @param config the configuration 180 * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be 181 * converted from strings to bytes. 182 * @param headerSerializer the header bytes serializer 183 * @param footerSerializer the footer bytes serializer 184 */ 185 protected AbstractStringLayout(final Configuration config, final Charset aCharset, 186 final Serializer headerSerializer, final Serializer footerSerializer) { 187 super(config, null, null); 188 this.headerSerializer = headerSerializer; 189 this.footerSerializer = footerSerializer; 190 this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; 191 textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; 192 } 193 194 protected byte[] getBytes(final String s) { 195 return s.getBytes(charset); 196 } 197 198 @Override 199 public Charset getCharset() { 200 return charset; 201 } 202 203 /** 204 * @return The default content type for Strings. 205 */ 206 @Override 207 public String getContentType() { 208 return "text/plain"; 209 } 210 211 /** 212 * Returns the footer, if one is available. 213 * 214 * @return A byte array containing the footer. 215 */ 216 @Override 217 public byte[] getFooter() { 218 return serializeToBytes(footerSerializer, super.getFooter()); 219 } 220 221 public Serializer getFooterSerializer() { 222 return footerSerializer; 223 } 224 225 /** 226 * Returns the header, if one is available. 227 * 228 * @return A byte array containing the header. 229 */ 230 @Override 231 public byte[] getHeader() { 232 return serializeToBytes(headerSerializer, super.getHeader()); 233 } 234 235 public Serializer getHeaderSerializer() { 236 return headerSerializer; 237 } 238 239 private DefaultLogEventFactory getLogEventFactory() { 240 return DefaultLogEventFactory.getInstance(); 241 } 242 243 /** 244 * Returns a {@code Encoder<StringBuilder>} that this Layout implementation can use for encoding log events. 245 * 246 * @return a {@code Encoder<StringBuilder>} 247 */ 248 protected Encoder<StringBuilder> getStringBuilderEncoder() { 249 if (textEncoder == null) { 250 textEncoder = new StringBuilderEncoder(getCharset()); 251 } 252 return textEncoder; 253 } 254 255 protected byte[] serializeToBytes(final Serializer serializer, final byte[] defaultValue) { 256 final String serializable = serializeToString(serializer); 257 if (serializable == null) { 258 return defaultValue; 259 } 260 return StringEncoder.toBytes(serializable, getCharset()); 261 } 262 263 protected String serializeToString(final Serializer serializer) { 264 if (serializer == null) { 265 return null; 266 } 267 final LoggerConfig rootLogger = getConfiguration().getRootLogger(); 268 // Using "" for the FQCN, does it matter? 269 final LogEvent logEvent = getLogEventFactory().createEvent(rootLogger.getName(), null, Strings.EMPTY, 270 rootLogger.getLevel(), null, null, null); 271 return serializer.toSerializable(logEvent); 272 } 273 274 /** 275 * Formats the Log Event as a byte array. 276 * 277 * @param event The Log Event. 278 * @return The formatted event as a byte array. 279 */ 280 @Override 281 public byte[] toByteArray(final LogEvent event) { 282 return getBytes(toSerializable(event)); 283 } 284 285}