/*
 * 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 MapMessages.

The following types have specific handlers:

It supports nesting up to a maximum depth of 8, which is set by log4j2.mapMessage.jsonFormatter.maxDepth property.

/** * 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); } }