blob: 3887f9767a786c3809d5a809100d697a5f3bab43 [file] [log] [blame]
/*
* 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 org.apache.logging.log4j.util.IndexedStringMap;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.apache.logging.log4j.util.StringBuilderFormattable;
import org.apache.logging.log4j.util.StringBuilders;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The default JSON formatter for {@link MapMessage}s.
* <p>
* The following types have specific handlers:
* <p>
* <ul>
* <li>{@link Map}
* <li>{@link Collection} ({@link List}, {@link Set}, etc.)
* <li>{@link Number} ({@link BigDecimal}, {@link Double}, {@link Long}, {@link Byte}, etc.)
* <li>{@link Boolean}
* <li>{@link StringBuilderFormattable}
* <li><tt>char/boolean/byte/short/int/long/float/double/Object</tt> arrays
* <li>{@link String}
* </ul>
* <p>
* It supports nesting up to a maximum depth of 8, which is set by
* <tt>log4j2.mapMessage.jsonFormatter.maxDepth</tt> property.
*/
enum MapMessageJsonFormatter {;
public static final int MAX_DEPTH = readMaxDepth();
private static final char DQUOTE = '"';
private static final char RBRACE = ']';
private static final char LBRACE = '[';
private static final char COMMA = ',';
private static final char RCURLY = '}';
private static final char LCURLY = '{';
private static final char COLON = ':';
private static int readMaxDepth() {
final int maxDepth = PropertiesUtil
.getProperties()
.getIntegerProperty("log4j2.mapMessage.jsonFormatter.maxDepth", 8);
if (maxDepth < 0) {
throw new IllegalArgumentException(
"was expecting a positive maxDepth, found: " + maxDepth);
}
return maxDepth;
}
static void format(final StringBuilder sb, final Object object) {
format(sb, object, 0);
}
private static void format(
final StringBuilder sb,
final Object object,
final int depth) {
if (depth >= MAX_DEPTH) {
throw new IllegalArgumentException("maxDepth has been exceeded");
}
// null
if (object == null) {
sb.append("null");
}
// map
else if (object instanceof IndexedStringMap) {
final IndexedStringMap map = (IndexedStringMap) object;
formatIndexedStringMap(sb, map, depth);
} else if (object instanceof Map) {
@SuppressWarnings("unchecked")
final Map<Object, Object> map = (Map<Object, Object>) object;
formatMap(sb, map, depth);
}
// list & collection
else if (object instanceof List) {
@SuppressWarnings("unchecked")
final List<Object> list = (List<Object>) object;
formatList(sb, list, depth);
} else if (object instanceof Collection) {
@SuppressWarnings("unchecked")
final Collection<Object> collection = (Collection<Object>) object;
formatCollection(sb, collection, depth);
}
// number & boolean
else if (object instanceof Number) {
final Number number = (Number) object;
formatNumber(sb, number);
} else if (object instanceof Boolean) {
final boolean booleanValue = (boolean) object;
formatBoolean(sb, booleanValue);
}
// formattable
else if (object instanceof StringBuilderFormattable) {
final StringBuilderFormattable formattable = (StringBuilderFormattable) object;
formatFormattable(sb, formattable);
}
// arrays
else if (object instanceof char[]) {
final char[] charValues = (char[]) object;
formatCharArray(sb, charValues);
} else if (object instanceof boolean[]) {
final boolean[] booleanValues = (boolean[]) object;
formatBooleanArray(sb, booleanValues);
} else if (object instanceof byte[]) {
final byte[] byteValues = (byte[]) object;
formatByteArray(sb, byteValues);
} else if (object instanceof short[]) {
final short[] shortValues = (short[]) object;
formatShortArray(sb, shortValues);
} else if (object instanceof int[]) {
final int[] intValues = (int[]) object;
formatIntArray(sb, intValues);
} else if (object instanceof long[]) {
final long[] longValues = (long[]) object;
formatLongArray(sb, longValues);
} else if (object instanceof float[]) {
final float[] floatValues = (float[]) object;
formatFloatArray(sb, floatValues);
} else if (object instanceof double[]) {
final double[] doubleValues = (double[]) object;
formatDoubleArray(sb, doubleValues);
} else if (object instanceof Object[]) {
final Object[] objectValues = (Object[]) object;
formatObjectArray(sb, objectValues, depth);
}
// string
else {
formatString(sb, object);
}
}
private static void formatIndexedStringMap(
final StringBuilder sb,
final IndexedStringMap map,
final int depth) {
sb.append(LCURLY);
final int nextDepth = depth + 1;
for (int entryIndex = 0; entryIndex < map.size(); entryIndex++) {
final String key = map.getKeyAt(entryIndex);
final Object value = map.getValueAt(entryIndex);
if (entryIndex > 0) {
sb.append(COMMA);
}
sb.append(DQUOTE);
final int keyStartIndex = sb.length();
sb.append(key);
StringBuilders.escapeJson(sb, keyStartIndex);
sb.append(DQUOTE).append(COLON);
format(sb, value, nextDepth);
}
sb.append(RCURLY);
}
private static void formatMap(
final StringBuilder sb,
final Map<Object, Object> map,
final int depth) {
sb.append(LCURLY);
final int nextDepth = depth + 1;
final boolean[] firstEntry = {true};
map.forEach((final Object key, final Object value) -> {
if (key == null) {
throw new IllegalArgumentException("null keys are not allowed");
}
if (firstEntry[0]) {
firstEntry[0] = false;
} else {
sb.append(COMMA);
}
sb.append(DQUOTE);
final String keyString = String.valueOf(key);
final int keyStartIndex = sb.length();
sb.append(keyString);
StringBuilders.escapeJson(sb, keyStartIndex);
sb.append(DQUOTE).append(COLON);
format(sb, value, nextDepth);
});
sb.append(RCURLY);
}
private static void formatList(
final StringBuilder sb,
final List<Object> items,
final int depth) {
sb.append(LBRACE);
final int nextDepth = depth + 1;
for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) {
if (itemIndex > 0) {
sb.append(COMMA);
}
final Object item = items.get(itemIndex);
format(sb, item, nextDepth);
}
sb.append(RBRACE);
}
private static void formatCollection(
final StringBuilder sb,
final Collection<Object> items,
final int depth) {
sb.append(LBRACE);
final int nextDepth = depth + 1;
final boolean[] firstItem = {true};
items.forEach((final Object item) -> {
if (firstItem[0]) {
firstItem[0] = false;
} else {
sb.append(COMMA);
}
format(sb, item, nextDepth);
});
sb.append(RBRACE);
}
private static void formatNumber(final StringBuilder sb, final Number number) {
if (number instanceof BigDecimal) {
final BigDecimal decimalNumber = (BigDecimal) number;
sb.append(decimalNumber.toString());
} else if (number instanceof Double) {
final double doubleNumber = (Double) number;
sb.append(doubleNumber);
} else if (number instanceof Float) {
final float floatNumber = (float) number;
sb.append(floatNumber);
} else if (number instanceof Byte ||
number instanceof Short ||
number instanceof Integer ||
number instanceof Long) {
final long longNumber = number.longValue();
sb.append(longNumber);
} else {
final long longNumber = number.longValue();
final double doubleValue = number.doubleValue();
if (Double.compare(longNumber, doubleValue) == 0) {
sb.append(longNumber);
} else {
sb.append(doubleValue);
}
}
}
private static void formatBoolean(final StringBuilder sb, final boolean booleanValue) {
sb.append(booleanValue);
}
private static void formatFormattable(
final StringBuilder sb,
final StringBuilderFormattable formattable) {
sb.append(DQUOTE);
final int startIndex = sb.length();
formattable.formatTo(sb);
StringBuilders.escapeJson(sb, startIndex);
sb.append(DQUOTE);
}
private static void formatCharArray(final StringBuilder sb, final char[] items) {
sb.append(LBRACE);
for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
if (itemIndex > 0) {
sb.append(COMMA);
}
final char item = items[itemIndex];
sb.append(DQUOTE);
final int startIndex = sb.length();
sb.append(item);
StringBuilders.escapeJson(sb, startIndex);
sb.append(DQUOTE);
}
sb.append(RBRACE);
}
private static void formatBooleanArray(final StringBuilder sb, final boolean[] items) {
sb.append(LBRACE);
for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
if (itemIndex > 0) {
sb.append(COMMA);
}
final boolean item = items[itemIndex];
sb.append(item);
}
sb.append(RBRACE);
}
private static void formatByteArray(final StringBuilder sb, final byte[] items) {
sb.append(LBRACE);
for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
if (itemIndex > 0) {
sb.append(COMMA);
}
final byte item = items[itemIndex];
sb.append(item);
}
sb.append(RBRACE);
}
private static void formatShortArray(final StringBuilder sb, final short[] items) {
sb.append(LBRACE);
for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
if (itemIndex > 0) {
sb.append(COMMA);
}
final short item = items[itemIndex];
sb.append(item);
}
sb.append(RBRACE);
}
private static void formatIntArray(final StringBuilder sb, final int[] items) {
sb.append(LBRACE);
for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
if (itemIndex > 0) {
sb.append(COMMA);
}
final int item = items[itemIndex];
sb.append(item);
}
sb.append(RBRACE);
}
private static void formatLongArray(final StringBuilder sb, final long[] items) {
sb.append(LBRACE);
for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
if (itemIndex > 0) {
sb.append(COMMA);
}
final long item = items[itemIndex];
sb.append(item);
}
sb.append(RBRACE);
}
private static void formatFloatArray(final StringBuilder sb, final float[] items) {
sb.append(LBRACE);
for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
if (itemIndex > 0) {
sb.append(COMMA);
}
final float item = items[itemIndex];
sb.append(item);
}
sb.append(RBRACE);
}
private static void formatDoubleArray(
final StringBuilder sb,
final double[] items) {
sb.append(LBRACE);
for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
if (itemIndex > 0) {
sb.append(COMMA);
}
final double item = items[itemIndex];
sb.append(item);
}
sb.append(RBRACE);
}
private static void formatObjectArray(
final StringBuilder sb,
final Object[] items,
final int depth) {
sb.append(LBRACE);
final int nextDepth = depth + 1;
for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
if (itemIndex > 0) {
sb.append(COMMA);
}
final Object item = items[itemIndex];
format(sb, item, nextDepth);
}
sb.append(RBRACE);
}
private static void formatString(final StringBuilder sb, final Object value) {
sb.append(DQUOTE);
final int startIndex = sb.length();
final String valueString = String.valueOf(value);
sb.append(valueString);
StringBuilders.escapeJson(sb, startIndex);
sb.append(DQUOTE);
}
}