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 return org.apache.logging.log4j.util.Constants.JAVA_MAJOR_VERSION < 8; 135 } 136 137 private static int size(final String property, final int defaultValue) { 138 return PropertiesUtil.getProperties().getIntegerProperty(property, defaultValue); 139 } 140 141 protected static void trimToMaxSize(final StringBuilder stringBuilder) { 142 StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE); 143 } 144 145 private Encoder<StringBuilder> textEncoder; 146 /** 147 * The charset for the formatted message. 148 */ 149 // LOG4J2-1099: Charset cannot be final due to serialization needs, so we serialize as Charset name instead 150 private transient Charset charset; 151 152 private final String charsetName; 153 154 private final Serializer footerSerializer; 155 156 private final Serializer headerSerializer; 157 158 private final boolean useCustomEncoding; 159 160 protected AbstractStringLayout(final Charset charset) { 161 this(charset, (byte[]) null, (byte[]) null); 162 } 163 164 /** 165 * Builds a new layout. 166 * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be 167 * converted from strings to bytes. 168 * @param header the header bytes 169 * @param footer the footer bytes 170 */ 171 protected AbstractStringLayout(final Charset aCharset, final byte[] header, final byte[] footer) { 172 super(null, header, footer); 173 this.headerSerializer = null; 174 this.footerSerializer = null; 175 this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; 176 this.charsetName = this.charset.name(); 177 useCustomEncoding = isPreJava8() 178 && (StandardCharsets.ISO_8859_1.equals(aCharset) || StandardCharsets.US_ASCII.equals(aCharset)); 179 textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; 180 } 181 182 /** 183 * Builds a new layout. 184 * @param config the configuration 185 * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be 186 * converted from strings to bytes. 187 * @param headerSerializer the header bytes serializer 188 * @param footerSerializer the footer bytes serializer 189 */ 190 protected AbstractStringLayout(final Configuration config, final Charset aCharset, 191 final Serializer headerSerializer, final Serializer footerSerializer) { 192 super(config, null, null); 193 this.headerSerializer = headerSerializer; 194 this.footerSerializer = footerSerializer; 195 this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; 196 this.charsetName = this.charset.name(); 197 useCustomEncoding = isPreJava8() 198 && (StandardCharsets.ISO_8859_1.equals(aCharset) || StandardCharsets.US_ASCII.equals(aCharset)); 199 textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; 200 } 201 202 protected byte[] getBytes(final String s) { 203 if (useCustomEncoding) { // rely on branch prediction to eliminate this check if false 204 return StringEncoder.encodeSingleByteChars(s); 205 } 206 try { // LOG4J2-935: String.getBytes(String) gives better performance 207 return s.getBytes(charsetName); 208 } catch (final UnsupportedEncodingException e) { 209 return s.getBytes(charset); 210 } 211 } 212 213 @Override 214 public Charset getCharset() { 215 return charset; 216 } 217 218 /** 219 * @return The default content type for Strings. 220 */ 221 @Override 222 public String getContentType() { 223 return "text/plain"; 224 } 225 226 /** 227 * Returns the footer, if one is available. 228 * 229 * @return A byte array containing the footer. 230 */ 231 @Override 232 public byte[] getFooter() { 233 return serializeToBytes(footerSerializer, super.getFooter()); 234 } 235 236 public Serializer getFooterSerializer() { 237 return footerSerializer; 238 } 239 240 /** 241 * Returns the header, if one is available. 242 * 243 * @return A byte array containing the header. 244 */ 245 @Override 246 public byte[] getHeader() { 247 return serializeToBytes(headerSerializer, super.getHeader()); 248 } 249 250 public Serializer getHeaderSerializer() { 251 return headerSerializer; 252 } 253 254 private DefaultLogEventFactory getLogEventFactory() { 255 return DefaultLogEventFactory.getInstance(); 256 } 257 258 /** 259 * Returns a {@code Encoder<StringBuilder>} that this Layout implementation can use for encoding log events. 260 * 261 * @return a {@code Encoder<StringBuilder>} 262 */ 263 protected Encoder<StringBuilder> getStringBuilderEncoder() { 264 if (textEncoder == null) { 265 textEncoder = new StringBuilderEncoder(getCharset()); 266 } 267 return textEncoder; 268 } 269 270 protected byte[] serializeToBytes(final Serializer serializer, final byte[] defaultValue) { 271 final String serializable = serializeToString(serializer); 272 if (serializer == null) { 273 return defaultValue; 274 } 275 return StringEncoder.toBytes(serializable, getCharset()); 276 } 277 278 protected String serializeToString(final Serializer serializer) { 279 if (serializer == null) { 280 return null; 281 } 282 final LoggerConfig rootLogger = getConfiguration().getRootLogger(); 283 // Using "" for the FQCN, does it matter? 284 final LogEvent logEvent = getLogEventFactory().createEvent(rootLogger.getName(), null, Strings.EMPTY, 285 rootLogger.getLevel(), null, null, null); 286 return serializer.toSerializable(logEvent); 287 } 288 289 /** 290 * Formats the Log Event as a byte array. 291 * 292 * @param event The Log Event. 293 * @return The formatted event as a byte array. 294 */ 295 @Override 296 public byte[] toByteArray(final LogEvent event) { 297 return getBytes(toSerializable(event)); 298 } 299 300}