简介
我们在几个Spring(Boot(应用程序中结合使用SLF4J和Logback,最近开始使用logstash Logback编码器来实现结构化日志记录。由于我们还必须支持纯文本日志,我们想知道是否可以自动将参数附加到日志消息中,而不必使用{}
标记手动将它们添加到消息中。
所需行为示例
为了说明所需的行为,这就是我们所希望的:
log.info("My message", kv("arg1", "firstArgument"), kv("arg2", "secondArgument"))
产生以下所需的输出,其中参数自动附加在消息末尾的括号中:
My message (arg1="firstArgument", arg2="secondArgument")
或者另一个消息中同时包含显式参数和末尾包含参数的示例:
log.info("Status changed: {} => {}", v("from", "READY"), v("to", "UNAVAILABLE"), kv("service", "database"))
产生以下所需输出:
Status changed: READY => UNAVAILABLE (from="READY", to="UNAVAILABLE", service="database")
问题
SLF4J/Logback是否可以实现这一点?如果没有,你知道其他日志框架或实现这一点的方法吗(在Java中(?
我不知道有任何日志框架可以让你做到这一点,但你可以简单地编写自己的日志框架。因为这实际上只是一个简单的API扩展,因此,您需要复制的只是各种log
消息。例如,这艘班轮可以处理它:
public static class LoggingExtensions {
@lombok.Value public static final class LogKeyValue {
String key, value;
}
public static LogKeyValue kv(String key, Object value) {
return new LogKeyValue(key, String.valueOf(value));
}
public static void info(Logger log, String message, Object... args) {
int extra = 0;
int len = args.length;
// Last arg could be a throwable, leave that alone.
if (len > 0 && args[len - 1] instanceof Throwable) len--;
for (int i = len - 1; i >= 0; i--) {
if (!(args[i] instanceof LogKeyValue)) break;
extra++;
}
if (extra > 0) {
StringBuilder sb = new StringBuilder(message.length() + 2 + (extra.size() - 1) * 2);
sb.append(message).append("({}");
for (int i = 1; i < extra; i++) sb.append(", {}");
message = sb.append(")").toString();
}
log.info(message, args);
}
}
此代码在消息末尾附加({}, {} {})
,每个"kv"类型1个。请注意,包括slf4j在内的大多数日志记录框架都允许在末尾添加1个异常,即使消息中没有匹配的{}
也是如此,因此该方法需要首先列出所有{}
参数,然后列出任何kv
参数,然后再列出0或1个可丢弃参数。
不过需要注意的是:
- 您必须更改所有代码才能调用这些实用程序方法。使用静态导入可以使它看起来很好看,但它确实会降低代码的惯用性,这是一个缺点
- 大多数日志框架都有大量的方法,因为varargs会导致数组的创建。在热点代码中,JDK可能会使其足够高效,这并不重要,但由于日志语句往往无处不在,否则会发生一些致命的剪切。日志框架调用大量方法来避免varargs惩罚是明智之举,这是不太可能的;通常,日志最终会出现在磁盘上,甚至是fsynced,这对性能的影响要大很多数量级。但是,日志框架必须满足所有场景,并且由于日志级别配置而最终被完全忽略的日志,在一个紧密的循环中,可以由于避免varargs惩罚而看到一些性能改进。如果发现日志框架影响性能,您也可以尝试进行优化:您可以询问日志处理程序所要求的日志级别是否相关,如果不相关,则立即询问
return;
。然后你也可以跟随并创建这个"爆炸"。请参阅slf4j,它为每个日志级别提供了10个方法,许多其他框架甚至有更多(在使用varargs之前,它们有1、2、3,有时甚至4个参数的变体(