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