2

介绍

我们在几个 Spring (Boot) 应用程序中结合使用 SLF4J 和 Logback,最近开始使用 logstash-logback-encoder 来实现结构化日志记录。由于我们还必须支持纯文本日志,我们想知道是否可以自动将参数附加到日志消息中,而无需使用{}标记手动将它们添加到消息中。

所需行为的示例

为了说明所需的行为,这是我们希望的:

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 中)?

4

1 回答 1

3

我不知道有任何日志框架可以让您执行此操作,但您可以轻松编写自己的日志框架。因为这实际上只是一个简单的 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 个异常,即使{}消息中没有匹配项也是如此,因此此方法要求您首先列出所有{}args,然后是任何kvargs,然后是 0 或 1 个 throwable。

不过有一些注意事项:

  • 您必须更改所有代码才能调用这些实用程序方法。您可以使用静态导入使它看起来不错,但它确实使您的代码不那么惯用,这是一个缺点。
  • 大多数日志框架都有大量的方法,因为可变参数会导致创建数组。在热点代码中,JDK 可能会使其足够高效,这无关紧要,但由于日志语句往往无处不在,否则您会遇到一些千刀万剐的情况。通过日志框架调用大量方法来避免可变参数的惩罚不太可能是明智之举。通常日志最终会存储在磁盘上,甚至是 fsync 的,这对性能的影响要大很多数量级。但是,日志框架必须迎合所有场景,并且由于日志级别配置而最终被完全忽略的日志,在一个紧密的循环中,可以由于避免了可变参数的惩罚,可以看到一些性能改进。如果您的日志框架影响性能,您还可以尝试进行优化:您可以询问日志处理程序是否要求的日志级别甚至相关,如果不相关,则return;立即进行。然后,您也可以跟随并创建这种“爆炸”。请参阅slf4j,它为每个日志级别提供 10 种方法,许多其他框架甚至更多(在诉诸可变参数之前,它们有 1、2、3 甚至 4 个参数的变体)。
于 2021-06-03T11:49:36.113 回答