1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package ch.qos.logback.classic.encoder;
16
17 import ch.qos.logback.classic.ClassicTestConstants;
18 import ch.qos.logback.classic.Level;
19 import ch.qos.logback.classic.Logger;
20 import ch.qos.logback.classic.LoggerContext;
21 import ch.qos.logback.classic.joran.JoranConfigurator;
22 import ch.qos.logback.classic.jsonTest.JsonLoggingEvent;
23 import ch.qos.logback.classic.jsonTest.JsonStringToLoggingEventMapper;
24 import ch.qos.logback.classic.jsonTest.ThrowableProxyComparator;
25 import ch.qos.logback.classic.spi.ILoggingEvent;
26 import ch.qos.logback.classic.spi.LoggingEvent;
27 import ch.qos.logback.classic.util.LogbackMDCAdapter;
28 import ch.qos.logback.core.joran.spi.JoranException;
29 import ch.qos.logback.core.read.ListAppender;
30 import ch.qos.logback.core.status.testUtil.StatusChecker;
31 import ch.qos.logback.core.testUtil.RandomUtil;
32 import ch.qos.logback.core.util.StatusPrinter;
33 import com.fasterxml.jackson.core.JsonProcessingException;
34 import org.junit.jupiter.api.AfterEach;
35 import org.junit.jupiter.api.BeforeEach;
36 import org.junit.jupiter.api.Test;
37 import org.slf4j.Marker;
38 import org.slf4j.event.KeyValuePair;
39 import org.slf4j.helpers.BasicMarkerFactory;
40
41 import java.io.IOException;
42 import java.nio.charset.StandardCharsets;
43 import java.nio.file.Files;
44 import java.nio.file.Path;
45 import java.util.Arrays;
46 import java.util.HashMap;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Objects;
50
51 import static org.junit.jupiter.api.Assertions.assertEquals;
52 import static org.junit.jupiter.api.Assertions.assertTrue;
53
54
55
56
57
58 class JsonEncoderTest {
59
60 int diff = RandomUtil.getPositiveInt();
61
62 LoggerContext loggerContext = new LoggerContext();
63 StatusChecker statusChecker = new StatusChecker(loggerContext);
64 Logger logger = loggerContext.getLogger(JsonEncoderTest.class);
65
66 JsonEncoder jsonEncoder = new JsonEncoder();
67
68 BasicMarkerFactory markerFactory = new BasicMarkerFactory();
69
70 Marker markerA = markerFactory.getMarker("A");
71
72 Marker markerB = markerFactory.getMarker("B");
73
74 ListAppender<ILoggingEvent> listAppender = new ListAppender();
75 JsonStringToLoggingEventMapper stringToLoggingEventMapper = new JsonStringToLoggingEventMapper(markerFactory);
76
77 LogbackMDCAdapter logbackMDCAdapter = new LogbackMDCAdapter();
78
79 @BeforeEach
80 void setUp() {
81 loggerContext.setName("test_" + diff);
82 loggerContext.setMDCAdapter(logbackMDCAdapter);
83
84 jsonEncoder.setContext(loggerContext);
85 jsonEncoder.start();
86
87 listAppender.setContext(loggerContext);
88 listAppender.start();
89 }
90
91 @AfterEach
92 void tearDown() {
93 }
94
95 @Test
96 void smoke() throws JsonProcessingException {
97 LoggingEvent event = new LoggingEvent("x", logger, Level.WARN, "hello", null, null);
98
99 byte[] resultBytes = jsonEncoder.encode(event);
100 String resultString = new String(resultBytes, StandardCharsets.UTF_8);
101
102 JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
103 compareEvents(event, resultEvent);
104 }
105
106 @Test
107 void contextWithProperties() throws JsonProcessingException {
108 loggerContext.putProperty("k", "v");
109 loggerContext.putProperty("k" + diff, "v" + diff);
110
111 LoggingEvent event = new LoggingEvent("x", logger, Level.WARN, "hello", null, null);
112
113 byte[] resultBytes = jsonEncoder.encode(event);
114 String resultString = new String(resultBytes, StandardCharsets.UTF_8);
115
116
117 JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
118 compareEvents(event, resultEvent);
119
120 }
121
122 private static void compareEvents(LoggingEvent event, JsonLoggingEvent resultEvent) {
123 assertEquals(event.getSequenceNumber(), resultEvent.getSequenceNumber());
124 assertEquals(event.getTimeStamp(), resultEvent.getTimeStamp());
125 assertEquals(event.getLevel(), resultEvent.getLevel());
126 assertEquals(event.getLoggerName(), resultEvent.getLoggerName());
127 assertEquals(event.getThreadName(), resultEvent.getThreadName());
128 assertEquals(event.getMarkerList(), resultEvent.getMarkerList());
129 assertEquals(event.getMDCPropertyMap(), resultEvent.getMDCPropertyMap());
130 assertTrue(compareKeyValuePairLists(event.getKeyValuePairs(), resultEvent.getKeyValuePairs()));
131
132 assertEquals(event.getLoggerContextVO(), resultEvent.getLoggerContextVO());
133 assertTrue(ThrowableProxyComparator.areEqual(event.getThrowableProxy(), resultEvent.getThrowableProxy()));
134
135 assertEquals(event.getMessage(), resultEvent.getMessage());
136 assertEquals(event.getFormattedMessage(), resultEvent.getFormattedMessage());
137
138 assertTrue(Arrays.equals(event.getArgumentArray(), resultEvent.getArgumentArray()));
139
140 }
141
142 private static boolean compareKeyValuePairLists(List<KeyValuePair> leftList, List<KeyValuePair> rightList) {
143 if (leftList == rightList)
144 return true;
145
146 if (leftList == null || rightList == null)
147 return false;
148
149 int length = leftList.size();
150 if (rightList.size() != length) {
151 System.out.println("length discrepancy");
152 return false;
153 }
154
155
156
157 for (int i = 0; i < length; i++) {
158 KeyValuePair leftKVP = leftList.get(i);
159 KeyValuePair rightKVP = rightList.get(i);
160
161 boolean result = Objects.equals(leftKVP.key, rightKVP.key) && Objects.equals(leftKVP.value, rightKVP.value);
162
163 if (!result) {
164 System.out.println("mismatch oin kvp " + leftKVP + " and " + rightKVP);
165 return false;
166 }
167 }
168 return true;
169
170 }
171
172 @Test
173 void withMarkers() throws JsonProcessingException {
174 LoggingEvent event = new LoggingEvent("x", logger, Level.WARN, "hello", null, null);
175 event.addMarker(markerA);
176 event.addMarker(markerB);
177
178 byte[] resultBytes = jsonEncoder.encode(event);
179 String resultString = new String(resultBytes, StandardCharsets.UTF_8);
180
181
182 JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
183 compareEvents(event, resultEvent);
184 }
185
186 @Test
187 void withArguments() throws JsonProcessingException {
188 LoggingEvent event = new LoggingEvent("x", logger, Level.WARN, "hello", null, new Object[] { "arg1", "arg2" });
189
190 byte[] resultBytes = jsonEncoder.encode(event);
191 String resultString = new String(resultBytes, StandardCharsets.UTF_8);
192
193
194 JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
195 compareEvents(event, resultEvent);
196 }
197
198 @Test
199 void withKeyValuePairs() throws JsonProcessingException {
200 LoggingEvent event = new LoggingEvent("x", logger, Level.WARN, "hello kvp", null,
201 new Object[] { "arg1", "arg2" });
202 event.addKeyValuePair(new KeyValuePair("k1", "v1"));
203 event.addKeyValuePair(new KeyValuePair("k2", "v2"));
204
205 byte[] resultBytes = jsonEncoder.encode(event);
206 String resultString = new String(resultBytes, StandardCharsets.UTF_8);
207
208 JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
209 compareEvents(event, resultEvent);
210 }
211
212 @Test
213 void withFormattedMessage() throws JsonProcessingException {
214 LoggingEvent event = new LoggingEvent("x", logger, Level.WARN, "hello {} {}", null,
215 new Object[] { "arg1", "arg2" });
216 jsonEncoder.setWithFormattedMessage(true);
217
218 byte[] resultBytes = jsonEncoder.encode(event);
219 String resultString = new String(resultBytes, StandardCharsets.UTF_8);
220
221
222 JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
223 compareEvents(event, resultEvent);
224 }
225
226 @Test
227 void withMDC() throws JsonProcessingException {
228 Map<String, String> map = new HashMap<>();
229 map.put("key", "value");
230 map.put("a", "b");
231
232 LoggingEvent event = new LoggingEvent("x", logger, Level.WARN, "hello kvp", null,
233 new Object[] { "arg1", "arg2" });
234 Map<String, String> mdcMap = new HashMap<>();
235 mdcMap.put("mdcK1", "v1");
236 mdcMap.put("mdcK2", "v2");
237
238 event.setMDCPropertyMap(mdcMap);
239
240 byte[] resultBytes = jsonEncoder.encode(event);
241 String resultString = new String(resultBytes, StandardCharsets.UTF_8);
242
243 JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
244 compareEvents(event, resultEvent);
245 }
246
247 @Test
248 void withThrowable() throws JsonProcessingException {
249 Throwable t = new RuntimeException("test");
250 LoggingEvent event = new LoggingEvent("in withThrowable test", logger, Level.WARN, "hello kvp", t, null);
251
252 byte[] resultBytes = jsonEncoder.encode(event);
253 String resultString = new String(resultBytes, StandardCharsets.UTF_8);
254 JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
255 compareEvents(event, resultEvent);
256 }
257
258 @Test
259 void withThrowableDisabled() throws JsonProcessingException {
260 Throwable t = new RuntimeException("withThrowableDisabled");
261 LoggingEvent event = new LoggingEvent("in withThrowable test", logger, Level.WARN, "hello kvp", t, null);
262 jsonEncoder.setWithThrowable(false);
263 byte[] resultBytes = jsonEncoder.encode(event);
264 String resultString = new String(resultBytes, StandardCharsets.UTF_8);
265
266 JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
267
268 LoggingEvent eventWithNoThrowable = new LoggingEvent("in withThrowable test", logger, Level.WARN, "hello kvp", null, null);
269 eventWithNoThrowable.setTimeStamp(event.getTimeStamp());
270
271 compareEvents(eventWithNoThrowable, resultEvent);
272 }
273
274
275 @Test
276 void withThrowableHavingCause() throws JsonProcessingException {
277 Throwable cause = new IllegalStateException("test cause");
278
279 Throwable t = new RuntimeException("test", cause);
280
281 LoggingEvent event = new LoggingEvent("in withThrowableHavingCause test", logger, Level.WARN, "hello kvp", t,
282 null);
283
284 byte[] resultBytes = jsonEncoder.encode(event);
285 String resultString = new String(resultBytes, StandardCharsets.UTF_8);
286
287 JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
288 compareEvents(event, resultEvent);
289 }
290
291 @Test
292 void withThrowableHavingCyclicCause() throws JsonProcessingException {
293 Throwable cause = new IllegalStateException("test cause");
294
295 Throwable t = new RuntimeException("test", cause);
296 cause.initCause(t);
297
298 LoggingEvent event = new LoggingEvent("in withThrowableHavingCyclicCause test", logger, Level.WARN, "hello kvp",
299 t, null);
300
301 byte[] resultBytes = jsonEncoder.encode(event);
302 String resultString = new String(resultBytes, StandardCharsets.UTF_8);
303
304 JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
305 compareEvents(event, resultEvent);
306 }
307
308 @Test
309 void withThrowableHavingSuppressed() throws JsonProcessingException {
310 Throwable suppressed = new IllegalStateException("test suppressed");
311
312 Throwable t = new RuntimeException("test");
313 t.addSuppressed(suppressed);
314
315 LoggingEvent event = new LoggingEvent("in withThrowableHavingCause test", logger, Level.WARN, "hello kvp", t,
316 null);
317
318 byte[] resultBytes = jsonEncoder.encode(event);
319 String resultString = new String(resultBytes, StandardCharsets.UTF_8);
320
321 JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
322 compareEvents(event, resultEvent);
323 }
324
325 void configure(String file) throws JoranException {
326 JoranConfigurator jc = new JoranConfigurator();
327 jc.setContext(loggerContext);
328 loggerContext.putProperty("diff", "" + diff);
329 jc.doConfigure(file);
330 }
331
332 @Test
333 void withJoran() throws JoranException, IOException {
334 String configFilePathStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "json/jsonEncoder.xml";
335
336 configure(configFilePathStr);
337 Logger logger = loggerContext.getLogger(this.getClass().getName());
338 logger.addAppender(listAppender);
339
340 logger.debug("hello");
341 logbackMDCAdapter.put("a1", "v1" + diff);
342 logger.atInfo().addKeyValue("ik" + diff, "iv" + diff).addKeyValue("a", "b").log("bla bla \"x\" foobar");
343 logbackMDCAdapter.put("a2", "v2" + diff);
344 logger.atWarn().addMarker(markerA).setMessage("some warning message").log();
345 logbackMDCAdapter.remove("a2");
346 logger.atError().addKeyValue("ek" + diff, "v" + diff).setCause(new RuntimeException("an error"))
347 .log("some error occurred");
348
349
350
351 Path outputFilePath = Path.of(ClassicTestConstants.OUTPUT_DIR_PREFIX + "json/test-" + diff + ".json");
352 List<String> lines = Files.readAllLines(outputFilePath);
353 int count = 4;
354 assertEquals(count, lines.size());
355
356 for (int i = 0; i < count; i++) {
357
358 LoggingEvent withnessEvent = (LoggingEvent) listAppender.list.get(i);
359 JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(lines.get(i));
360 compareEvents(withnessEvent, resultEvent);
361 }
362 }
363
364 @Test
365 void withJoranAndEnabledFormattedMessage() throws JoranException, IOException {
366 String configFilePathStr =
367 ClassicTestConstants.JORAN_INPUT_PREFIX + "json/jsonEncoderAndEnabledFormattedMessage.xml";
368
369 configure(configFilePathStr);
370 Logger logger = loggerContext.getLogger(this.getClass().getName());
371
372
373 statusChecker.isWarningOrErrorFree(0);
374
375 logger.atError().addKeyValue("ek1", "v1").addArgument("arg1").log("this is {}");
376
377 Path outputFilePath = Path.of(ClassicTestConstants.OUTPUT_DIR_PREFIX + "json/test-" + diff + ".json");
378 List<String> lines = Files.readAllLines(outputFilePath);
379
380 int count = 1;
381 assertEquals(count, lines.size());
382
383 String withness = "{\"sequenceNumber\":0,\"level\":\"ERROR\",\"threadName\":\"main\","
384 + "\"loggerName\":\"ch.qos.logback.classic.encoder.JsonEncoderTest\",\"mdc\": {},"
385 + "\"kvpList\": [{\"ek1\":\"v1\"}],\"formattedMessage\":\"this is arg1\",\"throwable\":null}";
386
387 assertEquals(withness, lines.get(0));
388 }
389 }