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

SLF4J/Java日志:是否可以自动添加日志参数? 介绍,java,logging,logback,slf4j,logstash-logback-encoder,Java,Logging,Logback,Slf4j,Logstash Logback Encoder,我们在几个Spring(Boot)应用程序中结合使用SLF4J和Logback,最近开始使用logstash Logback编码器来实现结构化日志记录。由于我们还必须支持纯文本日志,我们想知道是否可以自动将参数附加到日志消息中,而不必使用{}标记手动将参数添加到消息中 期望行为的示例 为了说明期望的行为,我们希望: log.info(“我的消息”,kv(“arg1”,“第一个参数”),kv(“arg2”,“第二个参数”)) 产生以下所需输出,其中参数自动附加在消息末尾的括号中: My mess

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

期望行为的示例 为了说明期望的行为,我们希望:

log.info(“我的消息”,kv(“arg1”,“第一个参数”),kv(“arg2”,“第二个参数”))
产生以下所需输出,其中参数自动附加在消息末尾的括号中:

My message (arg1="firstArgument", arg2="secondArgument")
或者另一个示例,消息中有显式参数,结尾有参数:

log.info(“状态更改:{}=>{}”、v(“从”、“准备就绪”)、v(“到”、“不可用”)、kv(“服务”、“数据库”))
产生以下期望输出:

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);
    }
}
公共静态类日志扩展{
@lombok.Value公共静态最终类LogKeyValue{
字符串键,值;
}
公共静态LogKeyValue kv(字符串键、对象值){
返回新的LogKeyValue(key,String.valueOf(value));
}
公共静态无效信息(记录器日志、字符串消息、对象…参数){
int extra=0;
int len=args.length;
//最后一个arg可能是一次性的,别管它了。
如果(len>0&&args[len-1]instanceof Throwable)len--;
对于(int i=len-1;i>=0;i--){
如果(!(args[i]instanceof LogKeyValue))中断;
额外++;
}
如果(额外>0){
StringBuilder sb=新的StringBuilder(message.length()+2+(extra.size()-1)*2);
附加(信息)附加({});
对于(inti=1;i
此代码在消息末尾添加
({},{}{})
,每种“kv”类型1个。请注意,大多数日志框架(包括slf4j)都允许您在最后处理1个异常,即使消息中没有匹配的
{}
,因此此方法要求您首先列出所有
{}
参数,然后列出任何
kv
参数,然后列出0或1个一次性参数

不过,有一些警告:

  • 您必须更改所有代码才能调用这些实用程序方法。使用静态导入可以使代码看起来更漂亮,但它确实会使代码不那么地道,这是一个缺点
  • 大多数日志框架都有大量的方法,因为varargs导致了数组的创建。在热插销代码中,JDK可能会使这一点变得足够有效,而这并不重要,但由于日志语句往往无处不在,否则就会出现千倍的削减。不太可能通过日志框架调用大量方法来避免varargs惩罚是明智之举;一般来说,日志最终会存储在磁盘上,甚至会进行fsynced,这对性能的影响要大很多个数量级。但是,日志框架必须满足所有场景,并且由于日志级别配置而最终被完全忽略的日志,在一个紧密的循环中,由于避免了varargs惩罚,可以看到一些性能改进。如果发现日志框架正在影响性能,您还可以尝试进行优化:您可以询问日志处理程序所要求的日志级别是否相关,如果不相关,只需返回
    return立即。然后,您还可以继续并创建此“爆炸”。看看哪个日志级别有10个方法,其他许多框架甚至有更多(在使用varargs之前,它们有1、2、3甚至4个参数的变体)

我不认为直接使用SLF4J和/或Logback就可以做到这一点,但您可以轻松编写自己的日志实用程序方法,满足您的需要。感谢您提供详细的答案!