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