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.UnsupportedEncodingException;
020import java.nio.charset.Charset;
021import java.nio.charset.StandardCharsets;
022
023import org.apache.logging.log4j.core.LogEvent;
024import org.apache.logging.log4j.core.StringLayout;
025import org.apache.logging.log4j.core.config.Configuration;
026import org.apache.logging.log4j.core.config.LoggerConfig;
027import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
028import org.apache.logging.log4j.core.config.plugins.PluginElement;
029import org.apache.logging.log4j.core.impl.DefaultLogEventFactory;
030import org.apache.logging.log4j.core.impl.LocationAware;
031import org.apache.logging.log4j.core.util.Constants;
032import org.apache.logging.log4j.core.util.StringEncoder;
033import org.apache.logging.log4j.spi.AbstractLogger;
034import org.apache.logging.log4j.util.PropertiesUtil;
035import org.apache.logging.log4j.util.StringBuilders;
036import org.apache.logging.log4j.util.Strings;
037
038/**
039 * Abstract base class for Layouts that result in a String.
040 * <p>
041 * Since 2.4.1, this class has custom logic to convert ISO-8859-1 or US-ASCII Strings to byte[] arrays to improve
042 * performance: all characters are simply cast to bytes.
043 * </p>
044 */
045/*
046 * Implementation note: prefer String.getBytes(String) to String.getBytes(Charset) for performance reasons. See
047 * https://issues.apache.org/jira/browse/LOG4J2-935 for details.
048 */
049public abstract class AbstractStringLayout extends AbstractLayout<String> implements StringLayout, LocationAware {
050
051    public abstract static class Builder<B extends Builder<B>> extends AbstractLayout.Builder<B> {
052
053        @PluginBuilderAttribute(value = "charset")
054        private Charset charset;
055
056        @PluginElement("footerSerializer")
057        private Serializer footerSerializer;
058
059        @PluginElement("headerSerializer")
060        private Serializer headerSerializer;
061
062        public Charset getCharset() {
063            return charset;
064        }
065
066        public Serializer getFooterSerializer() {
067            return footerSerializer;
068        }
069
070        public Serializer getHeaderSerializer() {
071            return headerSerializer;
072        }
073
074        public B setCharset(final Charset charset) {
075            this.charset = charset;
076            return asBuilder();
077        }
078
079        public B setFooterSerializer(final Serializer footerSerializer) {
080            this.footerSerializer = footerSerializer;
081            return asBuilder();
082        }
083
084        public B setHeaderSerializer(final Serializer headerSerializer) {
085            this.headerSerializer = headerSerializer;
086            return asBuilder();
087        }
088
089    }
090
091    @Override
092    public boolean requiresLocation() {
093        return false;
094    }
095
096    public interface Serializer {
097        String toSerializable(final LogEvent event);
098
099        default StringBuilder toSerializable(final LogEvent event, final StringBuilder builder) {
100            builder.append(toSerializable(event));
101            return builder;
102        }
103    }
104
105    /**
106     * Variation of {@link Serializer} that avoids allocating temporary objects.
107     * As of 2.13 this interface was merged into the Serializer interface.
108     * @since 2.6
109     */
110    public interface Serializer2 {
111        StringBuilder toSerializable(final LogEvent event, final StringBuilder builder);
112    }
113
114    /**
115     * Default length for new StringBuilder instances: {@value} .
116     */
117    protected static final int DEFAULT_STRING_BUILDER_SIZE = 1024;
118
119    protected static final int MAX_STRING_BUILDER_SIZE = Math.max(DEFAULT_STRING_BUILDER_SIZE,
120            size("log4j.layoutStringBuilder.maxSize", 2 * 1024));
121
122    private static final ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>();
123
124    /**
125     * Returns a {@code StringBuilder} that this Layout implementation can use to write the formatted log event to.
126     *
127     * @return a {@code StringBuilder}
128     */
129    protected static StringBuilder getStringBuilder() {
130        if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-2368
131            // Recursive logging may clobber the cached StringBuilder.
132            return new StringBuilder(DEFAULT_STRING_BUILDER_SIZE);
133        }
134        StringBuilder result = threadLocal.get();
135        if (result == null) {
136            result = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE);
137            threadLocal.set(result);
138        }
139        trimToMaxSize(result);
140        result.setLength(0);
141        return result;
142    }
143
144    // LOG4J2-1151: If the built-in JDK 8 encoders are available we should use them.
145    private static boolean isPreJava8() {
146        return org.apache.logging.log4j.util.Constants.JAVA_MAJOR_VERSION < 8;
147    }
148
149    private static int size(final String property, final int defaultValue) {
150        return PropertiesUtil.getProperties().getIntegerProperty(property, defaultValue);
151    }
152
153    protected static void trimToMaxSize(final StringBuilder stringBuilder) {
154        StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE);
155    }
156
157    private Encoder<StringBuilder> textEncoder;
158    /**
159     * The charset for the formatted message.
160     */
161    // LOG4J2-1099: Charset cannot be final due to serialization needs, so we serialize as Charset name instead
162    private transient Charset charset;
163
164    private final String charsetName;
165
166    private final Serializer footerSerializer;
167
168    private final Serializer headerSerializer;
169
170    private final boolean useCustomEncoding;
171
172    protected AbstractStringLayout(final Charset charset) {
173        this(charset, (byte[]) null, (byte[]) null);
174    }
175
176    /**
177     * Builds a new layout.
178     * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be
179     *      converted from strings to bytes.
180     * @param header the header bytes
181     * @param footer the footer bytes
182     */
183    protected AbstractStringLayout(final Charset aCharset, final byte[] header, final byte[] footer) {
184        super(null, header, footer);
185        this.headerSerializer = null;
186        this.footerSerializer = null;
187        this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset;
188        this.charsetName = this.charset.name();
189        useCustomEncoding = isPreJava8()
190                && (StandardCharsets.ISO_8859_1.equals(aCharset) || StandardCharsets.US_ASCII.equals(aCharset));
191        textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null;
192    }
193
194    /**
195     * Builds a new layout.
196     * @param config the configuration
197     * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be
198     *      converted from strings to bytes.
199     * @param headerSerializer the header bytes serializer
200     * @param footerSerializer the footer bytes serializer
201     */
202    protected AbstractStringLayout(final Configuration config, final Charset aCharset,
203            final Serializer headerSerializer, final Serializer footerSerializer) {
204        super(config, null, null);
205        this.headerSerializer = headerSerializer;
206        this.footerSerializer = footerSerializer;
207        this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset;
208        this.charsetName = this.charset.name();
209        useCustomEncoding = isPreJava8()
210                && (StandardCharsets.ISO_8859_1.equals(aCharset) || StandardCharsets.US_ASCII.equals(aCharset));
211        textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null;
212    }
213
214    protected byte[] getBytes(final String s) {
215        if (useCustomEncoding) { // rely on branch prediction to eliminate this check if false
216            return StringEncoder.encodeSingleByteChars(s);
217        }
218        try { // LOG4J2-935: String.getBytes(String) gives better performance
219            return s.getBytes(charsetName);
220        } catch (final UnsupportedEncodingException e) {
221            return s.getBytes(charset);
222        }
223    }
224
225    @Override
226    public Charset getCharset() {
227        return charset;
228    }
229
230    /**
231     * @return The default content type for Strings.
232     */
233    @Override
234    public String getContentType() {
235        return "text/plain";
236    }
237
238    /**
239     * Returns the footer, if one is available.
240     *
241     * @return A byte array containing the footer.
242     */
243    @Override
244    public byte[] getFooter() {
245        return serializeToBytes(footerSerializer, super.getFooter());
246    }
247
248    public Serializer getFooterSerializer() {
249        return footerSerializer;
250    }
251
252    /**
253     * Returns the header, if one is available.
254     *
255     * @return A byte array containing the header.
256     */
257    @Override
258    public byte[] getHeader() {
259        return serializeToBytes(headerSerializer, super.getHeader());
260    }
261
262    public Serializer getHeaderSerializer() {
263        return headerSerializer;
264    }
265
266    private DefaultLogEventFactory getLogEventFactory() {
267        return DefaultLogEventFactory.getInstance();
268    }
269
270    /**
271     * Returns a {@code Encoder<StringBuilder>} that this Layout implementation can use for encoding log events.
272     *
273     * @return a {@code Encoder<StringBuilder>}
274     */
275    protected Encoder<StringBuilder> getStringBuilderEncoder() {
276        if (textEncoder == null) {
277            textEncoder = new StringBuilderEncoder(getCharset());
278        }
279        return textEncoder;
280    }
281
282    protected byte[] serializeToBytes(final Serializer serializer, final byte[] defaultValue) {
283        final String serializable = serializeToString(serializer);
284        if (serializable == null) {
285            return defaultValue;
286        }
287        return StringEncoder.toBytes(serializable, getCharset());
288    }
289
290    protected String serializeToString(final Serializer serializer) {
291        if (serializer == null) {
292            return null;
293        }
294        final LoggerConfig rootLogger = getConfiguration().getRootLogger();
295        // Using "" for the FQCN, does it matter?
296        final LogEvent logEvent = getLogEventFactory().createEvent(rootLogger.getName(), null, Strings.EMPTY,
297                rootLogger.getLevel(), null, null, null);
298        return serializer.toSerializable(logEvent);
299    }
300
301    /**
302     * Formats the Log Event as a byte array.
303     *
304     * @param event The Log Event.
305     * @return The formatted event as a byte array.
306     */
307    @Override
308    public byte[] toByteArray(final LogEvent event) {
309        return getBytes(toSerializable(event));
310    }
311
312}