If you use logback, for instance, you can use ch.qos.logback.classic.encoder.JsonEncoder to log JSON objects:
You need this configuration in logback.xml:
configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.JsonEncoder"/>
</appender>
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
And then the code:
package com.example.so;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Eg {
public static void main(String[] args) {
Logger l = LoggerFactory.getLogger("test");
l.atInfo().addKeyValue("my-flag", true)
.addKeyValue("my-int", 123)
.addKeyValue("my-double", Math.PI)
.addKeyValue("my-name", "abcdefg")
.log();
}
}
produces:
{
"sequenceNumber": 0,
"timestamp": 1712366291199,
"nanoseconds": 199243000,
"level": "INFO",
"threadName": "main",
"loggerName": "test",
"context": {
"name": "default",
"birthdate": 1712366291102,
"properties": {}
},
"mdc": {},
"kvpList": [
{
"my-flag": "true"
},
{
"my-int": "123"
},
{
"my-double": "3.141592653589793"
},
{
"my-name": "abcdefg"
}
],
"message": "null",
"throwable": null
}
I'm getting the required dependencies via Spring Boot:
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:3.2.0-RC1:compile
[INFO] | +- org.springframework.boot:spring-boot-starter:jar:3.2.0-RC1:compile
[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:3.2.0-RC1:compile
[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.4.11:compile
[INFO] | | | | \- ch.qos.logback:logback-core:jar:1.4.11:compile
[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.21.0:compile
[INFO] | | | | \- org.apache.logging.log4j:log4j-api:jar:2.21.0:compile
[INFO] | | | \- org.slf4j:jul-to-slf4j:jar:2.0.9:compile
This solution logs all values as JSON strings, even if there's a primitive representation available. The class ch.qos.logback.classic.encoder.JsonEncoder doesn't lend itself to extension, but you can copy it and modify it to log numbers and booleans naturally. (The code below works but has not been tested much)
Add two methods:
private void appenderMemberUnquotedValue(StringBuilder sb, String key, String value) {
sb.append(QUOTE).append(key).append(QUOTE_COL).append(value);
}
String toJson(Object o) {
return switch (o) {
case String s -> "\"" + jsonEscape(s) + "\"";
case Boolean b -> b.toString();
case Number n -> n.toString();
case null -> "null";
default -> "\"" + jsonEscapedToString(o) + "\"";
};
}
And modify one line in this method:
private void appendKeyValuePairs(StringBuilder sb, ILoggingEvent event) {
List<KeyValuePair> kvpList = event.getKeyValuePairs();
if (kvpList == null || kvpList.isEmpty())
return;
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)); // OLD
appenderMemberUnquotedValue(sb, jsonEscapedToString(kvp.key), toJson(kvp.value)); // NEW
sb.append(CLOSE_OBJ);
}
sb.append(CLOSE_ARRAY);
sb.append(VALUE_SEPARATOR);
}