001/* 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2023, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014 015package ch.qos.logback.classic.encoder; 016 017import ch.qos.logback.classic.spi.ILoggingEvent; 018import ch.qos.logback.classic.spi.IThrowableProxy; 019import ch.qos.logback.classic.spi.LoggerContextVO; 020import ch.qos.logback.classic.spi.StackTraceElementProxy; 021import ch.qos.logback.core.CoreConstants; 022import ch.qos.logback.core.encoder.EncoderBase; 023import org.slf4j.Marker; 024import org.slf4j.event.KeyValuePair; 025 026import java.util.Arrays; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030 031import static ch.qos.logback.core.CoreConstants.COLON_CHAR; 032import static ch.qos.logback.core.CoreConstants.COMMA_CHAR; 033import static ch.qos.logback.core.CoreConstants.DOUBLE_QUOTE_CHAR; 034import static ch.qos.logback.core.CoreConstants.UTF_8_CHARSET; 035import static ch.qos.logback.core.encoder.JsonEscapeUtil.jsonEscapeString; 036import static ch.qos.logback.core.model.ModelConstants.NULL_STR; 037 038/** 039 * 040 * 041 * https://jsonlines.org/ https://datatracker.ietf.org/doc/html/rfc8259 042 */ 043public class JsonEncoder extends EncoderBase<ILoggingEvent> { 044 static final boolean DO_NOT_ADD_QUOTE_KEY = false; 045 static final boolean ADD_QUOTE_KEY = true; 046 static int DEFAULT_SIZE = 1024; 047 static int DEFAULT_SIZE_WITH_THROWABLE = DEFAULT_SIZE * 8; 048 049 static byte[] EMPTY_BYTES = new byte[0]; 050 051 public static final String CONTEXT_ATTR_NAME = "context"; 052 public static final String NAME_ATTR_NAME = "name"; 053 public static final String BIRTHDATE_ATTR_NAME = "birthdate"; 054 public static final String CONTEXT_PROPERTIES_ATTR_NAME = "properties"; 055 056 public static final String TIMESTAMP_ATTR_NAME = "timestamp"; 057 058 public static final String NANOSECONDS_ATTR_NAME = "nanoseconds"; 059 060 public static final String SEQUENCE_NUMBER_ATTR_NAME = "sequenceNumber"; 061 062 public static final String LEVEL_ATTR_NAME = "level"; 063 public static final String MARKERS_ATTR_NAME = "markers"; 064 public static final String THREAD_NAME_ATTR_NAME = "threadName"; 065 public static final String MDC_ATTR_NAME = "mdc"; 066 public static final String LOGGER_ATTR_NAME = "loggerName"; 067 068 public static final String MESSAGE_ATTR_NAME = "message"; 069 070 public static final String FORMATTED_MESSAGE_ATTR_NAME = "formattedMessage"; 071 072 public static final String ARGUMENT_ARRAY_ATTR_NAME = "arguments"; 073 public static final String KEY_VALUE_PAIRS_ATTR_NAME = "kvpList"; 074 075 public static final String THROWABLE_ATTR_NAME = "throwable"; 076 077 private static final String CYCLIC_THROWABLE_ATTR_NAME = "cyclic"; 078 079 public static final String CAUSE_ATTR_NAME = "cause"; 080 081 public static final String SUPPRESSED_ATTR_NAME = "suppressed"; 082 083 public static final String COMMON_FRAMES_COUNT_ATTR_NAME = "commonFramesCount"; 084 085 public static final String CLASS_NAME_ATTR_NAME = "className"; 086 public static final String METHOD_NAME_ATTR_NAME = "methodName"; 087 private static final String FILE_NAME_ATTR_NAME = "fileName"; 088 private static final String LINE_NUMBER_ATTR_NAME = "lineNumber"; 089 090 public static final String STEP_ARRAY_NAME_ATTRIBUTE = "stepArray"; 091 092 private static final char OPEN_OBJ = '{'; 093 private static final char CLOSE_OBJ = '}'; 094 private static final char OPEN_ARRAY = '['; 095 private static final char CLOSE_ARRAY = ']'; 096 097 private static final char QUOTE = DOUBLE_QUOTE_CHAR; 098 private static final char SP = ' '; 099 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}