Skip to content

Commit cdfa662

Browse files
committed
add support for throwable cause and suppressed[] in JsonEncoder, this fixes LOGBACK-1749
Signed-off-by: Ceki Gulcu <ceki@qos.ch>
1 parent 291eb52 commit cdfa662

File tree

7 files changed

+279
-38
lines changed

7 files changed

+279
-38
lines changed

logback-classic/src/main/java/ch/qos/logback/classic/encoder/JsonEncoder.java

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import ch.qos.logback.classic.spi.IThrowableProxy;
1919
import ch.qos.logback.classic.spi.LoggerContextVO;
2020
import ch.qos.logback.classic.spi.StackTraceElementProxy;
21+
import ch.qos.logback.classic.spi.ThrowableProxy;
2122
import ch.qos.logback.core.CoreConstants;
2223
import ch.qos.logback.core.encoder.EncoderBase;
2324
import org.slf4j.Marker;
@@ -30,6 +31,7 @@
3031
import static ch.qos.logback.core.CoreConstants.COLON_CHAR;
3132
import static ch.qos.logback.core.CoreConstants.COMMA_CHAR;
3233
import static ch.qos.logback.core.CoreConstants.DOUBLE_QUOTE_CHAR;
34+
import static ch.qos.logback.core.CoreConstants.SUPPRESSED;
3335
import static ch.qos.logback.core.CoreConstants.UTF_8_CHARSET;
3436
import static ch.qos.logback.core.encoder.JsonEscapeUtil.jsonEscapeString;
3537
import static ch.qos.logback.core.model.ModelConstants.NULL_STR;
@@ -71,12 +73,19 @@ public class JsonEncoder extends EncoderBase<ILoggingEvent> {
7173

7274
public static final String THROWABLE_ATTR_NAME = "throwable";
7375

74-
private static final String CLASS_NAME_ATTR_NAME = "className";
75-
private static final String METHOD_NAME_ATTR_NAME = "methodName";
76+
public static final String CAUSE_ATTR_NAME = "cause";
77+
78+
public static final String SUPPRESSED_ATTR_NAME = "suppressed";
79+
80+
81+
public static final String COMMON_FRAMES_COUNT_ATTR_NAME = "commonFramesCount";
82+
83+
public static final String CLASS_NAME_ATTR_NAME = "className";
84+
public static final String METHOD_NAME_ATTR_NAME = "methodName";
7685
private static final String FILE_NAME_ATTR_NAME = "fileName";
7786
private static final String LINE_NUMBER_ATTR_NAME = "lineNumber";
7887

79-
private static final String STEP_ARRAY_NAME_ATTRIBUTE = "stepArray";
88+
public static final String STEP_ARRAY_NAME_ATTRIBUTE = "stepArray";
8089

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

140149
appendArgumentArray(sb, event);
141150

142-
appendThrowableProxy(sb, event);
151+
appendThrowableProxy(sb, THROWABLE_ATTR_NAME, event.getThrowableProxy());
143152
sb.append(CLOSE_OBJ);
144153
sb.append(CoreConstants.JSON_LINE_SEPARATOR);
145154
return sb.toString().getBytes(UTF_8_CHARSET);
@@ -174,7 +183,6 @@ private void appendMap(StringBuilder sb, String attrName, Map<String, String> ma
174183

175184
sb.append(OPEN_OBJ);
176185

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

188196
sb.append(CLOSE_OBJ);
189-
190-
191-
192197
}
193198

194199

195-
private void appendThrowableProxy(StringBuilder sb, ILoggingEvent event) {
196-
IThrowableProxy itp = event.getThrowableProxy();
197-
sb.append(QUOTE).append(THROWABLE_ATTR_NAME).append(QUOTE_COL);
198-
if (itp == null) {
199-
sb.append(NULL_STR);
200-
return;
200+
private void appendThrowableProxy(StringBuilder sb, String attributeName, IThrowableProxy itp) {
201+
202+
if(attributeName != null) {
203+
sb.append(QUOTE).append(attributeName).append(QUOTE_COL);
204+
if (itp == null) {
205+
sb.append(NULL_STR);
206+
return;
207+
}
201208
}
202209

203210
sb.append(OPEN_OBJ);
@@ -206,13 +213,50 @@ private void appendThrowableProxy(StringBuilder sb, ILoggingEvent event) {
206213
sb.append(VALUE_SEPARATOR);
207214
appenderMember(sb, MESSAGE_ATTR_NAME, jsonEscape(itp.getMessage()));
208215
sb.append(VALUE_SEPARATOR);
216+
appendSTEPArray(sb, itp.getStackTraceElementProxyArray(), itp.getCommonFrames());
217+
if(itp.getCommonFrames() != 0) {
218+
sb.append(VALUE_SEPARATOR);
219+
appenderMemberWithIntValue(sb, COMMON_FRAMES_COUNT_ATTR_NAME, itp.getCommonFrames());
220+
}
221+
222+
IThrowableProxy cause = itp.getCause();
223+
if(cause != null) {
224+
sb.append(VALUE_SEPARATOR);
225+
appendThrowableProxy(sb, CAUSE_ATTR_NAME, cause);
226+
}
227+
228+
IThrowableProxy[] suppressedArray = itp.getSuppressed();
229+
if(suppressedArray != null && suppressedArray.length != 0) {
230+
sb.append(VALUE_SEPARATOR);
231+
sb.append(QUOTE).append(SUPPRESSED_ATTR_NAME).append(QUOTE_COL);
232+
sb.append(OPEN_ARRAY);
233+
boolean first = true;
234+
for(IThrowableProxy suppressedITP: suppressedArray) {
235+
if(first) {
236+
first = false;
237+
} else {
238+
sb.append(VALUE_SEPARATOR);
239+
}
240+
appendThrowableProxy(sb, null, suppressedITP);
241+
}
242+
sb.append(CLOSE_ARRAY);
243+
}
244+
245+
246+
sb.append(CLOSE_OBJ);
209247

210-
StackTraceElementProxy[] stepArray = itp.getStackTraceElementProxyArray();
248+
}
211249

250+
private void appendSTEPArray(StringBuilder sb, StackTraceElementProxy[] stepArray, int commonFrames) {
212251
sb.append(QUOTE).append(STEP_ARRAY_NAME_ATTRIBUTE).append(QUOTE_COL).append(OPEN_ARRAY);
213252

214253
int len = stepArray != null ? stepArray.length : 0;
215-
for (int i = 0; i < len; i++) {
254+
255+
if(commonFrames >= len) {
256+
commonFrames = 0;
257+
}
258+
259+
for (int i = 0; i < len - commonFrames; i++) {
216260
if (i != 0)
217261
sb.append(VALUE_SEPARATOR);
218262

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

238282
sb.append(CLOSE_ARRAY);
239-
sb.append(CLOSE_OBJ);
240-
241283
}
242284

243285
private void appenderMember(StringBuilder sb, String key, String value) {

logback-classic/src/test/java/ch/qos/logback/classic/encoder/JsonEncoderTest.java

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ void smoke() throws JsonProcessingException {
9797

9898
byte[] resultBytes = jsonEncoder.encode(event);
9999
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
100-
System.out.println(resultString);
100+
//System.out.println(resultString);
101101

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

114114
byte[] resultBytes = jsonEncoder.encode(event);
115115
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
116-
System.out.println(resultString);
116+
// System.out.println(resultString);
117117

118118
JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
119119
compareEvents(event, resultEvent);
@@ -200,7 +200,7 @@ void withMarkers() throws JsonProcessingException {
200200

201201
byte[] resultBytes = jsonEncoder.encode(event);
202202
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
203-
System.out.println(resultString);
203+
//System.out.println(resultString);
204204

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

213213
byte[] resultBytes = jsonEncoder.encode(event);
214214
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
215-
System.out.println(resultString);
215+
//System.out.println(resultString);
216216

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

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

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

261261
byte[] resultBytes = jsonEncoder.encode(event);
262262
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
263-
System.out.println(resultString);
263+
JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
264+
compareEvents(event, resultEvent);
265+
}
266+
267+
@Test
268+
void withThrowableHavingCause() throws JsonProcessingException {
269+
Throwable cause = new IllegalStateException("test cause");
270+
271+
Throwable t = new RuntimeException("test", cause);
272+
273+
274+
LoggingEvent event = new LoggingEvent("in withThrowableHavingCause test", logger, Level.WARN, "hello kvp", t, null);
275+
276+
byte[] resultBytes = jsonEncoder.encode(event);
277+
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
278+
//System.out.println(resultString);
279+
JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
280+
compareEvents(event, resultEvent);
281+
}
282+
283+
@Test
284+
void withThrowableHavingSuppressed() throws JsonProcessingException {
285+
Throwable suppressed = new IllegalStateException("test suppressed");
286+
287+
Throwable t = new RuntimeException("test");
288+
t.addSuppressed(suppressed);
289+
290+
LoggingEvent event = new LoggingEvent("in withThrowableHavingCause test", logger, Level.WARN, "hello kvp", t, null);
291+
292+
byte[] resultBytes = jsonEncoder.encode(event);
293+
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
294+
//System.out.println(resultString);
264295
JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
265296
compareEvents(event, resultEvent);
266297
}

logback-classic/src/test/java/ch/qos/logback/classic/jsonTest/JsonStringToLoggingEventMapper.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import ch.qos.logback.classic.Level;
1818
import ch.qos.logback.classic.encoder.JsonEncoder;
1919
import ch.qos.logback.classic.spi.LoggerContextVO;
20+
import ch.qos.logback.classic.spi.PubThrowableProxy;
2021
import ch.qos.logback.classic.spi.StackTraceElementProxy;
2122
import com.fasterxml.jackson.core.JsonProcessingException;
2223
import com.fasterxml.jackson.databind.JsonNode;
@@ -47,6 +48,7 @@ public JsonLoggingEvent mapStringToLoggingEvent(String resultString) throws Json
4748
module.addDeserializer(Marker.class, new MarkerDeserializer(markerFactory));
4849
module.addDeserializer(KeyValuePair.class, new KeyValuePairDeserializer());
4950
module.addDeserializer(LoggerContextVO.class, new LoggerContextVODeserializer());
51+
module.addDeserializer(PubThrowableProxy.class, new PubThrowableProxyDeserializer());
5052

5153
objectMapper.registerModule(module);
5254

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Logback: the reliable, generic, fast and flexible logging framework.
3+
* Copyright (C) 1999-2023, QOS.ch. All rights reserved.
4+
*
5+
* This program and the accompanying materials are dual-licensed under
6+
* either the terms of the Eclipse Public License v1.0 as published by
7+
* the Eclipse Foundation
8+
*
9+
* or (per the licensee's choosing)
10+
*
11+
* under the terms of the GNU Lesser General Public License version 2.1
12+
* as published by the Free Software Foundation.
13+
*/
14+
15+
package ch.qos.logback.classic.jsonTest;
16+
17+
import ch.qos.logback.classic.encoder.JsonEncoder;
18+
import ch.qos.logback.classic.spi.PubThrowableProxy;
19+
import ch.qos.logback.classic.spi.StackTraceElementProxy;
20+
import com.fasterxml.jackson.core.JacksonException;
21+
import com.fasterxml.jackson.core.JsonParser;
22+
import com.fasterxml.jackson.databind.DeserializationContext;
23+
import com.fasterxml.jackson.databind.JsonNode;
24+
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
25+
26+
import java.io.IOException;
27+
import java.util.ArrayList;
28+
import java.util.List;
29+
30+
public class PubThrowableProxyDeserializer extends StdDeserializer<PubThrowableProxy> {
31+
32+
protected PubThrowableProxyDeserializer() {
33+
this(null);
34+
}
35+
protected PubThrowableProxyDeserializer(Class<?> vc) {
36+
super(vc);
37+
}
38+
39+
@Override
40+
public PubThrowableProxy deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
41+
throws IOException, JacksonException {
42+
JsonNode node = jsonParser.getCodec().readTree(jsonParser);
43+
44+
return jsonNodeThrowableProxy(node);
45+
}
46+
47+
static StackTraceElementProxy[] EMPTY_STEP_ARRAY = new StackTraceElementProxy[0];
48+
static PubThrowableProxy[] EMPTY_PTP_ARRAY = new PubThrowableProxy[0];
49+
50+
private static PubThrowableProxy jsonNodeThrowableProxy(JsonNode node) {
51+
JsonNode classNameJN = node.get(JsonEncoder.CLASS_NAME_ATTR_NAME);
52+
JsonNode messageJN = node.get(JsonEncoder.MESSAGE_ATTR_NAME);
53+
JsonNode stepArrayJN = node.get(JsonEncoder.STEP_ARRAY_NAME_ATTRIBUTE);
54+
JsonNode causeJN = node.get(JsonEncoder.CAUSE_ATTR_NAME);
55+
JsonNode commonFramesCountJN = node.get(JsonEncoder.COMMON_FRAMES_COUNT_ATTR_NAME);
56+
57+
JsonNode suppressedJN = node.get(JsonEncoder.SUPPRESSED_ATTR_NAME);
58+
59+
PubThrowableProxy ptp = new PubThrowableProxy();
60+
ptp.setClassName(classNameJN.textValue());
61+
ptp.setMessage(messageJN.textValue());
62+
63+
List<StackTraceElementProxy> stepList = stepNodeToList(stepArrayJN);
64+
ptp.setStackTraceElementProxyArray(stepList.toArray(EMPTY_STEP_ARRAY));
65+
66+
if(commonFramesCountJN != null) {
67+
int commonFramesCount = commonFramesCountJN.asInt();
68+
ptp.setCommonFramesCount(commonFramesCount);
69+
}
70+
71+
if(causeJN != null) {
72+
PubThrowableProxy cause = jsonNodeThrowableProxy(causeJN);
73+
ptp.setCause(cause);
74+
}
75+
76+
if(suppressedJN != null) {
77+
//System.out.println("suppressedJN "+suppressedJN);
78+
List<PubThrowableProxy> ptpList = suppressedNodeToList(suppressedJN);
79+
System.out.println("iiiiiiiiiiii");
80+
System.out.println("ptpList="+ptpList);
81+
82+
ptp.setSuppressed(ptpList.toArray(EMPTY_PTP_ARRAY));
83+
}
84+
85+
System.out.println("xxxxxxxxxxxxx");
86+
System.out.println(ptp.getSuppressed());
87+
88+
return ptp;
89+
}
90+
91+
private static List<StackTraceElementProxy> stepNodeToList(JsonNode stepArrayJN) {
92+
List<StackTraceElementProxy> stepList = new ArrayList<>();
93+
for(JsonNode jsonNode: stepArrayJN) {
94+
StackTraceElementProxy step = STEPDeserializer.jsonNodeToSTEP(jsonNode);
95+
stepList.add(step);
96+
}
97+
return stepList;
98+
}
99+
100+
private static List<PubThrowableProxy> suppressedNodeToList(JsonNode ptpArrayJN) {
101+
List<PubThrowableProxy> ptpList = new ArrayList<>();
102+
for(JsonNode jsonNode: ptpArrayJN) {
103+
//System.out.println("---in suppressedNodeToList seeing "+jsonNode);
104+
PubThrowableProxy ptp = jsonNodeThrowableProxy(jsonNode);
105+
//System.out.println("--in suppressedNodeToList ptp="+ptp);
106+
ptpList.add(ptp);
107+
}
108+
return ptpList;
109+
}
110+
}

logback-classic/src/test/java/ch/qos/logback/classic/jsonTest/STEPDeserializer.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ public STEPDeserializer(Class<?> vc) {
3838
public StackTraceElementProxy deserialize(JsonParser jp, DeserializationContext ctxt)
3939
throws IOException, JsonProcessingException {
4040
JsonNode node = jp.getCodec().readTree(jp);
41+
return jsonNodeToSTEP(node);
42+
}
43+
44+
public static StackTraceElementProxy jsonNodeToSTEP(JsonNode node) {
4145
String className = node.get("className").asText();
4246
String methodName = node.get("methodName").asText();
4347
String fileName = node.get("fileName").asText();

0 commit comments

Comments
 (0)