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

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

Go to download

The Pax Logging API Library is to allow for the Pax Logging Service to be reloaded without stopping the many dependent bundles. It also contains the OSGi Log Service API and the Knopflerfish Log API.

There is a newer version: 2.2.8
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 java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Handles messages that consist of a format string containing '{}' to represent each replaceable token, and
 * the parameters.
 * 

* This class was originally written for Lilith by Joern Huxhorn where it is * licensed under the LGPL. It has been relicensed here with his permission providing that this attribution remain. *

*/ public class ParameterizedMessage implements Message { /** * Prefix for recursion. */ public static final String RECURSION_PREFIX = "[..."; /** * Suffix for recursion. */ public static final String RECURSION_SUFFIX = "...]"; /** * Prefix for errors. */ public static final String ERROR_PREFIX = "[!!!"; /** * Separator for errors. */ public static final String ERROR_SEPARATOR = "=>"; /** * Separator for error messages. */ public static final String ERROR_MSG_SEPARATOR = ":"; /** * Suffix for errors. */ public static final String ERROR_SUFFIX = "!!!]"; private static final long serialVersionUID = -665975803997290697L; private static final int HASHVAL = 31; private static final char DELIM_START = '{'; private static final char DELIM_STOP = '}'; private static final char ESCAPE_CHAR = '\\'; private final String messagePattern; private final String[] stringArgs; private transient Object[] argArray; private transient String formattedMessage; private transient Throwable throwable; /** * Creates a parameterized message. * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders * where parameters should be substituted. * @param stringArgs The arguments for substitution. * @param throwable A Throwable. */ public ParameterizedMessage(final String messagePattern, final String[] stringArgs, final Throwable throwable) { this.messagePattern = messagePattern; this.stringArgs = stringArgs; this.throwable = throwable; } /** * Creates a parameterized message. * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders * where parameters should be substituted. * @param objectArgs The arguments for substitution. * @param throwable A Throwable. */ public ParameterizedMessage(final String messagePattern, final Object[] objectArgs, final Throwable throwable) { this.messagePattern = messagePattern; this.throwable = throwable; this.stringArgs = argumentsToStrings(objectArgs); } /** * Constructs a ParameterizedMessage which contains the arguments converted to String as well as an optional * Throwable. * *

If the last argument is a Throwable and is NOT used up by a placeholder in the message pattern it is returned * in {@link #getThrowable()} and won't be contained in the created String[]. * If it is used up {@link #getThrowable()} will return null even if the last argument was a Throwable!

* * @param messagePattern the message pattern that to be checked for placeholders. * @param arguments the argument array to be converted. */ public ParameterizedMessage(final String messagePattern, final Object[] arguments) { this.messagePattern = messagePattern; this.stringArgs = argumentsToStrings(arguments); } /** * Constructor with a pattern and a single parameter. * @param messagePattern The message pattern. * @param arg The parameter. */ public ParameterizedMessage(final String messagePattern, final Object arg) { this(messagePattern, new Object[]{arg}); } /** * Constructor with a pattern and two parameters. * @param messagePattern The message pattern. * @param arg1 The first parameter. * @param arg2 The second parameter. */ public ParameterizedMessage(final String messagePattern, final Object arg1, final Object arg2) { this(messagePattern, new Object[]{arg1, arg2}); } private String[] argumentsToStrings(final Object[] arguments) { if (arguments == null) { return null; } final int argsCount = countArgumentPlaceholders(messagePattern); int resultArgCount = arguments.length; if (argsCount < arguments.length && throwable == null && arguments[arguments.length - 1] instanceof Throwable) { throwable = (Throwable) arguments[arguments.length - 1]; resultArgCount--; } argArray = new Object[resultArgCount]; System.arraycopy(arguments, 0, argArray, 0, resultArgCount); String[] strArgs; if (argsCount == 1 && throwable == null && arguments.length > 1) { // special case strArgs = new String[1]; strArgs[0] = deepToString(arguments); } else { strArgs = new String[resultArgCount]; for (int i = 0; i < strArgs.length; i++) { strArgs[i] = deepToString(arguments[i]); } } return strArgs; } /** * Returns the formatted message. * @return the formatted message. */ @Override public String getFormattedMessage() { if (formattedMessage == null) { formattedMessage = formatMessage(messagePattern, stringArgs); } return formattedMessage; } /** * Returns the message pattern. * @return the message pattern. */ @Override public String getFormat() { return messagePattern; } /** * Returns the message parameters. * @return the message parameters. */ @Override public Object[] getParameters() { if (argArray != null) { return argArray; } return stringArgs; } /** * Returns the Throwable that was given as the last argument, if any. * It will not survive serialization. The Throwable exists as part of the message * primarily so that it can be extracted from the end of the list of parameters * and then be added to the LogEvent. As such, the Throwable in the event should * not be used once the LogEvent has been constructed. * * @return the Throwable, if any. */ @Override public Throwable getThrowable() { return throwable; } protected String formatMessage(final String msgPattern, final String[] sArgs) { return formatStringArgs(msgPattern, sArgs); } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final ParameterizedMessage that = (ParameterizedMessage) o; if (messagePattern != null ? !messagePattern.equals(that.messagePattern) : that.messagePattern != null) { return false; } if (!Arrays.equals(stringArgs, that.stringArgs)) { return false; } //if (throwable != null ? !throwable.equals(that.throwable) : that.throwable != null) return false; return true; } @Override public int hashCode() { int result = messagePattern != null ? messagePattern.hashCode() : 0; result = HASHVAL * result + (stringArgs != null ? Arrays.hashCode(stringArgs) : 0); return result; } /** * Replace placeholders in the given messagePattern with arguments. * * @param messagePattern the message pattern containing placeholders. * @param arguments the arguments to be used to replace placeholders. * @return the formatted message. */ public static String format(final String messagePattern, final Object[] arguments) { if (messagePattern == null || arguments == null || arguments.length == 0) { return messagePattern; } if (arguments instanceof String[]) { return formatStringArgs(messagePattern, (String[]) arguments); } final String[] stringArgs = new String[arguments.length]; for (int i = 0; i < arguments.length; i++) { stringArgs[i] = String.valueOf(arguments[i]); } return formatStringArgs(messagePattern, stringArgs); } /** * Replace placeholders in the given messagePattern with arguments. *

* Package protected for unit tests. * * @param messagePattern the message pattern containing placeholders. * @param arguments the arguments to be used to replace placeholders. * @return the formatted message. */ // Profiling showed this method is important to log4j performance. Modify with care! // 33 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 static String formatStringArgs(final String messagePattern, final String[] arguments) { int len = 0; if (messagePattern == null || (len = messagePattern.length()) == 0 || arguments == null || arguments.length == 0) { return messagePattern; } return formatStringArgs0(messagePattern, len, arguments); } // Profiling showed this method is important to log4j performance. Modify with care! // 157 bytes (will be inlined when hot enough: < 325 bytes) private static String formatStringArgs0(final String messagePattern, final int len, final String[] arguments) { final char[] result = new char[len + sumStringLengths(arguments)]; int pos = 0; int escapeCounter = 0; int currentArgument = 0; int i = 0; for (; i < len - 1; i++) { // last char is excluded from the loop final char curChar = messagePattern.charAt(i); if (curChar == ESCAPE_CHAR) { escapeCounter++; } else { if (isDelimPair(curChar, messagePattern, i)) { // looks ahead one char i++; // write escaped escape chars pos = writeEscapedEscapeChars(escapeCounter, result, pos); if (isOdd(escapeCounter)) { // i.e. escaped // write escaped escape chars pos = writeDelimPair(result, pos); } else { // unescaped pos = writeArgOrDelimPair(arguments, currentArgument, result, pos); currentArgument++; } } else { pos = handleLiteralChar(result, pos, escapeCounter, curChar); } escapeCounter = 0; } } pos = handleRemainingCharIfAny(messagePattern, len, result, pos, escapeCounter, i); return new String(result, 0, pos); } /** * Returns the sum of the lengths of all Strings in the specified array. */ // Profiling showed this method is important to log4j performance. Modify with care! // 30 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 private static int sumStringLengths(final String[] arguments) { int result = 0; for (int i = 0; i < arguments.length; i++) { result += String.valueOf(arguments[i]).length(); } return result; } /** * Returns {@code true} if the specified char and the char at {@code curCharIndex + 1} in the specified message * pattern together form a "{}" delimiter pair, returns {@code false} otherwise. */ // Profiling showed this method is important to log4j performance. Modify with care! // 22 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 private static boolean isDelimPair(final char curChar, final String messagePattern, final int curCharIndex) { return curChar == DELIM_START && messagePattern.charAt(curCharIndex + 1) == DELIM_STOP; } /** * Detects whether the message pattern has been fully processed or if an unprocessed character remains and processes * it if necessary, returning the resulting position in the result char array. */ // Profiling showed this method is important to log4j performance. Modify with care! // 28 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 private static int handleRemainingCharIfAny(final String messagePattern, final int len, final char[] result, int pos, int escapeCounter, int i) { if (i == len - 1) { final char curChar = messagePattern.charAt(i); pos = handleLastChar(result, pos, escapeCounter, curChar); } return pos; } /** * Processes the last unprocessed character and returns the resulting position in the result char array. */ // Profiling showed this method is important to log4j performance. Modify with care! // 28 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 private static int handleLastChar(final char[] result, int pos, final int escapeCounter, final char curChar) { if (curChar == ESCAPE_CHAR) { pos = writeUnescapedEscapeChars(escapeCounter + 1, result, pos); } else { pos = handleLiteralChar(result, pos, escapeCounter, curChar); } return pos; } /** * Processes a literal char (neither an '\' escape char nor a "{}" delimiter pair) and returns the resulting * position. */ // Profiling showed this method is important to log4j performance. Modify with care! // 16 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 private static int handleLiteralChar(final char[] result, int pos, final int escapeCounter, final char curChar) { // any other char beside ESCAPE or DELIM_START/STOP-combo // write unescaped escape chars pos = writeUnescapedEscapeChars(escapeCounter, result, pos); result[pos++] = curChar; return pos; } /** * Writes "{}" to the specified result array at the specified position and returns the resulting position. */ // Profiling showed this method is important to log4j performance. Modify with care! // 18 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 private static int writeDelimPair(final char[] result, int pos) { result[pos++] = DELIM_START; result[pos++] = DELIM_STOP; return pos; } /** * Returns {@code true} if the specified parameter is odd. */ // Profiling showed this method is important to log4j performance. Modify with care! // 11 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 private static boolean isOdd(final int number) { return (number & 1) == 1; } /** * Writes a '\' char to the specified result array (starting at the specified position) for each pair of * '\' escape chars encountered in the message format and returns the resulting position. */ // Profiling showed this method is important to log4j performance. Modify with care! // 11 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 private static int writeEscapedEscapeChars(final int escapeCounter, final char[] result, final int pos) { final int escapedEscapes = escapeCounter >> 1; // divide by two return writeUnescapedEscapeChars(escapedEscapes, result, pos); } /** * Writes the specified number of '\' chars to the specified result array (starting at the specified position) and * returns the resulting position. */ // Profiling showed this method is important to log4j performance. Modify with care! // 20 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 private static int writeUnescapedEscapeChars(int escapeCounter, char[] result, int pos) { while (escapeCounter > 0) { result[pos++] = ESCAPE_CHAR; escapeCounter--; } return pos; } /** * Appends the argument at the specified argument index (or, if no such argument exists, the "{}" delimiter pair) to * the specified result char array at the specified position and returns the resulting position. */ // Profiling showed this method is important to log4j performance. Modify with care! // 25 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 private static int writeArgOrDelimPair(final String[] arguments, final int currentArgument, final char[] result, int pos) { if (currentArgument < arguments.length) { pos = writeArgAt0(arguments, currentArgument, result, pos); } else { pos = writeDelimPair(result, pos); } return pos; } /** * Appends the argument at the specified argument index to the specified result char array at the specified position * and returns the resulting position. */ // Profiling showed this method is important to log4j performance. Modify with care! // 30 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 private static int writeArgAt0(final String[] arguments, final int currentArgument, final char[] result, final int pos) { final String arg = String.valueOf(arguments[currentArgument]); int argLen = arg.length(); arg.getChars(0, argLen, result, pos); return pos + argLen; } /** * Counts the number of unescaped placeholders in the given messagePattern. * * @param messagePattern the message pattern to be analyzed. * @return the number of unescaped placeholders. */ public static int countArgumentPlaceholders(final String messagePattern) { if (messagePattern == null) { return 0; } final int delim = messagePattern.indexOf(DELIM_START); if (delim == -1) { // special case, no placeholders at all. return 0; } int result = 0; boolean isEscaped = false; for (int i = 0; i < messagePattern.length(); i++) { final char curChar = messagePattern.charAt(i); if (curChar == ESCAPE_CHAR) { isEscaped = !isEscaped; } else if (curChar == DELIM_START) { if (!isEscaped && i < messagePattern.length() - 1 && messagePattern.charAt(i + 1) == DELIM_STOP) { result++; i++; } isEscaped = false; } else { isEscaped = false; } } return result; } /** * 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) { if (o == null) { return null; } if (o instanceof String) { return (String) o; } final StringBuilder str = new StringBuilder(); final Set dejaVu = new HashSet<>(); // that's actually a neat name ;) recursiveDeepToString(o, str, dejaVu); return str.toString(); } /** * 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. *

* dejaVu is used in case of those container types to prevent an endless recursion. *

*

* 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 to convert into a String * @param str the StringBuilder that o will be appended to * @param dejaVu a list of container identities that were already used. */ private static void recursiveDeepToString(final Object o, final StringBuilder str, final Set dejaVu) { if (appendStringDateOrNull(o, str)) { return; } if (isMaybeRecursive(o)) { appendPotentiallyRecursiveValue(o, str, dejaVu); } else { tryObjectToString(o, str); } } private static boolean appendStringDateOrNull(final Object o, final StringBuilder str) { if (o == null || o instanceof String) { str.append(String.valueOf(o)); return true; } return appendDate(o, str); } private static boolean appendDate(final Object o, final StringBuilder str) { if (!(o instanceof Date)) { return false; } final Date date = (Date) o; final SimpleDateFormat format = getSimpleDateFormat(); str.append(format.format(date)); return true; } private static SimpleDateFormat getSimpleDateFormat() { // I'll leave it like this for the moment... this could probably be optimized using ThreadLocal... return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); } /** * Returns {@code true} if the specified object is an array, a Map or a Collection. */ private static boolean isMaybeRecursive(final Object o) { return o.getClass().isArray() || o instanceof Map || o instanceof Collection; } private static void appendPotentiallyRecursiveValue(final Object o, final StringBuilder str, final Set dejaVu) { final Class oClass = o.getClass(); if (oClass.isArray()) { appendArray(o, str, dejaVu, oClass); } else if (o instanceof Map) { appendMap(o, str, dejaVu); } else if (o instanceof Collection) { appendCollection(o, str, dejaVu); } } private static void appendArray(final Object o, final StringBuilder str, final Set dejaVu, final Class oClass) { if (oClass == byte[].class) { str.append(Arrays.toString((byte[]) o)); } else if (oClass == short[].class) { str.append(Arrays.toString((short[]) o)); } else if (oClass == int[].class) { str.append(Arrays.toString((int[]) o)); } else if (oClass == long[].class) { str.append(Arrays.toString((long[]) o)); } else if (oClass == float[].class) { str.append(Arrays.toString((float[]) o)); } else if (oClass == double[].class) { str.append(Arrays.toString((double[]) o)); } else if (oClass == boolean[].class) { str.append(Arrays.toString((boolean[]) o)); } else if (oClass == char[].class) { str.append(Arrays.toString((char[]) o)); } else { // special handling of container Object[] final String id = identityToString(o); if (dejaVu.contains(id)) { str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX); } else { dejaVu.add(id); final Object[] oArray = (Object[]) o; str.append('['); boolean first = true; for (final Object current : oArray) { if (first) { first = false; } else { str.append(", "); } recursiveDeepToString(current, str, new HashSet<>(dejaVu)); } str.append(']'); } //str.append(Arrays.deepToString((Object[]) o)); } } private static void appendMap(final Object o, final StringBuilder str, final Set dejaVu) { // special handling of container Map final String id = identityToString(o); if (dejaVu.contains(id)) { str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX); } else { dejaVu.add(id); final Map oMap = (Map) o; str.append('{'); boolean isFirst = true; for (final Object o1 : oMap.entrySet()) { final Map.Entry current = (Map.Entry) o1; if (isFirst) { isFirst = false; } else { str.append(", "); } final Object key = current.getKey(); final Object value = current.getValue(); recursiveDeepToString(key, str, new HashSet<>(dejaVu)); str.append('='); recursiveDeepToString(value, str, new HashSet<>(dejaVu)); } str.append('}'); } } private static void appendCollection(final Object o, final StringBuilder str, final Set dejaVu) { // special handling of container Collection final String id = identityToString(o); if (dejaVu.contains(id)) { str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX); } else { dejaVu.add(id); final Collection oCol = (Collection) o; str.append('['); boolean isFirst = true; for (final Object anOCol : oCol) { if (isFirst) { isFirst = false; } else { str.append(", "); } recursiveDeepToString(anOCol, str, new HashSet<>(dejaVu)); } str.append(']'); } } private static void tryObjectToString(final Object o, final StringBuilder str) { // it's just some other Object, we can only use toString(). try { str.append(o.toString()); } catch (final Throwable t) { handleErrorInObjectToString(o, str, t); } } private static void handleErrorInObjectToString(final Object o, final StringBuilder str, final Throwable t) { str.append(ERROR_PREFIX); str.append(identityToString(o)); str.append(ERROR_SEPARATOR); final String msg = t.getMessage(); final String className = t.getClass().getName(); str.append(className); if (!className.equals(msg)) { str.append(ERROR_MSG_SEPARATOR); str.append(msg); } str.append(ERROR_SUFFIX); } /** * 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) { if (obj == null) { return null; } return obj.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(obj)); } @Override public String toString() { return "ParameterizedMessage[messagePattern=" + messagePattern + ", stringArgs=" + Arrays.toString(stringArgs) + ", throwable=" + throwable + ']'; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy