View Javadoc
1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2023, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  
15  package ch.qos.logback.classic.encoder;
16  
17  import ch.qos.logback.classic.spi.ILoggingEvent;
18  import ch.qos.logback.classic.spi.IThrowableProxy;
19  import ch.qos.logback.classic.spi.LoggerContextVO;
20  import ch.qos.logback.classic.spi.StackTraceElementProxy;
21  import ch.qos.logback.core.CoreConstants;
22  import ch.qos.logback.core.encoder.EncoderBase;
23  import org.slf4j.Marker;
24  import org.slf4j.event.KeyValuePair;
25  
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  
30  import static ch.qos.logback.core.CoreConstants.COLON_CHAR;
31  import static ch.qos.logback.core.CoreConstants.COMMA_CHAR;
32  import static ch.qos.logback.core.CoreConstants.DOUBLE_QUOTE_CHAR;
33  import static ch.qos.logback.core.CoreConstants.UTF_8_CHARSET;
34  import static ch.qos.logback.core.encoder.JsonEscapeUtil.jsonEscapeString;
35  import static ch.qos.logback.core.model.ModelConstants.NULL_STR;
36  
37  /**
38   *
39   *
40   * http://ndjson.org/ https://datatracker.ietf.org/doc/html/rfc8259
41   */
42  public class JsonEncoder extends EncoderBase<ILoggingEvent> {
43      static final boolean DO_NOT_ADD_QUOTE_KEY = false;
44      static final boolean ADD_QUOTE_KEY = true;
45      static int DEFAULT_SIZE = 1024;
46      static int DEFAULT_SIZE_WITH_THROWABLE = DEFAULT_SIZE * 8;
47  
48      static byte[] EMPTY_BYTES = new byte[0];
49  
50      public static final String CONTEXT_ATTR_NAME = "context";
51      public static final String NAME_ATTR_NAME = "name";
52      public static final String BIRTHDATE_ATTR_NAME = "birthdate";
53      public static final String CONTEXT_PROPERTIES_ATTR_NAME = "properties";
54  
55      public static final String TIMESTAMP_ATTR_NAME = "timestamp";
56  
57      public static final String NANOSECONDS_ATTR_NAME = "nanoseconds";
58  
59      public static final String SEQUENCE_NUMBER_ATTR_NAME = "sequenceNumber";
60  
61      public static final String LEVEL_ATTR_NAME = "level";
62      public static final String MARKERS_ATTR_NAME = "markers";
63      public static final String THREAD_NAME_ATTR_NAME = "threadName";
64      public static final String MDC_ATTR_NAME = "mdc";
65      public static final String LOGGER_ATTR_NAME = "loggerName";
66  
67      public static final String MESSAGE_ATTR_NAME = "message";
68  
69      public static final String FORMATTED_MESSAGE_ATTR_NAME = "formattedMessage";
70  
71      public static final String ARGUMENT_ARRAY_ATTR_NAME = "arguments";
72      public static final String KEY_VALUE_PAIRS_ATTR_NAME = "kvpList";
73  
74      public static final String THROWABLE_ATTR_NAME = "throwable";
75  
76      private static final String CYCLIC_THROWABLE_ATTR_NAME = "cyclic";
77  
78      public static final String CAUSE_ATTR_NAME = "cause";
79  
80      public static final String SUPPRESSED_ATTR_NAME = "suppressed";
81  
82      public static final String COMMON_FRAMES_COUNT_ATTR_NAME = "commonFramesCount";
83  
84      public static final String CLASS_NAME_ATTR_NAME = "className";
85      public static final String METHOD_NAME_ATTR_NAME = "methodName";
86      private static final String FILE_NAME_ATTR_NAME = "fileName";
87      private static final String LINE_NUMBER_ATTR_NAME = "lineNumber";
88  
89      public static final String STEP_ARRAY_NAME_ATTRIBUTE = "stepArray";
90  
91      private static final char OPEN_OBJ = '{';
92      private static final char CLOSE_OBJ = '}';
93      private static final char OPEN_ARRAY = '[';
94      private static final char CLOSE_ARRAY = ']';
95  
96      private static final char QUOTE = DOUBLE_QUOTE_CHAR;
97      private static final char SP = ' ';
98      private static final char ENTRY_SEPARATOR = COLON_CHAR;
99  
100     private static final String COL_SP = ": ";
101 
102     private static final String QUOTE_COL = "\":";
103 
104     private static final char VALUE_SEPARATOR = COMMA_CHAR;
105 
106     private boolean withSequenceNumber = true;
107 
108     private boolean withTimestamp = true;
109     private boolean withNanoseconds = true;
110 
111 
112     private boolean withLevel = true;
113     private boolean withThreadName = true;
114     private boolean withLoggerName = true;
115     private boolean withContext = true;
116     private boolean withMarkers = true;
117     private boolean withMDC = true;
118     private boolean withKVPList = true;
119     private boolean withMessage = true;
120     private boolean withArguments = true;
121     private boolean withThrowable = true;
122     private boolean withFormattedMessage = false;
123 
124 
125     @Override
126     public byte[] headerBytes() {
127         return EMPTY_BYTES;
128     }
129 
130     @Override
131     public byte[] encode(ILoggingEvent event) {
132         final int initialCapacity = event.getThrowableProxy() == null ? DEFAULT_SIZE : DEFAULT_SIZE_WITH_THROWABLE;
133         StringBuilder sb = new StringBuilder(initialCapacity);
134         sb.append(OPEN_OBJ);
135 
136         if (withSequenceNumber) {
137             appenderMemberWithLongValue(sb, SEQUENCE_NUMBER_ATTR_NAME, event.getSequenceNumber());
138             sb.append(VALUE_SEPARATOR);
139         }
140 
141         if (withTimestamp) {
142             appenderMemberWithLongValue(sb, TIMESTAMP_ATTR_NAME, event.getTimeStamp());
143             sb.append(VALUE_SEPARATOR);
144         }
145 
146         if (withNanoseconds) {
147             appenderMemberWithLongValue(sb, NANOSECONDS_ATTR_NAME, event.getNanoseconds());
148             sb.append(VALUE_SEPARATOR);
149         }
150 
151         if (withLevel) {
152             String levelStr = event.getLevel() != null ? event.getLevel().levelStr : NULL_STR;
153             appenderMember(sb, LEVEL_ATTR_NAME, levelStr);
154             sb.append(VALUE_SEPARATOR);
155         }
156 
157         if (withThreadName) {
158             appenderMember(sb, THREAD_NAME_ATTR_NAME, jsonEscape(event.getThreadName()));
159             sb.append(VALUE_SEPARATOR);
160         }
161 
162         if (withLoggerName) {
163             appenderMember(sb, LOGGER_ATTR_NAME, event.getLoggerName());
164             sb.append(VALUE_SEPARATOR);
165         }
166 
167         if (withContext) {
168             appendLoggerContext(sb, event.getLoggerContextVO());
169             sb.append(VALUE_SEPARATOR);
170         }
171         if (withMarkers)
172             appendMarkers(sb, event);
173 
174         if (withMDC)
175             appendMDC(sb, event);
176 
177         if (withKVPList)
178             appendKeyValuePairs(sb, event);
179 
180         if (withMessage) {
181             appenderMember(sb, MESSAGE_ATTR_NAME, jsonEscape(event.getMessage()));
182             sb.append(VALUE_SEPARATOR);
183         }
184 
185         if (withFormattedMessage) {
186             appenderMember(sb, FORMATTED_MESSAGE_ATTR_NAME, jsonEscape(event.getFormattedMessage()));
187             sb.append(VALUE_SEPARATOR);
188         }
189 
190         if (withArguments)
191             appendArgumentArray(sb, event);
192 
193         if (withThrowable)
194             appendThrowableProxy(sb, THROWABLE_ATTR_NAME, event.getThrowableProxy());
195 
196         sb.append(CLOSE_OBJ);
197         sb.append(CoreConstants.JSON_LINE_SEPARATOR);
198         return sb.toString().getBytes(UTF_8_CHARSET);
199     }
200 
201     private void appendLoggerContext(StringBuilder sb, LoggerContextVO loggerContextVO) {
202 
203         sb.append(QUOTE).append(CONTEXT_ATTR_NAME).append(QUOTE_COL);
204         if (loggerContextVO == null) {
205             sb.append(NULL_STR);
206             return;
207         }
208 
209         sb.append(OPEN_OBJ);
210         appenderMember(sb, NAME_ATTR_NAME, nullSafeStr(loggerContextVO.getName()));
211         sb.append(VALUE_SEPARATOR);
212         appenderMemberWithLongValue(sb, BIRTHDATE_ATTR_NAME, loggerContextVO.getBirthTime());
213         sb.append(VALUE_SEPARATOR);
214 
215         appendMap(sb, CONTEXT_PROPERTIES_ATTR_NAME, loggerContextVO.getPropertyMap());
216         sb.append(CLOSE_OBJ);
217 
218     }
219 
220     private void appendMap(StringBuilder sb, String attrName, Map<String, String> map) {
221         sb.append(QUOTE).append(attrName).append(QUOTE_COL);
222         if (map == null) {
223             sb.append(NULL_STR);
224             return;
225         }
226 
227         sb.append(OPEN_OBJ);
228 
229         boolean addComma = false;
230         Set<Map.Entry<String, String>> entries = map.entrySet();
231         for (Map.Entry<String, String> entry : entries) {
232             if (addComma) {
233                 sb.append(VALUE_SEPARATOR);
234             }
235             addComma = true;
236             appenderMember(sb, jsonEscapedToString(entry.getKey()), jsonEscapedToString(entry.getValue()));
237         }
238 
239         sb.append(CLOSE_OBJ);
240     }
241 
242     private void appendThrowableProxy(StringBuilder sb, String attributeName, IThrowableProxy itp) {
243 
244         // in the nominal case, attributeName != null. However, attributeName will be null for suppressed
245         // IThrowableProxy array, in which case no attribute name is needed
246         if (attributeName != null) {
247             sb.append(QUOTE).append(attributeName).append(QUOTE_COL);
248             if (itp == null) {
249                 sb.append(NULL_STR);
250                 return;
251             }
252         }
253 
254         sb.append(OPEN_OBJ);
255 
256         appenderMember(sb, CLASS_NAME_ATTR_NAME, nullSafeStr(itp.getClassName()));
257 
258         sb.append(VALUE_SEPARATOR);
259         appenderMember(sb, MESSAGE_ATTR_NAME, jsonEscape(itp.getMessage()));
260 
261         if (itp.isCyclic()) {
262             sb.append(VALUE_SEPARATOR);
263             appenderMember(sb, CYCLIC_THROWABLE_ATTR_NAME, jsonEscape("true"));
264         }
265 
266         sb.append(VALUE_SEPARATOR);
267         appendSTEPArray(sb, itp.getStackTraceElementProxyArray(), itp.getCommonFrames());
268 
269         if (itp.getCommonFrames() != 0) {
270             sb.append(VALUE_SEPARATOR);
271             appenderMemberWithIntValue(sb, COMMON_FRAMES_COUNT_ATTR_NAME, itp.getCommonFrames());
272         }
273 
274         IThrowableProxy cause = itp.getCause();
275         if (cause != null) {
276             sb.append(VALUE_SEPARATOR);
277             appendThrowableProxy(sb, CAUSE_ATTR_NAME, cause);
278         }
279 
280         IThrowableProxy[] suppressedArray = itp.getSuppressed();
281         if (suppressedArray != null && suppressedArray.length != 0) {
282             sb.append(VALUE_SEPARATOR);
283             sb.append(QUOTE).append(SUPPRESSED_ATTR_NAME).append(QUOTE_COL);
284             sb.append(OPEN_ARRAY);
285             boolean first = true;
286             for (IThrowableProxy suppressedITP : suppressedArray) {
287                 if (first) {
288                     first = false;
289                 } else {
290                     sb.append(VALUE_SEPARATOR);
291                 }
292                 appendThrowableProxy(sb, null, suppressedITP);
293             }
294             sb.append(CLOSE_ARRAY);
295         }
296 
297         sb.append(CLOSE_OBJ);
298 
299     }
300 
301     private void appendSTEPArray(StringBuilder sb, StackTraceElementProxy[] stepArray, int commonFrames) {
302         sb.append(QUOTE).append(STEP_ARRAY_NAME_ATTRIBUTE).append(QUOTE_COL).append(OPEN_ARRAY);
303 
304         int len = stepArray != null ? stepArray.length : 0;
305 
306         if (commonFrames >= len) {
307             commonFrames = 0;
308         }
309 
310         for (int i = 0; i < len - commonFrames; i++) {
311             if (i != 0)
312                 sb.append(VALUE_SEPARATOR);
313 
314             StackTraceElementProxy step = stepArray[i];
315 
316             sb.append(OPEN_OBJ);
317             StackTraceElement ste = step.getStackTraceElement();
318 
319             appenderMember(sb, CLASS_NAME_ATTR_NAME, nullSafeStr(ste.getClassName()));
320             sb.append(VALUE_SEPARATOR);
321 
322             appenderMember(sb, METHOD_NAME_ATTR_NAME, nullSafeStr(ste.getMethodName()));
323             sb.append(VALUE_SEPARATOR);
324 
325             appenderMember(sb, FILE_NAME_ATTR_NAME, nullSafeStr(ste.getFileName()));
326             sb.append(VALUE_SEPARATOR);
327 
328             appenderMemberWithIntValue(sb, LINE_NUMBER_ATTR_NAME, ste.getLineNumber());
329             sb.append(CLOSE_OBJ);
330 
331         }
332 
333         sb.append(CLOSE_ARRAY);
334     }
335 
336     private void appenderMember(StringBuilder sb, String key, String value) {
337         sb.append(QUOTE).append(key).append(QUOTE_COL).append(QUOTE).append(value).append(QUOTE);
338     }
339 
340     private void appenderMemberWithIntValue(StringBuilder sb, String key, int value) {
341         sb.append(QUOTE).append(key).append(QUOTE_COL).append(value);
342     }
343 
344     private void appenderMemberWithLongValue(StringBuilder sb, String key, long value) {
345         sb.append(QUOTE).append(key).append(QUOTE_COL).append(value);
346     }
347 
348     private void appendKeyValuePairs(StringBuilder sb, ILoggingEvent event) {
349         List<KeyValuePair> kvpList = event.getKeyValuePairs();
350         if (kvpList == null || kvpList.isEmpty())
351             return;
352 
353         sb.append(QUOTE).append(KEY_VALUE_PAIRS_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_ARRAY);
354         final int len = kvpList.size();
355         for (int i = 0; i < len; i++) {
356             if (i != 0)
357                 sb.append(VALUE_SEPARATOR);
358             KeyValuePair kvp = kvpList.get(i);
359             sb.append(OPEN_OBJ);
360             appenderMember(sb, jsonEscapedToString(kvp.key), jsonEscapedToString(kvp.value));
361             sb.append(CLOSE_OBJ);
362         }
363         sb.append(CLOSE_ARRAY);
364         sb.append(VALUE_SEPARATOR);
365     }
366 
367     private void appendArgumentArray(StringBuilder sb, ILoggingEvent event) {
368         Object[] argumentArray = event.getArgumentArray();
369         if (argumentArray == null)
370             return;
371 
372         sb.append(QUOTE).append(ARGUMENT_ARRAY_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_ARRAY);
373         final int len = argumentArray.length;
374         for (int i = 0; i < len; i++) {
375             if (i != 0)
376                 sb.append(VALUE_SEPARATOR);
377             sb.append(QUOTE).append(jsonEscapedToString(argumentArray[i])).append(QUOTE);
378 
379         }
380         sb.append(CLOSE_ARRAY);
381         sb.append(VALUE_SEPARATOR);
382     }
383 
384     private void appendMarkers(StringBuilder sb, ILoggingEvent event) {
385         List<Marker> markerList = event.getMarkerList();
386         if (markerList == null)
387             return;
388 
389         sb.append(QUOTE).append(MARKERS_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_ARRAY);
390         final int len = markerList.size();
391         for (int i = 0; i < len; i++) {
392             if (i != 0)
393                 sb.append(VALUE_SEPARATOR);
394             sb.append(QUOTE).append(jsonEscapedToString(markerList.get(i))).append(QUOTE);
395 
396         }
397         sb.append(CLOSE_ARRAY);
398         sb.append(VALUE_SEPARATOR);
399 
400     }
401 
402     private String jsonEscapedToString(Object o) {
403         if (o == null)
404             return NULL_STR;
405         return jsonEscapeString(o.toString());
406     }
407 
408     private String nullSafeStr(String s) {
409         if (s == null)
410             return NULL_STR;
411         return s;
412     }
413 
414     private String jsonEscape(String s) {
415         if (s == null)
416             return NULL_STR;
417         return jsonEscapeString(s);
418     }
419 
420     private void appendMDC(StringBuilder sb, ILoggingEvent event) {
421         Map<String, String> map = event.getMDCPropertyMap();
422 
423         sb.append(QUOTE).append(MDC_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_OBJ);
424         if (isNotEmptyMap(map)) {
425             Set<Map.Entry<String, String>> entrySet = map.entrySet();
426             int i = 0;
427             for (Map.Entry<String, String> entry : entrySet) {
428                 if (i != 0)
429                     sb.append(VALUE_SEPARATOR);
430                 appenderMember(sb, jsonEscapedToString(entry.getKey()), jsonEscapedToString(entry.getValue()));
431                 i++;
432             }
433 
434         }
435         sb.append(CLOSE_OBJ);
436         sb.append(VALUE_SEPARATOR);
437 
438     }
439 
440     boolean isNotEmptyMap(Map map) {
441         if (map == null)
442             return false;
443         return !map.isEmpty();
444     }
445 
446     @Override
447     public byte[] footerBytes() {
448         return EMPTY_BYTES;
449     }
450 
451     /**
452      * @param withSequenceNumber
453      * @since 1.5.0
454      */
455     public void setWithSequenceNumber(boolean withSequenceNumber) {
456         this.withSequenceNumber = withSequenceNumber;
457     }
458 
459     /**
460      * @param withTimestamp
461      * @since 1.5.0
462      */
463     public void setWithTimestamp(boolean withTimestamp) {
464         this.withTimestamp = withTimestamp;
465     }
466 
467     /**
468      * @param withNanoseconds
469      * @since 1.5.0
470      */
471     public void setWithNanoseconds(boolean withNanoseconds) {
472         this.withNanoseconds = withNanoseconds;
473     }
474 
475     public void setWithLevel(boolean withLevel) {
476         this.withLevel = withLevel;
477     }
478 
479     public void setWithThreadName(boolean withThreadName) {
480         this.withThreadName = withThreadName;
481     }
482 
483     public void setWithLoggerName(boolean withLoggerName) {
484         this.withLoggerName = withLoggerName;
485     }
486 
487     public void setWithContext(boolean withContext) {
488         this.withContext = withContext;
489     }
490 
491     public void setWithMarkers(boolean withMarkers) {
492         this.withMarkers = withMarkers;
493     }
494 
495     public void setWithMDC(boolean withMDC) {
496         this.withMDC = withMDC;
497     }
498 
499     public void setWithKVPList(boolean withKVPList) {
500         this.withKVPList = withKVPList;
501     }
502 
503     public void setWithMessage(boolean withMessage) {
504         this.withMessage = withMessage;
505     }
506 
507     public void setWithArguments(boolean withArguments) {
508         this.withArguments = withArguments;
509     }
510 
511     public void setWithThrowable(boolean withThrowable) {
512         this.withThrowable = withThrowable;
513     }
514 
515     public void setWithFormattedMessage(boolean withFormattedMessage) {
516         this.withFormattedMessage = withFormattedMessage;
517     }
518 
519 }