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