All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.logging.log4j.message.ParameterizedMessage Maven / Gradle / Ivy

There is a newer version: 6.1.4
Show newest version
/*
 * 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.message;

import static org.apache.logging.log4j.message.ParameterFormatter.analyzePattern;
import static org.apache.logging.log4j.util.StringBuilders.trimToMaxSize;

import com.google.errorprone.annotations.InlineMe;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Objects;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterFormatter.MessagePatternAnalysis;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.Constants;
import org.apache.logging.log4j.util.StringBuilderFormattable;
import org.apache.logging.log4j.util.internal.SerializationUtil;

/**
 * A {@link Message} accepting argument placeholders in the formatting pattern.
 * 

* Only {@literal "{}"} strings are treated as argument placeholders. * Escaped (i.e., {@code "\"}-prefixed) or incomplete argument placeholders will be ignored. * Examples of argument placeholders that will be discarded and rendered intact: *

*
 * { }
 * foo\{}
 * {bar
 * {buzz}
 * 
*/ public class ParameterizedMessage implements Message, StringBuilderFormattable { /** * Prefix for recursion. */ public static final String RECURSION_PREFIX = ParameterFormatter.RECURSION_PREFIX; /** * Suffix for recursion. */ public static final String RECURSION_SUFFIX = ParameterFormatter.RECURSION_SUFFIX; /** * Prefix for errors. */ public static final String ERROR_PREFIX = ParameterFormatter.ERROR_PREFIX; /** * Separator for errors. */ public static final String ERROR_SEPARATOR = ParameterFormatter.ERROR_SEPARATOR; /** * Separator for error messages. */ public static final String ERROR_MSG_SEPARATOR = ParameterFormatter.ERROR_MSG_SEPARATOR; /** * Suffix for errors. */ public static final String ERROR_SUFFIX = ParameterFormatter.ERROR_SUFFIX; private static final long serialVersionUID = -665975803997290697L; private static final Logger STATUS_LOGGER = StatusLogger.getLogger(); private static final ThreadLocal FORMAT_BUFFER_HOLDER_REF = Constants.ENABLE_THREADLOCALS ? ThreadLocal.withInitial(FormatBufferHolder::new) : null; private static final class FormatBufferHolder { private final StringBuilder buffer = new StringBuilder(Constants.MAX_REUSABLE_MESSAGE_SIZE); private boolean used = false; } private final String pattern; private transient Object[] args; private final transient Throwable throwable; private final MessagePatternAnalysis patternAnalysis; private String formattedMessage; /** * Constructs an instance. *

* The {@link Throwable} associated with the message (and returned in {@link #getThrowable()}) will be determined as follows: *

*
    *
  1. If a {@code throwable} argument is provided
  2. *
  3. If the last argument is a {@link Throwable} and is not referred to by any placeholder in the pattern
  4. *
* * @param pattern a formatting pattern * @param args arguments to be formatted * @param throwable a {@link Throwable} * @deprecated Use {@link #ParameterizedMessage(String, Object[], Throwable)} instead */ @InlineMe( replacement = "this(pattern, Arrays.stream(args).toArray(Object[]::new), throwable)", imports = "java.util.Arrays") @Deprecated public ParameterizedMessage(final String pattern, final String[] args, final Throwable throwable) { this(pattern, Arrays.stream(args).toArray(Object[]::new), throwable); } /** * Constructs an instance. *

* The {@link Throwable} associated with the message (and returned in {@link #getThrowable()}) will be determined as follows: *

*
    *
  1. If a {@code throwable} argument is provided
  2. *
  3. If the last argument is a {@link Throwable} and is not referred to by any placeholder in the pattern
  4. *
* * @param pattern a formatting pattern * @param args arguments to be formatted * @param throwable a {@link Throwable} */ public ParameterizedMessage(final String pattern, final Object[] args, final Throwable throwable) { this.args = args; this.pattern = pattern; this.patternAnalysis = analyzePattern(pattern, args != null ? args.length : 0); this.throwable = determineThrowable(throwable, this.args, patternAnalysis); } private static Throwable determineThrowable( final Throwable throwable, final Object[] args, final MessagePatternAnalysis analysis) { // Short-circuit if an explicit `Throwable` is provided if (throwable != null) { return throwable; } // If the last `Throwable` argument is not consumed in the pattern, use that if (args != null && args.length > analysis.placeholderCount) { Object lastArg = args[args.length - 1]; if (lastArg instanceof Throwable) { return (Throwable) lastArg; } } // No `Throwable`s available return null; } /** * Constructor with a pattern and multiple arguments. *

* If the last argument is a {@link Throwable} and is not referred to by any placeholder in the pattern, it is returned in {@link #getThrowable()}. *

* * @param pattern a formatting pattern * @param args arguments to be formatted */ public ParameterizedMessage(final String pattern, final Object... args) { this(pattern, args, null); } /** * Constructor with a pattern and a single argument. *

* If the argument is a {@link Throwable} and is not referred to by any placeholder in the pattern, it is returned in {@link #getThrowable()}. *

* * @param pattern a formatting pattern * @param arg an argument */ public ParameterizedMessage(final String pattern, final Object arg) { this(pattern, new Object[] {arg}); } /** * Constructor with a pattern and two arguments. *

* If the last argument is a {@link Throwable} and is not referred to by any placeholder in the pattern, it is returned in {@link #getThrowable()} and won't be contained in the formatted message. *

* * @param pattern a formatting pattern * @param arg0 the first argument * @param arg1 the second argument */ public ParameterizedMessage(final String pattern, final Object arg0, final Object arg1) { this(pattern, new Object[] {arg0, arg1}); } /** * @return the message formatting pattern */ @Override public String getFormat() { return pattern; } /** * @return the message arguments */ @Override public Object[] getParameters() { return args; } /** * The {@link Throwable} provided along with the message by one of the following means: *
    *
  1. explicitly in the constructor
  2. *
  3. as the last message argument that is not referred to by any placeholder in the formatting pattern
  4. *
* * @return the {@link Throwable} provided along with the message */ @Override public Throwable getThrowable() { return throwable; } /** * Returns the formatted message. *

* If possible, the result will be cached for subsequent invocations. *

* * @return the formatted message */ @Override public String getFormattedMessage() { if (formattedMessage == null) { final FormatBufferHolder bufferHolder; // If there isn't a format buffer to reuse if (FORMAT_BUFFER_HOLDER_REF == null || (bufferHolder = FORMAT_BUFFER_HOLDER_REF.get()).used) { final StringBuilder buffer = new StringBuilder(Constants.MAX_REUSABLE_MESSAGE_SIZE); formatTo(buffer); formattedMessage = buffer.toString(); } // If there is a format buffer to reuse else { bufferHolder.used = true; final StringBuilder buffer = bufferHolder.buffer; try { formatTo(buffer); formattedMessage = buffer.toString(); } finally { trimToMaxSize(buffer, Constants.MAX_REUSABLE_MESSAGE_SIZE); buffer.setLength(0); bufferHolder.used = false; } } } return formattedMessage; } @Override public void formatTo(final StringBuilder buffer) { if (formattedMessage != null) { buffer.append(formattedMessage); } else { final int argCount = args != null ? args.length : 0; try { ParameterFormatter.formatMessage(buffer, pattern, args, argCount, patternAnalysis); } catch (final Exception error) { STATUS_LOGGER.error("Unable to format msg: {}", pattern, error); buffer.append(pattern); } } } /** * Returns the formatted message. * @param pattern a message pattern containing argument placeholders * @param args arguments to be used to replace placeholders */ public static String format(final String pattern, final Object[] args) { final int argCount = args != null ? args.length : 0; try { return ParameterFormatter.format(pattern, args, argCount); } catch (final Exception error) { STATUS_LOGGER.error("Unable to format msg: {}", pattern, error); return pattern; } } @Override public boolean equals(final Object object) { if (this == object) { return true; } if (!(object instanceof ParameterizedMessage)) { return false; } final ParameterizedMessage that = (ParameterizedMessage) object; return Objects.equals(pattern, that.pattern) && Arrays.equals(args, that.args); } @Override public int hashCode() { int result = pattern != null ? pattern.hashCode() : 0; result = 31 * result + (args != null ? Arrays.hashCode(args) : 0); return result; } /** * Returns the number of argument placeholders. * @param pattern the message pattern to be analyzed */ public static int countArgumentPlaceholders(final String pattern) { if (pattern == null) { return 0; } return analyzePattern(pattern, -1).placeholderCount; } /** * This method performs a deep toString of the given Object. * Primitive arrays are converted using their respective Arrays.toString methods while * special handling is implemented for "container types", i.e. Object[], Map and Collection because those could * contain themselves. *

* It should be noted that neither AbstractMap.toString() nor AbstractCollection.toString() implement such a * behavior. They only check if the container is directly contained in itself, but not if a contained container * contains the original one. Because of that, Arrays.toString(Object[]) isn't safe either. * Confusing? Just read the last paragraph again and check the respective toString() implementation. *

*

* This means, in effect, that logging would produce a usable output even if an ordinary System.out.println(o) * would produce a relatively hard-to-debug StackOverflowError. *

* @param o The object. * @return The String representation. */ public static String deepToString(final Object o) { return ParameterFormatter.deepToString(o); } /** * This method returns the same as if Object.toString() would not have been * overridden in obj. *

* Note that this isn't 100% secure as collisions can always happen with hash codes. *

*

* Copied from Object.hashCode(): *

*
* As much as is reasonably practical, the hashCode method defined by * class {@code Object} does return distinct integers for distinct * objects. (This is typically implemented by converting the internal * address of the object into an integer, but this implementation * technique is not required by the Java™ programming language.) *
* * @param obj the Object that is to be converted into an identity string. * @return the identity string as also defined in Object.toString() */ public static String identityToString(final Object obj) { return ParameterFormatter.identityToString(obj); } @Override public String toString() { // Avoid formatting arguments! // It can cause recursion, which can become pretty unpleasant while troubleshooting. return "ParameterizedMessage[messagePattern=" + pattern + ", argCount=" + args.length + ", throwableProvided=" + (throwable != null) + ']'; } private void writeObject(final ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeInt(args.length); for (final Object arg : args) { final Serializable serializableArg = arg instanceof Serializable ? (Serializable) arg : String.valueOf(arg); SerializationUtil.writeWrappedObject(serializableArg, out); } } private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { SerializationUtil.assertFiltered(in); in.defaultReadObject(); final int argCount = in.readInt(); args = new Object[argCount]; for (int argIndex = 0; argIndex < args.length; argIndex++) { args[argIndex] = SerializationUtil.readWrappedObject(in); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy