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.nio.charset.Charset;
020import java.nio.charset.StandardCharsets;
021
022import org.apache.logging.log4j.Level;
023import org.apache.logging.log4j.LogManager;
024import org.apache.logging.log4j.core.LogEvent;
025import org.apache.logging.log4j.core.StringLayout;
026import org.apache.logging.log4j.core.config.AbstractConfiguration;
027import org.apache.logging.log4j.core.config.Configuration;
028import org.apache.logging.log4j.core.config.LoggerConfig;
029import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
030import org.apache.logging.log4j.core.config.plugins.PluginElement;
031import org.apache.logging.log4j.core.impl.DefaultLogEventFactory;
032import org.apache.logging.log4j.core.impl.LocationAware;
033import org.apache.logging.log4j.core.util.Constants;
034import org.apache.logging.log4j.core.util.StringEncoder;
035import org.apache.logging.log4j.spi.AbstractLogger;
036import org.apache.logging.log4j.util.PropertiesUtil;
037import org.apache.logging.log4j.util.StringBuilders;
038import org.apache.logging.log4j.util.Strings;
039
040/**
041 * Abstract base class for Layouts that result in a String.
042 * <p>
043 * Since 2.4.1, this class has custom logic to convert ISO-8859-1 or US-ASCII Strings to byte[] arrays to improve
044 * performance: all characters are simply cast to bytes.
045 * </p>
046 */
047public abstract class AbstractStringLayout extends AbstractLayout<String> implements StringLayout, LocationAware {
048
049    public abstract static class Builder<B extends Builder<B>> extends AbstractLayout.Builder<B> {
050
051        @PluginBuilderAttribute(value = "charset")
052        private Charset charset;
053
054        @PluginElement("footerSerializer")
055        private Serializer footerSerializer;
056
057        @PluginElement("headerSerializer")
058        private Serializer headerSerializer;
059
060        public Charset getCharset() {
061            return charset;
062        }
063
064        public Serializer getFooterSerializer() {
065            return footerSerializer;
066        }
067
068        public Serializer getHeaderSerializer() {
069            return headerSerializer;
070        }
071
072        public B setCharset(final Charset charset) {
073            this.charset = charset;
074            return asBuilder();
075        }
076
077        public B setFooterSerializer(final Serializer footerSerializer) {
078            this.footerSerializer = footerSerializer;
079            return asBuilder();
080        }
081
082        public B setHeaderSerializer(final Serializer headerSerializer) {
083            this.headerSerializer = headerSerializer;
084            return asBuilder();
085        }
086
087    }
088
089    @Override
090    public boolean requiresLocation() {
091        return false;
092    }
093
094    public interface Serializer extends Serializer2 {
095        String toSerializable(final LogEvent event);
096
097        @Override
098        default StringBuilder toSerializable(final LogEvent event, final StringBuilder builder) {
099            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    private static int size(final String property, final int defaultValue) {
144        return PropertiesUtil.getProperties().getIntegerProperty(property, defaultValue);
145    }
146
147    protected static void trimToMaxSize(final StringBuilder stringBuilder) {
148        StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE);
149    }
150
151    private Encoder<StringBuilder> textEncoder;
152    /**
153     * The charset for the formatted message.
154     */
155    private final Charset charset;
156
157    private final Serializer footerSerializer;
158
159    private final Serializer headerSerializer;
160
161    protected AbstractStringLayout(final Charset charset) {
162        this(charset, (byte[]) null, (byte[]) null);
163    }
164
165    /**
166     * Builds a new layout.
167     * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be
168     *      converted from strings to bytes.
169     * @param header the header bytes
170     * @param footer the footer bytes
171     */
172    protected AbstractStringLayout(final Charset aCharset, final byte[] header, final byte[] footer) {
173        super(null, header, footer);
174        this.headerSerializer = null;
175        this.footerSerializer = null;
176        this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset;
177        textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null;
178    }
179
180    /**
181     * Builds a new layout.
182     * @param config the configuration. May be null.
183     * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be
184     *      converted from strings to bytes.
185     * @param headerSerializer the header bytes serializer
186     * @param footerSerializer the footer bytes serializer
187     */
188    protected AbstractStringLayout(final Configuration config, final Charset aCharset,
189            final Serializer headerSerializer, final Serializer footerSerializer) {
190        super(config, null, null);
191        this.headerSerializer = headerSerializer;
192        this.footerSerializer = footerSerializer;
193        this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset;
194        textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null;
195    }
196
197    protected byte[] getBytes(final String s) {
198        return s.getBytes(charset);
199    }
200
201    @Override
202    public Charset getCharset() {
203        return charset;
204    }
205
206    /**
207     * @return The default content type for Strings.
208     */
209    @Override
210    public String getContentType() {
211        return "text/plain";
212    }
213
214    /**
215     * Returns the footer, if one is available.
216     *
217     * @return A byte array containing the footer.
218     */
219    @Override
220    public byte[] getFooter() {
221        return serializeToBytes(footerSerializer, super.getFooter());
222    }
223
224    public Serializer getFooterSerializer() {
225        return footerSerializer;
226    }
227
228    /**
229     * Returns the header, if one is available.
230     *
231     * @return A byte array containing the header.
232     */
233    @Override
234    public byte[] getHeader() {
235        return serializeToBytes(headerSerializer, super.getHeader());
236    }
237
238    public Serializer getHeaderSerializer() {
239        return headerSerializer;
240    }
241
242    private DefaultLogEventFactory getLogEventFactory() {
243        return DefaultLogEventFactory.getInstance();
244    }
245
246    /**
247     * Returns a {@code Encoder<StringBuilder>} that this Layout implementation can use for encoding log events.
248     *
249     * @return a {@code Encoder<StringBuilder>}
250     */
251    protected Encoder<StringBuilder> getStringBuilderEncoder() {
252        if (textEncoder == null) {
253            textEncoder = new StringBuilderEncoder(getCharset());
254        }
255        return textEncoder;
256    }
257
258    protected byte[] serializeToBytes(final Serializer serializer, final byte[] defaultValue) {
259        final String serializable = serializeToString(serializer);
260        if (serializable == null) {
261            return defaultValue;
262        }
263        return StringEncoder.toBytes(serializable, getCharset());
264    }
265
266    protected String serializeToString(final Serializer serializer) {
267        if (serializer == null) {
268            return null;
269        }
270        final String loggerName;
271        final Level level;
272        if (configuration != null) {
273            final LoggerConfig rootLogger = configuration.getRootLogger();
274            loggerName = rootLogger.getName();
275            level = rootLogger.getLevel();
276        } else {
277            loggerName = LogManager.ROOT_LOGGER_NAME;
278            level = AbstractConfiguration.getDefaultLevel();
279        }
280        // Using "" for the FQCN, does it matter?
281        final LogEvent logEvent = getLogEventFactory().createEvent(loggerName, null, Strings.EMPTY,
282                level, null, null, null);
283        return serializer.toSerializable(logEvent);
284    }
285
286    /**
287     * Formats the Log Event as a byte array.
288     *
289     * @param event The Log Event.
290     * @return The formatted event as a byte array.
291     */
292    @Override
293    public byte[] toByteArray(final LogEvent event) {
294        return getBytes(toSerializable(event));
295    }
296
297}