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 public boolean requiresLocation() { 092 return false; 093 } 094 095 public interface Serializer { 096 String toSerializable(final LogEvent event); 097 } 098 099 /** 100 * Variation of {@link Serializer} that avoids allocating temporary objects. 101 * @since 2.6 102 */ 103 public interface Serializer2 { 104 StringBuilder toSerializable(final LogEvent event, final StringBuilder builder); 105 } 106 107 /** 108 * Default length for new StringBuilder instances: {@value} . 109 */ 110 protected static final int DEFAULT_STRING_BUILDER_SIZE = 1024; 111 112 protected static final int MAX_STRING_BUILDER_SIZE = Math.max(DEFAULT_STRING_BUILDER_SIZE, 113 size("log4j.layoutStringBuilder.maxSize", 2 * 1024)); 114 115 private static final ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>(); 116 117 /** 118 * Returns a {@code StringBuilder} that this Layout implementation can use to write the formatted log event to. 119 * 120 * @return a {@code StringBuilder} 121 */ 122 protected static StringBuilder getStringBuilder() { 123 if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-2368 124 // Recursive logging may clobber the cached StringBuilder. 125 return new StringBuilder(DEFAULT_STRING_BUILDER_SIZE); 126 } 127 StringBuilder result = threadLocal.get(); 128 if (result == null) { 129 result = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE); 130 threadLocal.set(result); 131 } 132 trimToMaxSize(result); 133 result.setLength(0); 134 return result; 135 } 136 137 // LOG4J2-1151: If the built-in JDK 8 encoders are available we should use them. 138 private static boolean isPreJava8() { 139 return org.apache.logging.log4j.util.Constants.JAVA_MAJOR_VERSION < 8; 140 } 141 142 private static int size(final String property, final int defaultValue) { 143 return PropertiesUtil.getProperties().getIntegerProperty(property, defaultValue); 144 } 145 146 protected static void trimToMaxSize(final StringBuilder stringBuilder) { 147 StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE); 148 } 149 150 private Encoder<StringBuilder> textEncoder; 151 /** 152 * The charset for the formatted message. 153 */ 154 // LOG4J2-1099: Charset cannot be final due to serialization needs, so we serialize as Charset name instead 155 private transient Charset charset; 156 157 private final String charsetName; 158 159 private final Serializer footerSerializer; 160 161 private final Serializer headerSerializer; 162 163 private final boolean useCustomEncoding; 164 165 protected AbstractStringLayout(final Charset charset) { 166 this(charset, (byte[]) null, (byte[]) null); 167 } 168 169 /** 170 * Builds a new layout. 171 * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be 172 * converted from strings to bytes. 173 * @param header the header bytes 174 * @param footer the footer bytes 175 */ 176 protected AbstractStringLayout(final Charset aCharset, final byte[] header, final byte[] footer) { 177 super(null, header, footer); 178 this.headerSerializer = null; 179 this.footerSerializer = null; 180 this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; 181 this.charsetName = this.charset.name(); 182 useCustomEncoding = isPreJava8() 183 && (StandardCharsets.ISO_8859_1.equals(aCharset) || StandardCharsets.US_ASCII.equals(aCharset)); 184 textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; 185 } 186 187 /** 188 * Builds a new layout. 189 * @param config the configuration 190 * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be 191 * converted from strings to bytes. 192 * @param headerSerializer the header bytes serializer 193 * @param footerSerializer the footer bytes serializer 194 */ 195 protected AbstractStringLayout(final Configuration config, final Charset aCharset, 196 final Serializer headerSerializer, final Serializer footerSerializer) { 197 super(config, null, null); 198 this.headerSerializer = headerSerializer; 199 this.footerSerializer = footerSerializer; 200 this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; 201 this.charsetName = this.charset.name(); 202 useCustomEncoding = isPreJava8() 203 && (StandardCharsets.ISO_8859_1.equals(aCharset) || StandardCharsets.US_ASCII.equals(aCharset)); 204 textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; 205 } 206 207 protected byte[] getBytes(final String s) { 208 if (useCustomEncoding) { // rely on branch prediction to eliminate this check if false 209 return StringEncoder.encodeSingleByteChars(s); 210 } 211 try { // LOG4J2-935: String.getBytes(String) gives better performance 212 return s.getBytes(charsetName); 213 } catch (final UnsupportedEncodingException e) { 214 return s.getBytes(charset); 215 } 216 } 217 218 @Override 219 public Charset getCharset() { 220 return charset; 221 } 222 223 /** 224 * @return The default content type for Strings. 225 */ 226 @Override 227 public String getContentType() { 228 return "text/plain"; 229 } 230 231 /** 232 * Returns the footer, if one is available. 233 * 234 * @return A byte array containing the footer. 235 */ 236 @Override 237 public byte[] getFooter() { 238 return serializeToBytes(footerSerializer, super.getFooter()); 239 } 240 241 public Serializer getFooterSerializer() { 242 return footerSerializer; 243 } 244 245 /** 246 * Returns the header, if one is available. 247 * 248 * @return A byte array containing the header. 249 */ 250 @Override 251 public byte[] getHeader() { 252 return serializeToBytes(headerSerializer, super.getHeader()); 253 } 254 255 public Serializer getHeaderSerializer() { 256 return headerSerializer; 257 } 258 259 private DefaultLogEventFactory getLogEventFactory() { 260 return DefaultLogEventFactory.getInstance(); 261 } 262 263 /** 264 * Returns a {@code Encoder<StringBuilder>} that this Layout implementation can use for encoding log events. 265 * 266 * @return a {@code Encoder<StringBuilder>} 267 */ 268 protected Encoder<StringBuilder> getStringBuilderEncoder() { 269 if (textEncoder == null) { 270 textEncoder = new StringBuilderEncoder(getCharset()); 271 } 272 return textEncoder; 273 } 274 275 protected byte[] serializeToBytes(final Serializer serializer, final byte[] defaultValue) { 276 final String serializable = serializeToString(serializer); 277 if (serializer == null) { 278 return defaultValue; 279 } 280 return StringEncoder.toBytes(serializable, getCharset()); 281 } 282 283 protected String serializeToString(final Serializer serializer) { 284 if (serializer == null) { 285 return null; 286 } 287 final LoggerConfig rootLogger = getConfiguration().getRootLogger(); 288 // Using "" for the FQCN, does it matter? 289 final LogEvent logEvent = getLogEventFactory().createEvent(rootLogger.getName(), null, Strings.EMPTY, 290 rootLogger.getLevel(), null, null, null); 291 return serializer.toSerializable(logEvent); 292 } 293 294 /** 295 * Formats the Log Event as a byte array. 296 * 297 * @param event The Log Event. 298 * @return The formatted event as a byte array. 299 */ 300 @Override 301 public byte[] toByteArray(final LogEvent event) { 302 return getBytes(toSerializable(event)); 303 } 304 305}