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