| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache license, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the license for the specific language governing permissions and |
| * limitations under the license. |
| */ |
| package org.apache.logging.log4j.core.layout; |
| |
| import java.io.UnsupportedEncodingException; |
| import java.nio.charset.Charset; |
| import java.nio.charset.StandardCharsets; |
| |
| import org.apache.logging.log4j.core.LogEvent; |
| import org.apache.logging.log4j.core.StringLayout; |
| import org.apache.logging.log4j.core.config.Configuration; |
| import org.apache.logging.log4j.core.config.LoggerConfig; |
| import org.apache.logging.log4j.plugins.PluginBuilderAttribute; |
| import org.apache.logging.log4j.plugins.PluginElement; |
| import org.apache.logging.log4j.core.impl.DefaultLogEventFactory; |
| import org.apache.logging.log4j.core.util.Constants; |
| import org.apache.logging.log4j.core.util.StringEncoder; |
| import org.apache.logging.log4j.spi.AbstractLogger; |
| import org.apache.logging.log4j.util.PropertiesUtil; |
| import org.apache.logging.log4j.util.StringBuilders; |
| import org.apache.logging.log4j.util.Strings; |
| |
| /** |
| * Abstract base class for Layouts that result in a String. |
| * <p> |
| * Since 2.4.1, this class has custom logic to convert ISO-8859-1 or US-ASCII Strings to byte[] arrays to improve |
| * performance: all characters are simply cast to bytes. |
| * </p> |
| */ |
| /* |
| * Implementation note: prefer String.getBytes(String) to String.getBytes(Charset) for performance reasons. See |
| * https://issues.apache.org/jira/browse/LOG4J2-935 for details. |
| */ |
| public abstract class AbstractStringLayout extends AbstractLayout<String> implements StringLayout { |
| |
| public abstract static class Builder<B extends Builder<B>> extends AbstractLayout.Builder<B> { |
| |
| @PluginBuilderAttribute(value = "charset") |
| private Charset charset; |
| |
| @PluginElement("footerSerializer") |
| private Serializer footerSerializer; |
| |
| @PluginElement("headerSerializer") |
| private Serializer headerSerializer; |
| |
| public Charset getCharset() { |
| return charset; |
| } |
| |
| public Serializer getFooterSerializer() { |
| return footerSerializer; |
| } |
| |
| public Serializer getHeaderSerializer() { |
| return headerSerializer; |
| } |
| |
| public B setCharset(final Charset charset) { |
| this.charset = charset; |
| return asBuilder(); |
| } |
| |
| public B setFooterSerializer(final Serializer footerSerializer) { |
| this.footerSerializer = footerSerializer; |
| return asBuilder(); |
| } |
| |
| public B setHeaderSerializer(final Serializer headerSerializer) { |
| this.headerSerializer = headerSerializer; |
| return asBuilder(); |
| } |
| |
| } |
| |
| public interface Serializer { |
| String toSerializable(final LogEvent event); |
| |
| default boolean requiresLocation() { |
| return false; |
| } |
| |
| default StringBuilder toSerializable(final LogEvent event, final StringBuilder builder) { |
| builder.append(toSerializable(event)); |
| return builder; |
| } |
| } |
| |
| /** |
| * Variation of {@link Serializer} that avoids allocating temporary objects. |
| * As of 2.13 this interface was merged into the Serializer interface. |
| * @since 2.6 |
| */ |
| public interface Serializer2 { |
| StringBuilder toSerializable(final LogEvent event, final StringBuilder builder); |
| } |
| |
| /** |
| * Default length for new StringBuilder instances: {@value} . |
| */ |
| protected static final int DEFAULT_STRING_BUILDER_SIZE = 1024; |
| |
| protected static final int MAX_STRING_BUILDER_SIZE = Math.max(DEFAULT_STRING_BUILDER_SIZE, |
| size("log4j.layoutStringBuilder.maxSize", 2 * 1024)); |
| |
| private static final ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>(); |
| |
| /** |
| * Returns a {@code StringBuilder} that this Layout implementation can use to write the formatted log event to. |
| * |
| * @return a {@code StringBuilder} |
| */ |
| protected static StringBuilder getStringBuilder() { |
| if (AbstractLogger.getRecursionDepth() > 1) { // LOG4J2-2368 |
| // Recursive logging may clobber the cached StringBuilder. |
| return new StringBuilder(DEFAULT_STRING_BUILDER_SIZE); |
| } |
| StringBuilder result = threadLocal.get(); |
| if (result == null) { |
| result = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE); |
| threadLocal.set(result); |
| } |
| trimToMaxSize(result); |
| result.setLength(0); |
| return result; |
| } |
| |
| // LOG4J2-1151: If the built-in JDK 8 encoders are available we should use them. |
| private static boolean isPreJava8() { |
| return org.apache.logging.log4j.util.Constants.JAVA_MAJOR_VERSION < 8; |
| } |
| |
| private static int size(final String property, final int defaultValue) { |
| return PropertiesUtil.getProperties().getIntegerProperty(property, defaultValue); |
| } |
| |
| protected static void trimToMaxSize(final StringBuilder stringBuilder) { |
| StringBuilders.trimToMaxSize(stringBuilder, MAX_STRING_BUILDER_SIZE); |
| } |
| |
| private Encoder<StringBuilder> textEncoder; |
| /** |
| * The charset for the formatted message. |
| */ |
| // LOG4J2-1099: Charset cannot be final due to serialization needs, so we serialize as Charset name instead |
| private transient Charset charset; |
| |
| private final String charsetName; |
| |
| private final Serializer footerSerializer; |
| |
| private final Serializer headerSerializer; |
| |
| private final boolean useCustomEncoding; |
| |
| protected AbstractStringLayout(final Charset charset) { |
| this(charset, (byte[]) null, (byte[]) null); |
| } |
| |
| /** |
| * Builds a new layout. |
| * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be |
| * converted from strings to bytes. |
| * @param header the header bytes |
| * @param footer the footer bytes |
| */ |
| protected AbstractStringLayout(final Charset aCharset, final byte[] header, final byte[] footer) { |
| super(null, header, footer); |
| this.headerSerializer = null; |
| this.footerSerializer = null; |
| this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; |
| this.charsetName = this.charset.name(); |
| useCustomEncoding = isPreJava8() |
| && (StandardCharsets.ISO_8859_1.equals(aCharset) || StandardCharsets.US_ASCII.equals(aCharset)); |
| textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; |
| } |
| |
| /** |
| * Builds a new layout. |
| * @param config the configuration |
| * @param aCharset the charset used to encode the header bytes, footer bytes and anything else that needs to be |
| * converted from strings to bytes. |
| * @param headerSerializer the header bytes serializer |
| * @param footerSerializer the footer bytes serializer |
| */ |
| protected AbstractStringLayout(final Configuration config, final Charset aCharset, |
| final Serializer headerSerializer, final Serializer footerSerializer) { |
| super(config, null, null); |
| this.headerSerializer = headerSerializer; |
| this.footerSerializer = footerSerializer; |
| this.charset = aCharset == null ? StandardCharsets.UTF_8 : aCharset; |
| this.charsetName = this.charset.name(); |
| useCustomEncoding = isPreJava8() |
| && (StandardCharsets.ISO_8859_1.equals(aCharset) || StandardCharsets.US_ASCII.equals(aCharset)); |
| textEncoder = Constants.ENABLE_DIRECT_ENCODERS ? new StringBuilderEncoder(charset) : null; |
| } |
| |
| protected byte[] getBytes(final String s) { |
| if (useCustomEncoding) { // rely on branch prediction to eliminate this check if false |
| return StringEncoder.encodeSingleByteChars(s); |
| } |
| try { // LOG4J2-935: String.getBytes(String) gives better performance |
| return s.getBytes(charsetName); |
| } catch (final UnsupportedEncodingException e) { |
| return s.getBytes(charset); |
| } |
| } |
| |
| @Override |
| public Charset getCharset() { |
| return charset; |
| } |
| |
| /** |
| * @return The default content type for Strings. |
| */ |
| @Override |
| public String getContentType() { |
| return "text/plain"; |
| } |
| |
| /** |
| * Returns the footer, if one is available. |
| * |
| * @return A byte array containing the footer. |
| */ |
| @Override |
| public byte[] getFooter() { |
| return serializeToBytes(footerSerializer, super.getFooter()); |
| } |
| |
| public Serializer getFooterSerializer() { |
| return footerSerializer; |
| } |
| |
| /** |
| * Returns the header, if one is available. |
| * |
| * @return A byte array containing the header. |
| */ |
| @Override |
| public byte[] getHeader() { |
| return serializeToBytes(headerSerializer, super.getHeader()); |
| } |
| |
| public Serializer getHeaderSerializer() { |
| return headerSerializer; |
| } |
| |
| private DefaultLogEventFactory getLogEventFactory() { |
| return DefaultLogEventFactory.getInstance(); |
| } |
| |
| /** |
| * Returns a {@code Encoder<StringBuilder>} that this Layout implementation can use for encoding log events. |
| * |
| * @return a {@code Encoder<StringBuilder>} |
| */ |
| protected Encoder<StringBuilder> getStringBuilderEncoder() { |
| if (textEncoder == null) { |
| textEncoder = new StringBuilderEncoder(getCharset()); |
| } |
| return textEncoder; |
| } |
| |
| protected byte[] serializeToBytes(final Serializer serializer, final byte[] defaultValue) { |
| final String serializable = serializeToString(serializer); |
| if (serializer == null) { |
| return defaultValue; |
| } |
| return StringEncoder.toBytes(serializable, getCharset()); |
| } |
| |
| protected String serializeToString(final Serializer serializer) { |
| if (serializer == null) { |
| return null; |
| } |
| final LoggerConfig rootLogger = getConfiguration().getRootLogger(); |
| // Using "" for the FQCN, does it matter? |
| final LogEvent logEvent = getLogEventFactory().createEvent(rootLogger.getName(), null, Strings.EMPTY, |
| rootLogger.getLevel(), null, null, null); |
| return serializer.toSerializable(logEvent); |
| } |
| |
| /** |
| * Formats the Log Event as a byte array. |
| * |
| * @param event The Log Event. |
| * @return The formatted event as a byte array. |
| */ |
| @Override |
| public byte[] toByteArray(final LogEvent event) { |
| return getBytes(toSerializable(event)); |
| } |
| |
| } |