【问题标题】:SLF4J/Java logging: Is it possible to add log arguments automatically?SLF4J/Java 日志记录:是否可以自动添加日志参数?
【发布时间】:2021-08-21 12:46:18
【问题描述】:

简介

我们在几个 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 中)?

【问题讨论】:

  • 我认为这不可能直接使用 SLF4J 和/或 Logback,但您可以轻松编写自己的日志记录实用程序方法来满足您的需求。

标签: java logging logback slf4j logstash-logback-encoder


【解决方案1】:

我不知道有任何日志框架可以让您执行此操作,但您可以轻松编写自己的日志框架。因为这实际上只是一个简单的 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,然后列出任何 @987654327 @args,然后是 0 或 1 个可抛出对象。

不过有一些注意事项:

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

【讨论】:

  • 感谢您的详细解答!
猜你喜欢
  • 2012-08-26
  • 2011-11-07
  • 1970-01-01
  • 2021-05-26
  • 1970-01-01
  • 1970-01-01
  • 2012-03-28
  • 1970-01-01
  • 2010-10-15
相关资源
最近更新 更多