1
2
3
4
5
6
7
8
9
10
11
12
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
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
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
266
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
468
469
470 public void setWithSequenceNumber(boolean withSequenceNumber) {
471 this.withSequenceNumber = withSequenceNumber;
472 }
473
474
475
476
477
478 public void setWithTimestamp(boolean withTimestamp) {
479 this.withTimestamp = withTimestamp;
480 }
481
482
483
484
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 }