Skip to content

Commit

Permalink
add support for throwable cause and suppressed[] in JsonEncoder, this…
Browse files Browse the repository at this point in the history
… fixes LOGBACK-1749

Signed-off-by: Ceki Gulcu <ceki@qos.ch>
  • Loading branch information
ceki committed Jul 7, 2023
1 parent 291eb52 commit cdfa662
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 38 deletions.
Expand Up @@ -18,6 +18,7 @@
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.classic.spi.ThrowableProxy;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.encoder.EncoderBase;
import org.slf4j.Marker;
Expand All @@ -30,6 +31,7 @@
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.SUPPRESSED;
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;
Expand Down Expand Up @@ -71,12 +73,19 @@ public class JsonEncoder extends EncoderBase<ILoggingEvent> {

public static final String THROWABLE_ATTR_NAME = "throwable";

private static final String CLASS_NAME_ATTR_NAME = "className";
private static final String METHOD_NAME_ATTR_NAME = "methodName";
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";

private static final String STEP_ARRAY_NAME_ATTRIBUTE = "stepArray";
public static final String STEP_ARRAY_NAME_ATTRIBUTE = "stepArray";

private static final char OPEN_OBJ = '{';
private static final char CLOSE_OBJ = '}';
Expand Down Expand Up @@ -139,7 +148,7 @@ public byte[] encode(ILoggingEvent event) {

appendArgumentArray(sb, event);

appendThrowableProxy(sb, event);
appendThrowableProxy(sb, THROWABLE_ATTR_NAME, event.getThrowableProxy());
sb.append(CLOSE_OBJ);
sb.append(CoreConstants.JSON_LINE_SEPARATOR);
return sb.toString().getBytes(UTF_8_CHARSET);
Expand Down Expand Up @@ -174,7 +183,6 @@ private void appendMap(StringBuilder sb, String attrName, Map<String, String> ma

sb.append(OPEN_OBJ);


boolean addComma = false;
Set<Map.Entry<String, String>> entries = map.entrySet();
for(Map.Entry<String, String> entry: entries) {
Expand All @@ -186,18 +194,17 @@ private void appendMap(StringBuilder sb, String attrName, Map<String, String> ma
}

sb.append(CLOSE_OBJ);



}


private void appendThrowableProxy(StringBuilder sb, ILoggingEvent event) {
IThrowableProxy itp = event.getThrowableProxy();
sb.append(QUOTE).append(THROWABLE_ATTR_NAME).append(QUOTE_COL);
if (itp == null) {
sb.append(NULL_STR);
return;
private void appendThrowableProxy(StringBuilder sb, String attributeName, IThrowableProxy itp) {

if(attributeName != null) {
sb.append(QUOTE).append(attributeName).append(QUOTE_COL);
if (itp == null) {
sb.append(NULL_STR);
return;
}
}

sb.append(OPEN_OBJ);
Expand All @@ -206,13 +213,50 @@ private void appendThrowableProxy(StringBuilder sb, ILoggingEvent event) {
sb.append(VALUE_SEPARATOR);
appenderMember(sb, MESSAGE_ATTR_NAME, jsonEscape(itp.getMessage()));
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) {
sb.append(VALUE_SEPARATOR);
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) {
if(first) {
first = false;
} else {
sb.append(VALUE_SEPARATOR);
}
appendThrowableProxy(sb, null, suppressedITP);
}
sb.append(CLOSE_ARRAY);
}


sb.append(CLOSE_OBJ);

StackTraceElementProxy[] stepArray = itp.getStackTraceElementProxyArray();
}

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;
for (int i = 0; i < len; i++) {

if(commonFrames >= len) {
commonFrames = 0;
}

for (int i = 0; i < len - commonFrames; i++) {
if (i != 0)
sb.append(VALUE_SEPARATOR);

Expand All @@ -236,8 +280,6 @@ private void appendThrowableProxy(StringBuilder sb, ILoggingEvent event) {
}

sb.append(CLOSE_ARRAY);
sb.append(CLOSE_OBJ);

}

private void appenderMember(StringBuilder sb, String key, String value) {
Expand Down
Expand Up @@ -97,7 +97,7 @@ void smoke() throws JsonProcessingException {

byte[] resultBytes = jsonEncoder.encode(event);
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
System.out.println(resultString);
//System.out.println(resultString);

JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
compareEvents(event, resultEvent);
Expand All @@ -113,7 +113,7 @@ void contextWithProperties() throws JsonProcessingException {

byte[] resultBytes = jsonEncoder.encode(event);
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
System.out.println(resultString);
// System.out.println(resultString);

JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
compareEvents(event, resultEvent);
Expand Down Expand Up @@ -200,7 +200,7 @@ void withMarkers() throws JsonProcessingException {

byte[] resultBytes = jsonEncoder.encode(event);
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
System.out.println(resultString);
//System.out.println(resultString);

JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
compareEvents(event, resultEvent);
Expand All @@ -212,7 +212,7 @@ void withArguments() throws JsonProcessingException {

byte[] resultBytes = jsonEncoder.encode(event);
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
System.out.println(resultString);
//System.out.println(resultString);

JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
compareEvents(event, resultEvent);
Expand All @@ -227,7 +227,7 @@ void withKeyValuePairs() throws JsonProcessingException {

byte[] resultBytes = jsonEncoder.encode(event);
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
System.out.println(resultString);
//System.out.println(resultString);
JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
compareEvents(event, resultEvent);
}
Expand All @@ -248,7 +248,7 @@ void withMDC() throws JsonProcessingException {

byte[] resultBytes = jsonEncoder.encode(event);
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
System.out.println(resultString);
//System.out.println(resultString);
JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
compareEvents(event, resultEvent);
}
Expand All @@ -260,7 +260,38 @@ void withThrowable() throws JsonProcessingException {

byte[] resultBytes = jsonEncoder.encode(event);
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
System.out.println(resultString);
JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
compareEvents(event, resultEvent);
}

@Test
void withThrowableHavingCause() throws JsonProcessingException {
Throwable cause = new IllegalStateException("test cause");

Throwable t = new RuntimeException("test", cause);


LoggingEvent event = new LoggingEvent("in withThrowableHavingCause test", logger, Level.WARN, "hello kvp", t, null);

byte[] resultBytes = jsonEncoder.encode(event);
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
//System.out.println(resultString);
JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
compareEvents(event, resultEvent);
}

@Test
void withThrowableHavingSuppressed() throws JsonProcessingException {
Throwable suppressed = new IllegalStateException("test suppressed");

Throwable t = new RuntimeException("test");
t.addSuppressed(suppressed);

LoggingEvent event = new LoggingEvent("in withThrowableHavingCause test", logger, Level.WARN, "hello kvp", t, null);

byte[] resultBytes = jsonEncoder.encode(event);
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
//System.out.println(resultString);
JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
compareEvents(event, resultEvent);
}
Expand Down
Expand Up @@ -17,6 +17,7 @@
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.encoder.JsonEncoder;
import ch.qos.logback.classic.spi.LoggerContextVO;
import ch.qos.logback.classic.spi.PubThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
Expand Down Expand Up @@ -47,6 +48,7 @@ public JsonLoggingEvent mapStringToLoggingEvent(String resultString) throws Json
module.addDeserializer(Marker.class, new MarkerDeserializer(markerFactory));
module.addDeserializer(KeyValuePair.class, new KeyValuePairDeserializer());
module.addDeserializer(LoggerContextVO.class, new LoggerContextVODeserializer());
module.addDeserializer(PubThrowableProxy.class, new PubThrowableProxyDeserializer());

objectMapper.registerModule(module);

Expand Down
@@ -0,0 +1,110 @@
/*
* 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.jsonTest;

import ch.qos.logback.classic.encoder.JsonEncoder;
import ch.qos.logback.classic.spi.PubThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class PubThrowableProxyDeserializer extends StdDeserializer<PubThrowableProxy> {

protected PubThrowableProxyDeserializer() {
this(null);
}
protected PubThrowableProxyDeserializer(Class<?> vc) {
super(vc);
}

@Override
public PubThrowableProxy deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JacksonException {
JsonNode node = jsonParser.getCodec().readTree(jsonParser);

return jsonNodeThrowableProxy(node);
}

static StackTraceElementProxy[] EMPTY_STEP_ARRAY = new StackTraceElementProxy[0];
static PubThrowableProxy[] EMPTY_PTP_ARRAY = new PubThrowableProxy[0];

private static PubThrowableProxy jsonNodeThrowableProxy(JsonNode node) {
JsonNode classNameJN = node.get(JsonEncoder.CLASS_NAME_ATTR_NAME);
JsonNode messageJN = node.get(JsonEncoder.MESSAGE_ATTR_NAME);
JsonNode stepArrayJN = node.get(JsonEncoder.STEP_ARRAY_NAME_ATTRIBUTE);
JsonNode causeJN = node.get(JsonEncoder.CAUSE_ATTR_NAME);
JsonNode commonFramesCountJN = node.get(JsonEncoder.COMMON_FRAMES_COUNT_ATTR_NAME);

JsonNode suppressedJN = node.get(JsonEncoder.SUPPRESSED_ATTR_NAME);

PubThrowableProxy ptp = new PubThrowableProxy();
ptp.setClassName(classNameJN.textValue());
ptp.setMessage(messageJN.textValue());

List<StackTraceElementProxy> stepList = stepNodeToList(stepArrayJN);
ptp.setStackTraceElementProxyArray(stepList.toArray(EMPTY_STEP_ARRAY));

if(commonFramesCountJN != null) {
int commonFramesCount = commonFramesCountJN.asInt();
ptp.setCommonFramesCount(commonFramesCount);
}

if(causeJN != null) {
PubThrowableProxy cause = jsonNodeThrowableProxy(causeJN);
ptp.setCause(cause);
}

if(suppressedJN != null) {
//System.out.println("suppressedJN "+suppressedJN);
List<PubThrowableProxy> ptpList = suppressedNodeToList(suppressedJN);
System.out.println("iiiiiiiiiiii");
System.out.println("ptpList="+ptpList);

ptp.setSuppressed(ptpList.toArray(EMPTY_PTP_ARRAY));
}

System.out.println("xxxxxxxxxxxxx");
System.out.println(ptp.getSuppressed());

return ptp;
}

private static List<StackTraceElementProxy> stepNodeToList(JsonNode stepArrayJN) {
List<StackTraceElementProxy> stepList = new ArrayList<>();
for(JsonNode jsonNode: stepArrayJN) {
StackTraceElementProxy step = STEPDeserializer.jsonNodeToSTEP(jsonNode);
stepList.add(step);
}
return stepList;
}

private static List<PubThrowableProxy> suppressedNodeToList(JsonNode ptpArrayJN) {
List<PubThrowableProxy> ptpList = new ArrayList<>();
for(JsonNode jsonNode: ptpArrayJN) {
//System.out.println("---in suppressedNodeToList seeing "+jsonNode);
PubThrowableProxy ptp = jsonNodeThrowableProxy(jsonNode);
//System.out.println("--in suppressedNodeToList ptp="+ptp);
ptpList.add(ptp);
}
return ptpList;
}
}
Expand Up @@ -38,6 +38,10 @@ public STEPDeserializer(Class<?> vc) {
public StackTraceElementProxy deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
return jsonNodeToSTEP(node);
}

public static StackTraceElementProxy jsonNodeToSTEP(JsonNode node) {
String className = node.get("className").asText();
String methodName = node.get("methodName").asText();
String fileName = node.get("fileName").asText();
Expand Down

0 comments on commit cdfa662

Please sign in to comment.