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