Logstash Logback:如何合并日志中的多个结构化参数?
简化结构化日志中对象ID字段的记录方式
当前每次记录日志都需要手动添加三个v()参数来处理SomeObjectId的三个字段,操作繁琐。以下两种方案可以实现统一处理,同时保留日志消息的可读性和结构化字段输出:
方案一:自定义StructuredArgument实现类
这种方式灵活性最高,可完全控制结构化字段的写入逻辑和日志显示格式:
import net.logstash.logback.argument.StructuredArgument; import com.fasterxml.jackson.core.JsonGenerator; import java.io.IOException; import java.time.LocalDate; // 原有业务类定义 class SomeObject { SomeObjectId id; // 其他字段 } class SomeObjectId { LocalDate date; int objectId; String objectLocation; // Getter方法 public LocalDate getDate() { return date; } public int getObjectId() { return objectId; } public String getObjectLocation() { return objectLocation; } } // 自定义结构化参数类 public class SomeObjectIdArgument implements StructuredArgument { private final SomeObjectId id; private SomeObjectIdArgument(SomeObjectId id) { this.id = id; } // 静态工厂方法,简化调用 public static SomeObjectIdArgument from(SomeObjectId id) { return new SomeObjectIdArgument(id); } // 实现结构化字段的写入逻辑 @Override public void writeTo(JsonGenerator generator) throws IOException { generator.writeStringField("date", id.getDate().toString()); generator.writeNumberField("objectId", id.getObjectId()); generator.writeStringField("location", id.getObjectLocation()); } // 定义日志消息中显示的字符串格式 @Override public String toString() { return String.format("%s %d %s", id.getDate(), id.getObjectId(), id.getObjectLocation()); } }
使用方式:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo { private static final Logger log = LoggerFactory.getLogger(Demo.class); public static void main(String[] args) { SomeObjectId id = new SomeObjectId(); id.date = LocalDate.of(2022, 11, 30); id.objectId = 123; id.objectLocation = "NL"; log.info("Some object with ID {} is processed on {}.", SomeObjectIdArgument.from(id), "someOtherArgument" ); } }
方案二:利用StructuredArguments.map()简化实现
如果不想自定义类,可以直接用logstash-logback提供的工具方法快速实现:
import net.logstash.logback.argument.StructuredArgument; import static net.logstash.logback.argument.StructuredArguments.map; import java.util.LinkedHashMap; import java.util.Map; public class LogTags { public static StructuredArgument fromId(SomeObjectId id) { // 构造结构化字段映射 Map<String, Object> idFields = new LinkedHashMap<>(); idFields.put("date", id.getDate()); idFields.put("objectId", id.getObjectId()); idFields.put("location", id.getObjectLocation()); // 返回包含结构化字段和显示字符串的参数 return map(idFields, () -> String.format("%s %d %s", id.getDate(), id.getObjectId(), id.getObjectLocation())); } }
使用方式:
log.info("Some object with ID {} is processed on {}.", LogTags.fromId(someObject.getId()), "someOtherArgument" );
效果验证
两种方案生成的日志输出与原方式完全一致:
{ "@timestamp": "2022-11-30T12:34:56.000+00:00", "@version": "1", "message": "Some object with ID 2022-11-30 123 NL is processed on someOtherArgument", "thread_name": "main", "level": "INFO", "date": "2022-11-30", "objectId": 123, "location": "NL" }
这样既避免了重复编写多个v()参数,又保持了日志的可读性和结构化字段的完整性。
内容的提问来源于stack exchange,提问作者Michiel
相关产品推荐
相关产品推荐

