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