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