View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.layout;
18  
19  import java.io.IOException;
20  import java.io.ObjectInputStream;
21  import java.io.ObjectOutputStream;
22  import java.io.UnsupportedEncodingException;
23  import java.nio.charset.Charset;
24  import java.nio.charset.StandardCharsets;
25  
26  import org.apache.logging.log4j.core.LogEvent;
27  import org.apache.logging.log4j.core.StringLayout;
28  import org.apache.logging.log4j.core.util.StringEncoder;
29  
30  /**
31   * Abstract base class for Layouts that result in a String.
32   * <p>
33   * Since 2.4.1, this class has custom logic to convert ISO-8859-1 or US-ASCII Strings to byte[] arrays to improve
34   * performance: all characters are simply cast to bytes.
35   */
36  /*
37   * Implementation note: prefer String.getBytes(String) to String.getBytes(Charset) for performance reasons. See
38   * https://issues.apache.org/jira/browse/LOG4J2-935 for details.
39   */
40  public abstract class AbstractStringLayout extends AbstractLayout<String> implements StringLayout {
41  
42      /**
43       * Default length for new StringBuilder instances: {@value} .
44       */
45      protected static final int DEFAULT_STRING_BUILDER_SIZE = 1024;
46  
47      private final static ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>();
48  
49      private static final long serialVersionUID = 1L;
50  
51      /**
52       * The charset for the formatted message.
53       */
54      // LOG4J2-1099: charset cannot be final due to serialization needs, so we serialize as charset name instead
55      private transient Charset charset;
56      private final String charsetName;
57      private final boolean useCustomEncoding;
58  
59      protected AbstractStringLayout(final Charset charset) {
60          this(charset, null, null);
61      }
62  
63      /**
64       * Builds a new layout.
65       * @param charset the charset used to encode the header bytes, footer bytes and anything else that needs to be 
66       *      converted from strings to bytes.
67       * @param header the header bytes
68       * @param footer the footer bytes
69       */
70      protected AbstractStringLayout(final Charset charset, final byte[] header, final byte[] footer) {
71          super(header, footer);
72          this.charset = charset == null ? StandardCharsets.UTF_8 : charset;
73          this.charsetName = this.charset.name();
74          useCustomEncoding = isPreJava8()
75                  && (StandardCharsets.ISO_8859_1.equals(charset) || StandardCharsets.US_ASCII.equals(charset));
76      }
77  
78      // LOG4J2-1151: If the built-in JDK 8 encoders are available we should use them.
79      private static boolean isPreJava8() {
80          final String version = System.getProperty("java.version");
81          final String[] parts = version.split("\\.");
82          try {
83              int major = Integer.parseInt(parts[1]);
84              return major < 8;
85          } catch (Exception ex) {
86              return true;
87          }
88      }
89  
90      private void writeObject(final ObjectOutputStream out) throws IOException {
91          out.defaultWriteObject();
92          out.writeUTF(charset.name());
93      }
94  
95      private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
96          in.defaultReadObject();
97          final String csName = in.readUTF();
98          charset = Charset.forName(csName);
99      }
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 }