View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.message;
18  
19  import org.apache.logging.log4j.util.IndexedStringMap;
20  import org.apache.logging.log4j.util.PropertiesUtil;
21  import org.apache.logging.log4j.util.StringBuilderFormattable;
22  import org.apache.logging.log4j.util.StringBuilders;
23  
24  import java.math.BigDecimal;
25  import java.util.Collection;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  
30  /**
31   * The default JSON formatter for {@link MapMessage}s.
32   * <p>
33   * The following types have specific handlers:
34   * <p>
35   * <ul>
36   *     <li>{@link Map}
37   *     <li>{@link Collection} ({@link List}, {@link Set}, etc.)
38   *     <li>{@link Number} ({@link BigDecimal}, {@link Double}, {@link Long}, {@link Byte}, etc.)
39   *     <li>{@link Boolean}
40   *     <li>{@link StringBuilderFormattable}
41   *     <li><tt>char/boolean/byte/short/int/long/float/double/Object</tt> arrays
42   *     <li>{@link String}
43   * </ul>
44   * <p>
45   * It supports nesting up to a maximum depth of 8, which is set by
46   * <tt>log4j2.mapMessage.jsonFormatter.maxDepth</tt> property.
47   */
48  enum MapMessageJsonFormatter {;
49  
50      public static final int MAX_DEPTH = readMaxDepth();
51  
52      private static final char DQUOTE = '"';
53  
54      private static final char RBRACE = ']';
55  
56      private static final char LBRACE = '[';
57  
58      private static final char COMMA = ',';
59  
60      private static final char RCURLY = '}';
61  
62      private static final char LCURLY = '{';
63  
64      private static final char COLON = ':';
65  
66      private static int readMaxDepth() {
67          final int maxDepth = PropertiesUtil
68                  .getProperties()
69                  .getIntegerProperty("log4j2.mapMessage.jsonFormatter.maxDepth", 8);
70          if (maxDepth < 0) {
71              throw new IllegalArgumentException(
72                      "was expecting a positive maxDepth, found: " + maxDepth);
73          }
74          return maxDepth;
75      }
76  
77      static void format(final StringBuilder sb, final Object object) {
78          format(sb, object, 0);
79      }
80  
81      private static void format(
82              final StringBuilder sb,
83              final Object object,
84              final int depth) {
85  
86          if (depth >= MAX_DEPTH) {
87              throw new IllegalArgumentException("maxDepth has been exceeded");
88          }
89  
90          // null
91          if (object == null) {
92              sb.append("null");
93          }
94  
95          // map
96          else if (object instanceof IndexedStringMap) {
97              final IndexedStringMap map = (IndexedStringMap) object;
98              formatIndexedStringMap(sb, map, depth);
99          } else if (object instanceof Map) {
100             @SuppressWarnings("unchecked")
101             final Map<Object, Object> map = (Map<Object, Object>) object;
102             formatMap(sb, map, depth);
103         }
104 
105         // list & collection
106         else if (object instanceof List) {
107             @SuppressWarnings("unchecked")
108             final List<Object> list = (List<Object>) object;
109             formatList(sb, list, depth);
110         } else if (object instanceof Collection) {
111             @SuppressWarnings("unchecked")
112             final Collection<Object> collection = (Collection<Object>) object;
113             formatCollection(sb, collection, depth);
114         }
115 
116         // number & boolean
117         else if (object instanceof Number) {
118             final Number number = (Number) object;
119             formatNumber(sb, number);
120         } else if (object instanceof Boolean) {
121             final boolean booleanValue = (boolean) object;
122             formatBoolean(sb, booleanValue);
123         }
124 
125         // formattable
126         else if (object instanceof StringBuilderFormattable) {
127             final StringBuilderFormattable formattable = (StringBuilderFormattable) object;
128             formatFormattable(sb, formattable);
129         }
130 
131         // arrays
132         else if (object instanceof char[]) {
133             final char[] charValues = (char[]) object;
134             formatCharArray(sb, charValues);
135         } else if (object instanceof boolean[]) {
136             final boolean[] booleanValues = (boolean[]) object;
137             formatBooleanArray(sb, booleanValues);
138         } else if (object instanceof byte[]) {
139             final byte[] byteValues = (byte[]) object;
140             formatByteArray(sb, byteValues);
141         } else if (object instanceof short[]) {
142             final short[] shortValues = (short[]) object;
143             formatShortArray(sb, shortValues);
144         } else if (object instanceof int[]) {
145             final int[] intValues = (int[]) object;
146             formatIntArray(sb, intValues);
147         } else if (object instanceof long[]) {
148             final long[] longValues = (long[]) object;
149             formatLongArray(sb, longValues);
150         } else if (object instanceof float[]) {
151             final float[] floatValues = (float[]) object;
152             formatFloatArray(sb, floatValues);
153         } else if (object instanceof double[]) {
154             final double[] doubleValues = (double[]) object;
155             formatDoubleArray(sb, doubleValues);
156         } else if (object instanceof Object[]) {
157             final Object[] objectValues = (Object[]) object;
158             formatObjectArray(sb, objectValues, depth);
159         }
160 
161         // string
162         else {
163             formatString(sb, object);
164         }
165 
166     }
167 
168     private static void formatIndexedStringMap(
169             final StringBuilder sb,
170             final IndexedStringMap map,
171             final int depth) {
172         sb.append(LCURLY);
173         final int nextDepth = depth + 1;
174         for (int entryIndex = 0; entryIndex < map.size(); entryIndex++) {
175             final String key = map.getKeyAt(entryIndex);
176             final Object value = map.getValueAt(entryIndex);
177             if (entryIndex > 0) {
178                 sb.append(COMMA);
179             }
180             sb.append(DQUOTE);
181             final int keyStartIndex = sb.length();
182             sb.append(key);
183             StringBuilders.escapeJson(sb, keyStartIndex);
184             sb.append(DQUOTE).append(COLON);
185             format(sb, value, nextDepth);
186         }
187         sb.append(RCURLY);
188     }
189 
190     private static void formatMap(
191             final StringBuilder sb,
192             final Map<Object, Object> map,
193             final int depth) {
194         sb.append(LCURLY);
195         final int nextDepth = depth + 1;
196         final boolean[] firstEntry = {true};
197         map.forEach((final Object key, final Object value) -> {
198             if (key == null) {
199                 throw new IllegalArgumentException("null keys are not allowed");
200             }
201             if (firstEntry[0]) {
202                 firstEntry[0] = false;
203             } else {
204                 sb.append(COMMA);
205             }
206             sb.append(DQUOTE);
207             final String keyString = String.valueOf(key);
208             final int keyStartIndex = sb.length();
209             sb.append(keyString);
210             StringBuilders.escapeJson(sb, keyStartIndex);
211             sb.append(DQUOTE).append(COLON);
212             format(sb, value, nextDepth);
213         });
214         sb.append(RCURLY);
215     }
216 
217     private static void formatList(
218             final StringBuilder sb,
219             final List<Object> items,
220             final int depth) {
221         sb.append(LBRACE);
222         final int nextDepth = depth + 1;
223         for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) {
224             if (itemIndex > 0) {
225                 sb.append(COMMA);
226             }
227             final Object item = items.get(itemIndex);
228             format(sb, item, nextDepth);
229         }
230         sb.append(RBRACE);
231     }
232 
233     private static void formatCollection(
234             final StringBuilder sb,
235             final Collection<Object> items,
236             final int depth) {
237         sb.append(LBRACE);
238         final int nextDepth = depth + 1;
239         final boolean[] firstItem = {true};
240         items.forEach((final Object item) -> {
241             if (firstItem[0]) {
242                 firstItem[0] = false;
243             } else {
244                 sb.append(COMMA);
245             }
246             format(sb, item, nextDepth);
247         });
248         sb.append(RBRACE);
249     }
250 
251     private static void formatNumber(final StringBuilder sb, final Number number) {
252         if (number instanceof BigDecimal) {
253             final BigDecimal decimalNumber = (BigDecimal) number;
254             sb.append(decimalNumber.toString());
255         } else if (number instanceof Double) {
256             final double doubleNumber = (Double) number;
257             sb.append(doubleNumber);
258         } else if (number instanceof Float) {
259             final float floatNumber = (float) number;
260             sb.append(floatNumber);
261         } else if (number instanceof Byte ||
262                 number instanceof Short ||
263                 number instanceof Integer ||
264                 number instanceof Long) {
265             final long longNumber = number.longValue();
266             sb.append(longNumber);
267         } else {
268             final long longNumber = number.longValue();
269             final double doubleValue = number.doubleValue();
270             if (Double.compare(longNumber, doubleValue) == 0) {
271                 sb.append(longNumber);
272             } else {
273                 sb.append(doubleValue);
274             }
275         }
276     }
277 
278     private static void formatBoolean(final StringBuilder sb, final boolean booleanValue) {
279         sb.append(booleanValue);
280     }
281 
282     private static void formatFormattable(
283             final StringBuilder sb,
284             final StringBuilderFormattable formattable) {
285         sb.append(DQUOTE);
286         final int startIndex = sb.length();
287         formattable.formatTo(sb);
288         StringBuilders.escapeJson(sb, startIndex);
289         sb.append(DQUOTE);
290     }
291 
292     private static void formatCharArray(final StringBuilder sb, final char[] items) {
293         sb.append(LBRACE);
294         for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
295             if (itemIndex > 0) {
296                 sb.append(COMMA);
297             }
298             final char item = items[itemIndex];
299             sb.append(DQUOTE);
300             final int startIndex = sb.length();
301             sb.append(item);
302             StringBuilders.escapeJson(sb, startIndex);
303             sb.append(DQUOTE);
304         }
305         sb.append(RBRACE);
306     }
307 
308     private static void formatBooleanArray(final StringBuilder sb, final boolean[] items) {
309         sb.append(LBRACE);
310         for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
311             if (itemIndex > 0) {
312                 sb.append(COMMA);
313             }
314             final boolean item = items[itemIndex];
315             sb.append(item);
316         }
317         sb.append(RBRACE);
318     }
319 
320     private static void formatByteArray(final StringBuilder sb, final byte[] items) {
321         sb.append(LBRACE);
322         for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
323             if (itemIndex > 0) {
324                 sb.append(COMMA);
325             }
326             final byte item = items[itemIndex];
327             sb.append(item);
328         }
329         sb.append(RBRACE);
330     }
331 
332     private static void formatShortArray(final StringBuilder sb, final short[] items) {
333         sb.append(LBRACE);
334         for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
335             if (itemIndex > 0) {
336                 sb.append(COMMA);
337             }
338             final short item = items[itemIndex];
339             sb.append(item);
340         }
341         sb.append(RBRACE);
342     }
343 
344     private static void formatIntArray(final StringBuilder sb, final int[] items) {
345         sb.append(LBRACE);
346         for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
347             if (itemIndex > 0) {
348                 sb.append(COMMA);
349             }
350             final int item = items[itemIndex];
351             sb.append(item);
352         }
353         sb.append(RBRACE);
354     }
355 
356     private static void formatLongArray(final StringBuilder sb, final long[] items) {
357         sb.append(LBRACE);
358         for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
359             if (itemIndex > 0) {
360                 sb.append(COMMA);
361             }
362             final long item = items[itemIndex];
363             sb.append(item);
364         }
365         sb.append(RBRACE);
366     }
367 
368     private static void formatFloatArray(final StringBuilder sb, final float[] items) {
369         sb.append(LBRACE);
370         for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
371             if (itemIndex > 0) {
372                 sb.append(COMMA);
373             }
374             final float item = items[itemIndex];
375             sb.append(item);
376         }
377         sb.append(RBRACE);
378     }
379 
380     private static void formatDoubleArray(
381             final StringBuilder sb,
382             final double[] items) {
383         sb.append(LBRACE);
384         for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
385             if (itemIndex > 0) {
386                 sb.append(COMMA);
387             }
388             final double item = items[itemIndex];
389             sb.append(item);
390         }
391         sb.append(RBRACE);
392     }
393 
394     private static void formatObjectArray(
395             final StringBuilder sb,
396             final Object[] items,
397             final int depth) {
398         sb.append(LBRACE);
399         final int nextDepth = depth + 1;
400         for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
401             if (itemIndex > 0) {
402                 sb.append(COMMA);
403             }
404             final Object item = items[itemIndex];
405             format(sb, item, nextDepth);
406         }
407         sb.append(RBRACE);
408     }
409 
410     private static void formatString(final StringBuilder sb, final Object value) {
411         sb.append(DQUOTE);
412         final int startIndex = sb.length();
413         final String valueString = String.valueOf(value);
414         sb.append(valueString);
415         StringBuilders.escapeJson(sb, startIndex);
416         sb.append(DQUOTE);
417     }
418 
419 }