如何在Logback日志JSON根节点写入POJO并修改其属性?
自定义Logback日志格式:修改POJO内置属性方案
核心问题拆解
你遇到的JsonGenerationException本质是JSON结构冲突:Logback的JSON生成器默认已在构建根对象,直接调用writeObject()会尝试在根节点内嵌套另一个对象,破坏JSON结构;而writeObjectField("")或StructuredArguments.fields(pojo)是将POJO的字段平铺到根对象,符合JSON单根节点要求。
要在Provider或Encoder中修改POJO的timestamp、log_level等内置日志属性,可通过以下两种实用方案实现:
方案一:自定义Encoder(最直接可控)
通过实现Logback的Encoder<ILoggingEvent>接口,直接拦截日志事件,将内置属性注入POJO后序列化。
1. 定义自定义日志POJO
public class CustomLogEntry { private String timestamp; private String logLevel; private String message; private String threadName; private String traceId; // 自定义排查字段 private String customField1; private String customField2; // getter/setter(若用Lombok可简化) }
2. 实现自定义Encoder
public class CustomLogEncoder extends EncoderBase<ILoggingEvent> { private final ObjectMapper objectMapper = new ObjectMapper(); // 统一时间戳格式为ISO-8601 private final DateTimeFormatter timestampFormatter = DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.systemDefault()); @Override public byte[] encode(ILoggingEvent event) { CustomLogEntry logEntry = new CustomLogEntry(); // 从ILoggingEvent提取内置属性并设置到POJO logEntry.setTimestamp(timestampFormatter.format(Instant.ofEpochMilli(event.getTimeStamp()))); logEntry.setLogLevel(event.getLevel().toString()); logEntry.setMessage(event.getFormattedMessage()); logEntry.setThreadName(event.getThreadName()); // 从MDC获取链路追踪ID等排查字段 logEntry.setTraceId(MDC.get("traceId")); // 处理日志参数中的自定义字段 for (Object arg : event.getArgumentArray()) { if (arg instanceof CustomExtraFields) { CustomExtraFields extra = (CustomExtraFields) arg; logEntry.setCustomField1(extra.getField1()); logEntry.setCustomField2(extra.getField2()); } } try { // 序列化POJO为JSON,加上换行符符合日志格式 return (objectMapper.writeValueAsString(logEntry) + getLineSeparator()).getBytes(StandardCharsets.UTF_8); } catch (JsonProcessingException e) { addError("序列化日志失败", e); return new byte[0]; } } @Override public void start() { super.start(); // 配置ObjectMapper,忽略空字段 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); } }
3. 在logback.xml中配置
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="com.yourpackage.CustomLogEncoder" /> </appender> <root level="INFO"> <appender-ref ref="CONSOLE" /> </root>
方案二:自定义StructuredArgument(结合原fields用法)
若想保留StructuredArguments.fields(pojo)的便捷性,可自定义StructuredArgument实现,在写入JSON前修改POJO属性。
1. 自定义StructuredArgument实现
public class CustomLogArgument implements StructuredArgument { private final CustomLogEntry logEntry; public CustomLogArgument(CustomLogEntry logEntry) { this.logEntry = logEntry; } @Override public void writeTo(JsonGenerator generator, ProviderContext context) throws IOException { // 从ProviderContext获取当前日志事件 ILoggingEvent event = context.getLoggingEvent(); // 修改POJO的内置属性 logEntry.setTimestamp(Instant.ofEpochMilli(event.getTimeStamp()).toString()); logEntry.setLogLevel(event.getLevel().toString()); // 将POJO字段平铺到JSON根节点 generator.writeObject(logEntry); } @Override public String toString() { return logEntry.toString(); } }
2. 业务代码中使用
CustomLogEntry entry = new CustomLogEntry(); entry.setCustomField1("排查字段1"); entry.setCustomField2("排查字段2"); // 日志输出时传入自定义参数 logger.info("用户登录成功", new CustomLogArgument(entry));
关键注意事项
- POJO可变性:若POJO是不可变类(如用
@Value注解),需在构建POJO实例时直接传入timestamp、log_level等属性,可在Encoder或自定义Argument中提前构建实例。 - 时间戳格式:Logback的
getTimeStamp()返回毫秒数,建议转成ISO-8601字符串,方便跨系统解析。 - 日志级别统一:可将
event.getLevel()转成大写字符串(如INFO、ERROR),避免格式不一致。
内容的提问来源于stack exchange,提问作者user3322417
相关产品推荐
相关产品推荐

