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.UnsupportedEncodingException;
20  import java.nio.charset.Charset;
21  import java.nio.charset.StandardCharsets;
22  
23  import org.apache.logging.log4j.core.LogEvent;
24  import org.apache.logging.log4j.core.StringLayout;
25  import org.apache.logging.log4j.core.config.Configuration;
26  import org.apache.logging.log4j.core.config.LoggerConfig;
27  import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
28  import org.apache.logging.log4j.core.config.plugins.PluginElement;
29  import org.apache.logging.log4j.core.impl.DefaultLogEventFactory;
30  import org.apache.logging.log4j.core.util.Constants;
31  import org.apache.logging.log4j.core.util.StringEncoder;
32  import org.apache.logging.log4j.spi.AbstractLogger;
33  import org.apache.logging.log4j.util.PropertiesUtil;
34  import org.apache.logging.log4j.util.StringBuilders;
35  import org.apache.logging.log4j.util.Strings;
36  
37  /**
38   * Abstract base class for Layouts that result in a String.
39   * <p>
40   * Since 2.4.1, this class has custom logic to convert ISO-8859-1 or US-ASCII Strings to byte[] arrays to improve
41   * performance: all characters are simply cast to bytes.
42   * </p>
43   */
44  /*
45   * Implementation note: prefer String.getBytes(String) to String.getBytes(Charset) for performance reasons. See
46   * https://issues.apache.org/jira/browse/LOG4J2-935 for details.
47   */
48  public abstract class AbstractStringLayout extends AbstractLayout<String> implements StringLayout {
49  
50      public abstract static class Builder<B extends Builder<B>> extends AbstractLayout.Builder<B> {
51  
52          @PluginBuilderAttribute(value = "charset")
53          private Charset charset;
54  
55          @PluginElement("footerSerializer")
56          private Serializer footerSerializer;
57  
58          @PluginElement("headerSerializer")
59          private Serializer headerSerializer;
60  
61          public Charset getCharset() {
62              return charset;
63          }
64  
65          public Serializer getFooterSerializer() {
66              return footerSerializer;
67          }
68  
69          public Serializer getHeaderSerializer() {
70              return headerSerializer;
71          }
72  
73          public B setCharset(final Charset charset) {
74              this.charset = charset;
75              return asBuilder();
76          }
77  
78          public B setFooterSerializer(final Serializer footerSerializer) {
79              this.footerSerializer = footerSerializer;
80              return asBuilder();
81          }
82  
83          public B setHeaderSerializer(final Serializer headerSerializer) {
84              this.headerSerializer = headerSerializer;
85              return asBuilder();
86          }
87  
88      }
89  
90      public interface Serializer {
91          String toSerializable(final LogEvent event);
92      }
93  
94      /**
95       * Variation of {@link Serializer} that avoids allocating temporary objects.
96       * @since 2.6
97       */
98      public interface Serializer2 {
99          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         final String version = System.getProperty("java.version");
135         final String[] parts = version.split("\\.");
136         try {
137             final int major = Integer.parseInt(parts[1]);
138             return major < 8;
139         } catch (final Exception ex) {
140             return true;
141         }
142     }
143 
144     private static int size(final String property, final int defaultValue) {
145         return PropertiesUtil.getProperties().getIntegerProperty(property, defaultValue);
146     }
147 
148     protected static void trimToMaxSize(final StringBuilder stringBuilder) {
149         StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE);
150     }
151 
152     private Encoder<StringBuilder> textEncoder;
153     /**
154      * The charset for the formatted message.
155      */
156     // LOG4J2-1099: Charset cannot be final due to serialization needs, so we serialize as Charset name instead
157     private transient Charset charset;
158 
159     private final String charsetName;
160 
161     private final Serializer footerSerializer;
162 
163     private final Serializer headerSerializer;
164 
165     private final boolean useCustomEncoding;
166 
167     protected AbstractStringLayout(final Charset charset) {
168         this(charset, (byte[]) null, (byte[]) null);
169     }
170 
171     /**
172      * Builds a new layout.
173      * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be
174      *      converted from strings to bytes.
175      * @param header the header bytes
176      * @param footer the footer bytes
177      */
178     protected AbstractStringLayout(final Charset aCharset, final byte[] header, final byte[] footer) {
179         super(null, header, footer);
180         this.headerSerializer = null;
181         this.footerSerializer = null;
182         this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset;
183         this.charsetName = this.charset.name();
184         useCustomEncoding = isPreJava8()
185                 && (StandardCharsets.ISO_8859_1.equals(aCharset) || StandardCharsets.US_ASCII.equals(aCharset));
186         textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null;
187     }
188 
189     /**
190      * Builds a new layout.
191      * @param config the configuration
192      * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be
193      *      converted from strings to bytes.
194      * @param headerSerializer the header bytes serializer
195      * @param footerSerializer the footer bytes serializer
196      */
197     protected AbstractStringLayout(final Configuration config, final Charset aCharset,
198             final Serializer headerSerializer, final Serializer footerSerializer) {
199         super(config, null, null);
200         this.headerSerializer = headerSerializer;
201         this.footerSerializer = footerSerializer;
202         this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset;
203         this.charsetName = this.charset.name();
204         useCustomEncoding = isPreJava8()
205                 && (StandardCharsets.ISO_8859_1.equals(aCharset) || StandardCharsets.US_ASCII.equals(aCharset));
206         textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null;
207     }
208 
209     protected byte[] getBytes(final String s) {
210         if (useCustomEncoding) { // rely on branch prediction to eliminate this check if false
211             return StringEncoder.encodeSingleByteChars(s);
212         }
213         try { // LOG4J2-935: String.getBytes(String) gives better performance
214             return s.getBytes(charsetName);
215         } catch (final UnsupportedEncodingException e) {
216             return s.getBytes(charset);
217         }
218     }
219 
220     @Override
221     public Charset getCharset() {
222         return charset;
223     }
224 
225     /**
226      * @return The default content type for Strings.
227      */
228     @Override
229     public String getContentType() {
230         return "text/plain";
231     }
232 
233     /**
234      * Returns the footer, if one is available.
235      *
236      * @return A byte array containing the footer.
237      */
238     @Override
239     public byte[] getFooter() {
240         return serializeToBytes(footerSerializer, super.getFooter());
241     }
242 
243     public Serializer getFooterSerializer() {
244         return footerSerializer;
245     }
246 
247     /**
248      * Returns the header, if one is available.
249      *
250      * @return A byte array containing the header.
251      */
252     @Override
253     public byte[] getHeader() {
254         return serializeToBytes(headerSerializer, super.getHeader());
255     }
256 
257     public Serializer getHeaderSerializer() {
258         return headerSerializer;
259     }
260 
261     private DefaultLogEventFactory getLogEventFactory() {
262         return DefaultLogEventFactory.getInstance();
263     }
264 
265     /**
266      * Returns a {@code Encoder<StringBuilder>} that this Layout implementation can use for encoding log events.
267      *
268      * @return a {@code Encoder<StringBuilder>}
269      */
270     protected Encoder<StringBuilder> getStringBuilderEncoder() {
271         if (textEncoder == null) {
272             textEncoder = new StringBuilderEncoder(getCharset());
273         }
274         return textEncoder;
275     }
276 
277     protected byte[] serializeToBytes(final Serializer serializer, final byte[] defaultValue) {
278         final String serializable = serializeToString(serializer);
279         if (serializer == null) {
280             return defaultValue;
281         }
282         return StringEncoder.toBytes(serializable, getCharset());
283     }
284 
285     protected String serializeToString(final Serializer serializer) {
286         if (serializer == null) {
287             return null;
288         }
289         final LoggerConfig rootLogger = getConfiguration().getRootLogger();
290         // Using "" for the FQCN, does it matter?
291         final LogEvent logEvent = getLogEventFactory().createEvent(rootLogger.getName(), null, Strings.EMPTY,
292                 rootLogger.getLevel(), null, null, null);
293         return serializer.toSerializable(logEvent);
294     }
295 
296     /**
297      * Formats the Log Event as a byte array.
298      *
299      * @param event The Log Event.
300      * @return The formatted event as a byte array.
301      */
302     @Override
303     public byte[] toByteArray(final LogEvent event) {
304         return getBytes(toSerializable(event));
305     }
306 
307 }