微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

SLF4J/Java 日志记录:是否可以自动添加日志参数?

如何解决SLF4J/Java 日志记录:是否可以自动添加日志参数?

简介

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

解决方法

我不知道有任何日志框架可以让您执行此操作,但是您可以轻松地编写自己的日志框架。因为这实际上只是一个简单的 API 扩展,因此您需要复制的只是各种 log 消息。例如,这个 one-liner 会处理它:

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 个 throwable。

一些警告:

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。