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