JsonEncoderTest.java
/*
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2026, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v2.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.ClassicTestConstants;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.classic.jsonTest.JsonLoggingEvent;
import ch.qos.logback.classic.jsonTest.JsonStringToLoggingEventMapper;
import ch.qos.logback.classic.jsonTest.ThrowableProxyComparator;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.classic.util.LogbackMDCAdapter;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.read.ListAppender;
import ch.qos.logback.core.status.testUtil.StatusChecker;
import ch.qos.logback.core.testUtil.RandomUtil;
import ch.qos.logback.core.util.StatusPrinter;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Marker;
import org.slf4j.event.KeyValuePair;
import org.slf4j.helpers.BasicMarkerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
// When running from an IDE, add the following on the command line
//
// --add-opens ch.qos.logback.classic/ch.qos.logback.classic.jsonTest=ALL-UNNAMED
//
class JsonEncoderTest {
int diff = RandomUtil.getPositiveInt();
LoggerContext loggerContext = new LoggerContext();
StatusChecker statusChecker = new StatusChecker(loggerContext);
Logger logger = loggerContext.getLogger(JsonEncoderTest.class);
JsonEncoder jsonEncoder = new JsonEncoder();
BasicMarkerFactory markerFactory = new BasicMarkerFactory();
Marker markerA = markerFactory.getMarker("A");
Marker markerB = markerFactory.getMarker("B");
ListAppender<ILoggingEvent> listAppender = new ListAppender();
JsonStringToLoggingEventMapper stringToLoggingEventMapper = new JsonStringToLoggingEventMapper(markerFactory);
LogbackMDCAdapter logbackMDCAdapter = new LogbackMDCAdapter();
@BeforeEach
void setUp() {
loggerContext.setName("test_" + diff);
loggerContext.setMDCAdapter(logbackMDCAdapter);
jsonEncoder.setContext(loggerContext);
jsonEncoder.start();
listAppender.setContext(loggerContext);
listAppender.start();
}
@AfterEach
void tearDown() {
}
@Test
void smoke() throws JsonProcessingException {
LoggingEvent event = new LoggingEvent("x", logger, Level.WARN, "hello", null, 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 contextWithProperties() throws JsonProcessingException {
loggerContext.putProperty("k", "v");
loggerContext.putProperty("k" + diff, "v" + diff);
LoggingEvent event = new LoggingEvent("x", logger, Level.WARN, "hello", null, 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);
}
private static void compareEvents(LoggingEvent event, JsonLoggingEvent resultEvent) {
assertEquals(event.getSequenceNumber(), resultEvent.getSequenceNumber());
assertEquals(event.getTimeStamp(), resultEvent.getTimeStamp());
assertEquals(event.getLevel(), resultEvent.getLevel());
assertEquals(event.getLoggerName(), resultEvent.getLoggerName());
assertEquals(event.getThreadName(), resultEvent.getThreadName());
assertEquals(event.getMarkerList(), resultEvent.getMarkerList());
assertEquals(event.getMDCPropertyMap(), resultEvent.getMDCPropertyMap());
assertTrue(compareKeyValuePairLists(event.getKeyValuePairs(), resultEvent.getKeyValuePairs()));
assertEquals(event.getLoggerContextVO(), resultEvent.getLoggerContextVO());
assertTrue(ThrowableProxyComparator.areEqual(event.getThrowableProxy(), resultEvent.getThrowableProxy()));
assertEquals(event.getMessage(), resultEvent.getMessage());
assertEquals(event.getFormattedMessage(), resultEvent.getFormattedMessage());
assertTrue(Arrays.equals(event.getArgumentArray(), resultEvent.getArgumentArray()));
}
private static boolean compareKeyValuePairLists(List<KeyValuePair> leftList, List<KeyValuePair> rightList) {
if (leftList == rightList)
return true;
if (leftList == null || rightList == null)
return false;
int length = leftList.size();
if (rightList.size() != length) {
System.out.println("length discrepancy");
return false;
}
//System.out.println("checking KeyValuePair lists");
for (int i = 0; i < length; i++) {
KeyValuePair leftKVP = leftList.get(i);
KeyValuePair rightKVP = rightList.get(i);
boolean result = Objects.equals(leftKVP.key, rightKVP.key) && Objects.equals(leftKVP.value, rightKVP.value);
if (!result) {
System.out.println("mismatch oin kvp " + leftKVP + " and " + rightKVP);
return false;
}
}
return true;
}
@Test
void withMarkers() throws JsonProcessingException {
LoggingEvent event = new LoggingEvent("x", logger, Level.WARN, "hello", null, null);
event.addMarker(markerA);
event.addMarker(markerB);
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 withArguments() throws JsonProcessingException {
LoggingEvent event = new LoggingEvent("x", logger, Level.WARN, "hello", null, new Object[] { "arg1", "arg2" });
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 withKeyValuePairs() throws JsonProcessingException {
LoggingEvent event = new LoggingEvent("x", logger, Level.WARN, "hello kvp", null,
new Object[] { "arg1", "arg2" });
event.addKeyValuePair(new KeyValuePair("k1", "v1"));
event.addKeyValuePair(new KeyValuePair("k2", "v2"));
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 withFormattedMessage() throws JsonProcessingException {
LoggingEvent event = new LoggingEvent("x", logger, Level.WARN, "hello {} {}", null,
new Object[] { "arg1", "arg2" });
jsonEncoder.setWithFormattedMessage(true);
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 withMDC() throws JsonProcessingException {
Map<String, String> map = new HashMap<>();
map.put("key", "value");
map.put("a", "b");
LoggingEvent event = new LoggingEvent("x", logger, Level.WARN, "hello kvp", null,
new Object[] { "arg1", "arg2" });
Map<String, String> mdcMap = new HashMap<>();
mdcMap.put("mdcK1", "v1");
mdcMap.put("mdcK2", "v2");
event.setMDCPropertyMap(mdcMap);
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 withThrowable() throws JsonProcessingException {
Throwable t = new RuntimeException("test");
LoggingEvent event = new LoggingEvent("in withThrowable test", logger, Level.WARN, "hello kvp", t, null);
byte[] resultBytes = jsonEncoder.encode(event);
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
compareEvents(event, resultEvent);
}
@Test
void withThrowableDisabled() throws JsonProcessingException {
Throwable t = new RuntimeException("withThrowableDisabled");
LoggingEvent event = new LoggingEvent("in withThrowable test", logger, Level.WARN, "hello kvp", t, null);
jsonEncoder.setWithThrowable(false);
byte[] resultBytes = jsonEncoder.encode(event);
String resultString = new String(resultBytes, StandardCharsets.UTF_8);
//System.out.println(resultString);
JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(resultString);
LoggingEvent eventWithNoThrowable = new LoggingEvent("in withThrowable test", logger, Level.WARN, "hello kvp", null, null);
eventWithNoThrowable.setTimeStamp(event.getTimeStamp());
compareEvents(eventWithNoThrowable, 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 withThrowableHavingCyclicCause() throws JsonProcessingException {
Throwable cause = new IllegalStateException("test cause");
Throwable t = new RuntimeException("test", cause);
cause.initCause(t);
LoggingEvent event = new LoggingEvent("in withThrowableHavingCyclicCause 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);
}
void configure(String file) throws JoranException {
JoranConfigurator jc = new JoranConfigurator();
jc.setContext(loggerContext);
loggerContext.putProperty("diff", "" + diff);
jc.doConfigure(file);
}
@Test
void withJoran() throws JoranException, IOException {
String configFilePathStr = ClassicTestConstants.JORAN_INPUT_PREFIX + "json/jsonEncoder.xml";
configure(configFilePathStr);
Logger logger = loggerContext.getLogger(this.getClass().getName());
logger.addAppender(listAppender);
logger.debug("hello");
logbackMDCAdapter.put("a1", "v1" + diff);
logger.atInfo().addKeyValue("ik" + diff, "iv" + diff).addKeyValue("a", "b").log("bla bla \"x\" foobar");
logbackMDCAdapter.put("a2", "v2" + diff);
logger.atWarn().addMarker(markerA).setMessage("some warning message").log();
logbackMDCAdapter.remove("a2");
logger.atError().addKeyValue("ek" + diff, "v" + diff).setCause(new RuntimeException("an error"))
.log("some error occurred");
//StatusPrinter.print(loggerContext);
Path outputFilePath = Path.of(ClassicTestConstants.OUTPUT_DIR_PREFIX + "json/test-" + diff + ".json");
List<String> lines = Files.readAllLines(outputFilePath);
int count = 4;
assertEquals(count, lines.size());
for (int i = 0; i < count; i++) {
//System.out.println("i = " + i);
LoggingEvent withnessEvent = (LoggingEvent) listAppender.list.get(i);
JsonLoggingEvent resultEvent = stringToLoggingEventMapper.mapStringToLoggingEvent(lines.get(i));
compareEvents(withnessEvent, resultEvent);
}
}
@Test
void withJoranAndEnabledFormattedMessage() throws JoranException, IOException {
String configFilePathStr =
ClassicTestConstants.JORAN_INPUT_PREFIX + "json/jsonEncoderAndEnabledFormattedMessage.xml";
configure(configFilePathStr);
Logger logger = loggerContext.getLogger(this.getClass().getName());
//StatusPrinter.print(loggerContext);
statusChecker.isWarningOrErrorFree(0);
logger.atError().addKeyValue("ek1", "v1").addArgument("arg1").log("this is {}");
Path outputFilePath = Path.of(ClassicTestConstants.OUTPUT_DIR_PREFIX + "json/test-" + diff + ".json");
List<String> lines = Files.readAllLines(outputFilePath);
int count = 1;
assertEquals(count, lines.size());
String withness = "{\"sequenceNumber\":0,\"level\":\"ERROR\",\"threadName\":\"main\","
+ "\"loggerName\":\"ch.qos.logback.classic.encoder.JsonEncoderTest\",\"mdc\": {},"
+ "\"kvpList\": [{\"ek1\":\"v1\"}],\"formattedMessage\":\"this is arg1\",\"throwable\":null}";
assertEquals(withness, lines.get(0));
}
}