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.IOException; 020import java.io.ObjectInputStream; 021import java.io.ObjectOutputStream; 022import java.io.UnsupportedEncodingException; 023import java.nio.charset.Charset; 024import java.nio.charset.StandardCharsets; 025 026import org.apache.logging.log4j.core.LogEvent; 027import org.apache.logging.log4j.core.StringLayout; 028import org.apache.logging.log4j.core.util.StringEncoder; 029 030/** 031 * Abstract base class for Layouts that result in a String. 032 * <p> 033 * Since 2.4.1, this class has custom logic to convert ISO-8859-1 or US-ASCII Strings to byte[] arrays to improve 034 * performance: all characters are simply cast to bytes. 035 */ 036/* 037 * Implementation note: prefer String.getBytes(String) to String.getBytes(Charset) for performance reasons. See 038 * https://issues.apache.org/jira/browse/LOG4J2-935 for details. 039 */ 040public abstract class AbstractStringLayout extends AbstractLayout<String> implements StringLayout { 041 042 /** 043 * Default length for new StringBuilder instances: {@value} . 044 */ 045 protected static final int DEFAULT_STRING_BUILDER_SIZE = 1024; 046 047 private final static ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>(); 048 049 private static final long serialVersionUID = 1L; 050 051 /** 052 * The charset for the formatted message. 053 */ 054 // LOG4J2-1099: charset cannot be final due to serialization needs, so we serialize as charset name instead 055 private transient Charset charset; 056 private final String charsetName; 057 private final boolean useCustomEncoding; 058 059 protected AbstractStringLayout(final Charset charset) { 060 this(charset, null, null); 061 } 062 063 /** 064 * Builds a new layout. 065 * @param charset the charset used to encode the header bytes, footer bytes and anything else that needs to be 066 * converted from strings to bytes. 067 * @param header the header bytes 068 * @param footer the footer bytes 069 */ 070 protected AbstractStringLayout(final Charset charset, final byte[] header, final byte[] footer) { 071 super(header, footer); 072 this.charset = charset == null ? StandardCharsets.UTF_8 : charset; 073 this.charsetName = this.charset.name(); 074 useCustomEncoding = isPreJava8() 075 && (StandardCharsets.ISO_8859_1.equals(charset) || StandardCharsets.US_ASCII.equals(charset)); 076 } 077 078 // LOG4J2-1151: If the built-in JDK 8 encoders are available we should use them. 079 private static boolean isPreJava8() { 080 final String version = System.getProperty("java.version"); 081 final String[] parts = version.split("\\."); 082 try { 083 int major = Integer.parseInt(parts[1]); 084 return major < 8; 085 } catch (Exception ex) { 086 return true; 087 } 088 } 089 090 private void writeObject(final ObjectOutputStream out) throws IOException { 091 out.defaultWriteObject(); 092 out.writeUTF(charset.name()); 093 } 094 095 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 096 in.defaultReadObject(); 097 final String csName = in.readUTF(); 098 charset = Charset.forName(csName); 099 } 100 101 /** 102 * Returns a {@code StringBuilder} that this Layout implementation can use to write the formatted log event to. 103 * 104 * @return a {@code StringBuilder} 105 */ 106 protected StringBuilder getStringBuilder() { 107 StringBuilder result = threadLocal.get(); 108 if (result == null) { 109 result = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE); 110 threadLocal.set(result); 111 } 112 result.setLength(0); 113 return result; 114 } 115 116 protected byte[] getBytes(final String s) { 117 if (useCustomEncoding) { // rely on branch prediction to eliminate this check if false 118 return StringEncoder.encodeSingleByteChars(s); 119 } 120 try { // LOG4J2-935: String.getBytes(String) gives better performance 121 return s.getBytes(charsetName); 122 } catch (UnsupportedEncodingException e) { 123 return s.getBytes(charset); 124 } 125 } 126 127 @Override 128 public Charset getCharset() { 129 return charset; 130 } 131 132 /** 133 * @return The default content type for Strings. 134 */ 135 @Override 136 public String getContentType() { 137 return "text/plain"; 138 } 139 140 /** 141 * Formats the Log Event as a byte array. 142 * 143 * @param event The Log Event. 144 * @return The formatted event as a byte array. 145 */ 146 @Override 147 public byte[] toByteArray(final LogEvent event) { 148 return getBytes(toSerializable(event)); 149 } 150 151}