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.List; 027import java.util.Map; 028import java.util.Set; 029 030import static ch.qos.logback.core.CoreConstants.COLON_CHAR; 031import static ch.qos.logback.core.CoreConstants.COMMA_CHAR; 032import static ch.qos.logback.core.CoreConstants.DOUBLE_QUOTE_CHAR; 033import static ch.qos.logback.core.CoreConstants.UTF_8_CHARSET; 034import static ch.qos.logback.core.encoder.JsonEscapeUtil.jsonEscapeString; 035import static ch.qos.logback.core.model.ModelConstants.NULL_STR; 036 037/** 038 * 039 * 040 * http://ndjson.org/ https://datatracker.ietf.org/doc/html/rfc8259 041 */ 042public class JsonEncoder extends EncoderBase<ILoggingEvent> { 043 static final boolean DO_NOT_ADD_QUOTE_KEY = false; 044 static final boolean ADD_QUOTE_KEY = true; 045 static int DEFAULT_SIZE = 1024; 046 static int DEFAULT_SIZE_WITH_THROWABLE = DEFAULT_SIZE * 8; 047 048 static byte[] EMPTY_BYTES = new byte[0]; 049 050 public static final String CONTEXT_ATTR_NAME = "context"; 051 public static final String NAME_ATTR_NAME = "name"; 052 public static final String BIRTHDATE_ATTR_NAME = "birthdate"; 053 public static final String CONTEXT_PROPERTIES_ATTR_NAME = "properties"; 054 055 public static final String TIMESTAMP_ATTR_NAME = "timestamp"; 056 057 public static final String NANOSECONDS_ATTR_NAME = "nanoseconds"; 058 059 public static final String SEQUENCE_NUMBER_ATTR_NAME = "sequenceNumber"; 060 061 public static final String LEVEL_ATTR_NAME = "level"; 062 public static final String MARKERS_ATTR_NAME = "markers"; 063 public static final String THREAD_NAME_ATTR_NAME = "threadName"; 064 public static final String MDC_ATTR_NAME = "mdc"; 065 public static final String LOGGER_ATTR_NAME = "loggerName"; 066 067 public static final String MESSAGE_ATTR_NAME = "message"; 068 069 public static final String FORMATTED_MESSAGE_ATTR_NAME = "formattedMessage"; 070 071 public static final String ARGUMENT_ARRAY_ATTR_NAME = "arguments"; 072 public static final String KEY_VALUE_PAIRS_ATTR_NAME = "kvpList"; 073 074 public static final String THROWABLE_ATTR_NAME = "throwable"; 075 076 private static final String CYCLIC_THROWABLE_ATTR_NAME = "cyclic"; 077 078 public static final String CAUSE_ATTR_NAME = "cause"; 079 080 public static final String SUPPRESSED_ATTR_NAME = "suppressed"; 081 082 public static final String COMMON_FRAMES_COUNT_ATTR_NAME = "commonFramesCount"; 083 084 public static final String CLASS_NAME_ATTR_NAME = "className"; 085 public static final String METHOD_NAME_ATTR_NAME = "methodName"; 086 private static final String FILE_NAME_ATTR_NAME = "fileName"; 087 private static final String LINE_NUMBER_ATTR_NAME = "lineNumber"; 088 089 public static final String STEP_ARRAY_NAME_ATTRIBUTE = "stepArray"; 090 091 private static final char OPEN_OBJ = '{'; 092 private static final char CLOSE_OBJ = '}'; 093 private static final char OPEN_ARRAY = '['; 094 private static final char CLOSE_ARRAY = ']'; 095 096 private static final char QUOTE = DOUBLE_QUOTE_CHAR; 097 private static final char SP = ' '; 098 private static final char ENTRY_SEPARATOR = COLON_CHAR; 099 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}