ch.qos.logback.classic.encoder.JsonEncoder Maven / Gradle / Ivy
/*
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2023, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/
package ch.qos.logback.classic.encoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.LoggerContextVO;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.encoder.EncoderBase;
import org.slf4j.Marker;
import org.slf4j.event.KeyValuePair;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static ch.qos.logback.core.CoreConstants.COLON_CHAR;
import static ch.qos.logback.core.CoreConstants.COMMA_CHAR;
import static ch.qos.logback.core.CoreConstants.DOUBLE_QUOTE_CHAR;
import static ch.qos.logback.core.CoreConstants.UTF_8_CHARSET;
import static ch.qos.logback.core.encoder.JsonEscapeUtil.jsonEscapeString;
import static ch.qos.logback.core.model.ModelConstants.NULL_STR;
/**
*
*
* https://jsonlines.org/ https://datatracker.ietf.org/doc/html/rfc8259
*/
public class JsonEncoder extends EncoderBase {
static final boolean DO_NOT_ADD_QUOTE_KEY = false;
static final boolean ADD_QUOTE_KEY = true;
static int DEFAULT_SIZE = 1024;
static int DEFAULT_SIZE_WITH_THROWABLE = DEFAULT_SIZE * 8;
static byte[] EMPTY_BYTES = new byte[0];
public static final String CONTEXT_ATTR_NAME = "context";
public static final String NAME_ATTR_NAME = "name";
public static final String BIRTHDATE_ATTR_NAME = "birthdate";
public static final String CONTEXT_PROPERTIES_ATTR_NAME = "properties";
public static final String TIMESTAMP_ATTR_NAME = "timestamp";
public static final String NANOSECONDS_ATTR_NAME = "nanoseconds";
public static final String SEQUENCE_NUMBER_ATTR_NAME = "sequenceNumber";
public static final String LEVEL_ATTR_NAME = "level";
public static final String MARKERS_ATTR_NAME = "markers";
public static final String THREAD_NAME_ATTR_NAME = "threadName";
public static final String MDC_ATTR_NAME = "mdc";
public static final String LOGGER_ATTR_NAME = "loggerName";
public static final String MESSAGE_ATTR_NAME = "message";
public static final String FORMATTED_MESSAGE_ATTR_NAME = "formattedMessage";
public static final String ARGUMENT_ARRAY_ATTR_NAME = "arguments";
public static final String KEY_VALUE_PAIRS_ATTR_NAME = "kvpList";
public static final String THROWABLE_ATTR_NAME = "throwable";
private static final String CYCLIC_THROWABLE_ATTR_NAME = "cyclic";
public static final String CAUSE_ATTR_NAME = "cause";
public static final String SUPPRESSED_ATTR_NAME = "suppressed";
public static final String COMMON_FRAMES_COUNT_ATTR_NAME = "commonFramesCount";
public static final String CLASS_NAME_ATTR_NAME = "className";
public static final String METHOD_NAME_ATTR_NAME = "methodName";
private static final String FILE_NAME_ATTR_NAME = "fileName";
private static final String LINE_NUMBER_ATTR_NAME = "lineNumber";
public static final String STEP_ARRAY_NAME_ATTRIBUTE = "stepArray";
private static final char OPEN_OBJ = '{';
private static final char CLOSE_OBJ = '}';
private static final char OPEN_ARRAY = '[';
private static final char CLOSE_ARRAY = ']';
private static final char QUOTE = DOUBLE_QUOTE_CHAR;
private static final char SP = ' ';
private static final char ENTRY_SEPARATOR = COLON_CHAR;
private static final String COL_SP = ": ";
private static final String QUOTE_COL = "\":";
private static final char VALUE_SEPARATOR = COMMA_CHAR;
private boolean withSequenceNumber = true;
private boolean withTimestamp = true;
private boolean withNanoseconds = true;
private boolean withLevel = true;
private boolean withThreadName = true;
private boolean withLoggerName = true;
private boolean withContext = true;
private boolean withMarkers = true;
private boolean withMDC = true;
private boolean withKVPList = true;
private boolean withMessage = true;
private boolean withArguments = true;
private boolean withThrowable = true;
private boolean withFormattedMessage = false;
@Override
public byte[] headerBytes() {
return EMPTY_BYTES;
}
@Override
public byte[] encode(ILoggingEvent event) {
final int initialCapacity = event.getThrowableProxy() == null ? DEFAULT_SIZE : DEFAULT_SIZE_WITH_THROWABLE;
StringBuilder sb = new StringBuilder(initialCapacity);
sb.append(OPEN_OBJ);
if (withSequenceNumber) {
appenderMemberWithLongValue(sb, SEQUENCE_NUMBER_ATTR_NAME, event.getSequenceNumber());
}
if (withTimestamp) {
appendValueSeparator(sb, withSequenceNumber);
appenderMemberWithLongValue(sb, TIMESTAMP_ATTR_NAME, event.getTimeStamp());
}
if (withNanoseconds) {
appendValueSeparator(sb, withSequenceNumber, withTimestamp);
appenderMemberWithLongValue(sb, NANOSECONDS_ATTR_NAME, event.getNanoseconds());
}
if (withLevel) {
appendValueSeparator(sb, withNanoseconds, withSequenceNumber, withTimestamp);
String levelStr = event.getLevel() != null ? event.getLevel().levelStr : NULL_STR;
appenderMember(sb, LEVEL_ATTR_NAME, levelStr);
}
if (withThreadName) {
appendValueSeparator(sb, withLevel, withNanoseconds, withSequenceNumber, withTimestamp);
appenderMember(sb, THREAD_NAME_ATTR_NAME, jsonEscape(event.getThreadName()));
}
if (withLoggerName) {
appendValueSeparator(sb, withThreadName, withLevel, withNanoseconds, withSequenceNumber, withTimestamp);
appenderMember(sb, LOGGER_ATTR_NAME, event.getLoggerName());
}
if (withContext) {
// at this stage we assume that at least one field was written
sb.append(VALUE_SEPARATOR);
appendLoggerContext(sb, event.getLoggerContextVO());
}
if (withMarkers)
appendMarkers(sb, event);
if (withMDC)
appendMDC(sb, event);
if (withKVPList)
appendKeyValuePairs(sb, event);
if (withMessage) {
sb.append(VALUE_SEPARATOR);
appenderMember(sb, MESSAGE_ATTR_NAME, jsonEscape(event.getMessage()));
}
if (withFormattedMessage) {
sb.append(VALUE_SEPARATOR);
appenderMember(sb, FORMATTED_MESSAGE_ATTR_NAME, jsonEscape(event.getFormattedMessage()));
}
if (withArguments) {
appendArgumentArray(sb, event);
}
if (withThrowable)
appendThrowableProxy(sb, THROWABLE_ATTR_NAME, event.getThrowableProxy());
sb.append(CLOSE_OBJ);
sb.append(CoreConstants.JSON_LINE_SEPARATOR);
return sb.toString().getBytes(UTF_8_CHARSET);
}
void appendValueSeparator(StringBuilder sb, boolean... subsequentConditionals) {
boolean enabled = false;
for (boolean subsequent : subsequentConditionals) {
if (subsequent) {
enabled = true;
break;
}
}
if (enabled)
sb.append(VALUE_SEPARATOR);
}
private void appendLoggerContext(StringBuilder sb, LoggerContextVO loggerContextVO) {
sb.append(QUOTE).append(CONTEXT_ATTR_NAME).append(QUOTE_COL);
if (loggerContextVO == null) {
sb.append(NULL_STR);
return;
}
sb.append(OPEN_OBJ);
appenderMember(sb, NAME_ATTR_NAME, nullSafeStr(loggerContextVO.getName()));
sb.append(VALUE_SEPARATOR);
appenderMemberWithLongValue(sb, BIRTHDATE_ATTR_NAME, loggerContextVO.getBirthTime());
sb.append(VALUE_SEPARATOR);
appendMap(sb, CONTEXT_PROPERTIES_ATTR_NAME, loggerContextVO.getPropertyMap());
sb.append(CLOSE_OBJ);
}
private void appendMap(StringBuilder sb, String attrName, Map map) {
sb.append(QUOTE).append(attrName).append(QUOTE_COL);
if (map == null) {
sb.append(NULL_STR);
return;
}
sb.append(OPEN_OBJ);
boolean addComma = false;
Set> entries = map.entrySet();
for (Map.Entry entry : entries) {
if (addComma) {
sb.append(VALUE_SEPARATOR);
}
addComma = true;
appenderMember(sb, jsonEscapedToString(entry.getKey()), jsonEscapedToString(entry.getValue()));
}
sb.append(CLOSE_OBJ);
}
private void appendThrowableProxy(StringBuilder sb, String attributeName, IThrowableProxy itp) {
appendThrowableProxy(sb, attributeName, itp, true);
}
private void appendThrowableProxy(StringBuilder sb, String attributeName, IThrowableProxy itp, boolean appendValueSeparator) {
if (appendValueSeparator)
sb.append(VALUE_SEPARATOR);
// in the nominal case, attributeName != null. However, attributeName will be null for suppressed
// IThrowableProxy array, in which case no attribute name is needed
if (attributeName != null) {
sb.append(QUOTE).append(attributeName).append(QUOTE_COL);
if (itp == null) {
sb.append(NULL_STR);
return;
}
}
sb.append(OPEN_OBJ);
appenderMember(sb, CLASS_NAME_ATTR_NAME, nullSafeStr(itp.getClassName()));
sb.append(VALUE_SEPARATOR);
appenderMember(sb, MESSAGE_ATTR_NAME, jsonEscape(itp.getMessage()));
if (itp.isCyclic()) {
sb.append(VALUE_SEPARATOR);
appenderMember(sb, CYCLIC_THROWABLE_ATTR_NAME, jsonEscape("true"));
}
sb.append(VALUE_SEPARATOR);
appendSTEPArray(sb, itp.getStackTraceElementProxyArray(), itp.getCommonFrames());
if (itp.getCommonFrames() != 0) {
sb.append(VALUE_SEPARATOR);
appenderMemberWithIntValue(sb, COMMON_FRAMES_COUNT_ATTR_NAME, itp.getCommonFrames());
}
IThrowableProxy cause = itp.getCause();
if (cause != null) {
appendThrowableProxy(sb, CAUSE_ATTR_NAME, cause);
}
IThrowableProxy[] suppressedArray = itp.getSuppressed();
if (suppressedArray != null && suppressedArray.length != 0) {
sb.append(VALUE_SEPARATOR);
sb.append(QUOTE).append(SUPPRESSED_ATTR_NAME).append(QUOTE_COL);
sb.append(OPEN_ARRAY);
boolean first = true;
for (IThrowableProxy suppressedITP : suppressedArray) {
appendThrowableProxy(sb, null, suppressedITP, !first);
if (first)
first = false;
}
sb.append(CLOSE_ARRAY);
}
sb.append(CLOSE_OBJ);
}
private void appendSTEPArray(StringBuilder sb, StackTraceElementProxy[] stepArray, int commonFrames) {
sb.append(QUOTE).append(STEP_ARRAY_NAME_ATTRIBUTE).append(QUOTE_COL).append(OPEN_ARRAY);
int len = stepArray != null ? stepArray.length : 0;
if (commonFrames >= len) {
commonFrames = 0;
}
for (int i = 0; i < len - commonFrames; i++) {
if (i != 0)
sb.append(VALUE_SEPARATOR);
StackTraceElementProxy step = stepArray[i];
sb.append(OPEN_OBJ);
StackTraceElement ste = step.getStackTraceElement();
appenderMember(sb, CLASS_NAME_ATTR_NAME, nullSafeStr(ste.getClassName()));
sb.append(VALUE_SEPARATOR);
appenderMember(sb, METHOD_NAME_ATTR_NAME, nullSafeStr(ste.getMethodName()));
sb.append(VALUE_SEPARATOR);
appenderMember(sb, FILE_NAME_ATTR_NAME, nullSafeStr(ste.getFileName()));
sb.append(VALUE_SEPARATOR);
appenderMemberWithIntValue(sb, LINE_NUMBER_ATTR_NAME, ste.getLineNumber());
sb.append(CLOSE_OBJ);
}
sb.append(CLOSE_ARRAY);
}
private void appenderMember(StringBuilder sb, String key, String value) {
sb.append(QUOTE).append(key).append(QUOTE_COL).append(QUOTE).append(value).append(QUOTE);
}
private void appenderMemberWithIntValue(StringBuilder sb, String key, int value) {
sb.append(QUOTE).append(key).append(QUOTE_COL).append(value);
}
private void appenderMemberWithLongValue(StringBuilder sb, String key, long value) {
sb.append(QUOTE).append(key).append(QUOTE_COL).append(value);
}
private void appendKeyValuePairs(StringBuilder sb, ILoggingEvent event) {
List kvpList = event.getKeyValuePairs();
if (kvpList == null || kvpList.isEmpty())
return;
sb.append(VALUE_SEPARATOR);
sb.append(QUOTE).append(KEY_VALUE_PAIRS_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_ARRAY);
final int len = kvpList.size();
for (int i = 0; i < len; i++) {
if (i != 0)
sb.append(VALUE_SEPARATOR);
KeyValuePair kvp = kvpList.get(i);
sb.append(OPEN_OBJ);
appenderMember(sb, jsonEscapedToString(kvp.key), jsonEscapedToString(kvp.value));
sb.append(CLOSE_OBJ);
}
sb.append(CLOSE_ARRAY);
}
private void appendArgumentArray(StringBuilder sb, ILoggingEvent event) {
Object[] argumentArray = event.getArgumentArray();
if (argumentArray == null)
return;
sb.append(VALUE_SEPARATOR);
sb.append(QUOTE).append(ARGUMENT_ARRAY_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_ARRAY);
final int len = argumentArray.length;
for (int i = 0; i < len; i++) {
if (i != 0)
sb.append(VALUE_SEPARATOR);
sb.append(QUOTE).append(jsonEscapedToString(argumentArray[i])).append(QUOTE);
}
sb.append(CLOSE_ARRAY);
}
private void appendMarkers(StringBuilder sb, ILoggingEvent event) {
List markerList = event.getMarkerList();
if (markerList == null)
return;
sb.append(VALUE_SEPARATOR);
sb.append(QUOTE).append(MARKERS_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_ARRAY);
final int len = markerList.size();
for (int i = 0; i < len; i++) {
if (i != 0)
sb.append(VALUE_SEPARATOR);
sb.append(QUOTE).append(jsonEscapedToString(markerList.get(i))).append(QUOTE);
}
sb.append(CLOSE_ARRAY);
}
private String jsonEscapedToString(Object o) {
if (o == null)
return NULL_STR;
return jsonEscapeString(o.toString());
}
private String nullSafeStr(String s) {
if (s == null)
return NULL_STR;
return s;
}
private String jsonEscape(String s) {
if (s == null)
return NULL_STR;
return jsonEscapeString(s);
}
private void appendMDC(StringBuilder sb, ILoggingEvent event) {
Map map = event.getMDCPropertyMap();
sb.append(VALUE_SEPARATOR);
sb.append(QUOTE).append(MDC_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_OBJ);
if (isNotEmptyMap(map)) {
Set> entrySet = map.entrySet();
int i = 0;
for (Map.Entry entry : entrySet) {
if (i != 0)
sb.append(VALUE_SEPARATOR);
appenderMember(sb, jsonEscapedToString(entry.getKey()), jsonEscapedToString(entry.getValue()));
i++;
}
}
sb.append(CLOSE_OBJ);
}
boolean isNotEmptyMap(Map map) {
if (map == null)
return false;
return !map.isEmpty();
}
@Override
public byte[] footerBytes() {
return EMPTY_BYTES;
}
/**
* @param withSequenceNumber
* @since 1.5.0
*/
public void setWithSequenceNumber(boolean withSequenceNumber) {
this.withSequenceNumber = withSequenceNumber;
}
/**
* @param withTimestamp
* @since 1.5.0
*/
public void setWithTimestamp(boolean withTimestamp) {
this.withTimestamp = withTimestamp;
}
/**
* @param withNanoseconds
* @since 1.5.0
*/
public void setWithNanoseconds(boolean withNanoseconds) {
this.withNanoseconds = withNanoseconds;
}
public void setWithLevel(boolean withLevel) {
this.withLevel = withLevel;
}
public void setWithThreadName(boolean withThreadName) {
this.withThreadName = withThreadName;
}
public void setWithLoggerName(boolean withLoggerName) {
this.withLoggerName = withLoggerName;
}
public void setWithContext(boolean withContext) {
this.withContext = withContext;
}
public void setWithMarkers(boolean withMarkers) {
this.withMarkers = withMarkers;
}
public void setWithMDC(boolean withMDC) {
this.withMDC = withMDC;
}
public void setWithKVPList(boolean withKVPList) {
this.withKVPList = withKVPList;
}
public void setWithMessage(boolean withMessage) {
this.withMessage = withMessage;
}
public void setWithArguments(boolean withArguments) {
this.withArguments = withArguments;
}
public void setWithThrowable(boolean withThrowable) {
this.withThrowable = withThrowable;
}
public void setWithFormattedMessage(boolean withFormattedMessage) {
this.withFormattedMessage = withFormattedMessage;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy