没有toString()的slf4j

时间:2012-07-02 14:33:54

标签: slf4j

当您在slf4j中执行类似LOG.debug("Exported {}.", product)的操作时,它最终会在参数上调用toString(),例如product

由于某些原因,我不能在我想用作参数的所有类上覆盖toString()。有些类来自第三方jar,其他类也会在其他上下文中调用toString(),我想在日志语句中打印的信息不可用。

但是,我有一个用于调试目的的类,它有一个方法DebugFormatter.format(Object),它有一长串的instanceofs,它选择例程来查找关于该对象的一些有用的调试信息。

我的问题是:是否可以配置slf4j以便它调用这样的静态方法而不是toString()?

当然,在将对象作为参数传递给Logger.debug()之前,我可以在对象上调用我的格式方法,但是即使没有启用相应的记录器,它也会被执行。所以我不得不用if (LOG.isDebugEnabled())包围它,这意味着错过了在debug()中拥有参数的重点。

4 个答案:

答案 0 :(得分:9)

你可以创建一个垫片,拿走你的对象并从DebugFormatter.format()函数中调用toString()。像这样:

class DebugFormatObject {
  private final Object o;

  public static DebugFormatObject forDebug(Object o) {
    return new DebugFormatObject(o);
  }

  private DebugFormatObject(Object o) {
    this.o = o;
  }

  @Override
  public String toString() {
    return DebugFormatter.format(o);
  }
}

使用适当的静态导入,您的日志记录语句将变为:

LOG.debug("Exported {}.", forDebug(product));

这确实比直接传递对象略微增加了开销,但它是一个小的,不变的开销 - 并且创建的对象将是非常短暂的。

答案 1 :(得分:5)

如果底层日志记录框架是本地实现,例如,则参数的toString()调用由日志记录框架执行,而不是由SLF4J执行。接下来,您可以通过创建custom converter实际输出/打印日志消息时调用DebugFormatter.format(o)。更具体地说,您将创建一个替换%msg /%消息的转换器。

对于非本机实现,toString()调用由SLF4J进行。因此,Andrew和Sean提供的答案适用。

答案 2 :(得分:3)

对于那些想要编写自定义Logback Convereter的人来说,这里是我编写的代码,以避免遗留对象的完全转储:

...
import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;

public class PrettyMessageConverter extends MessageConverter {

    private static final String EMPTY = "";
    private static final String PLACEHOLDER = "{}";

    @Override
    public String convert(ILoggingEvent event) {
        StringBuilder message = new StringBuilder(event.getMessage());

    int argumentIndex = 0;
        int placeholderIndex = -1;
        while ( (placeholderIndex=message.indexOf(PLACEHOLDER, placeholderIndex))!=-1 && message.charAt(placeholderIndex-1)!='\\' ) {
        String stringValue = valueOf(getArgument(event.getArgumentArray(), argumentIndex++));
            message.replace(placeholderIndex, placeholderIndex+PLACEHOLDER.length(), stringValue);
        }
        return message.toString();
    }

    /**
     * Return a {@link String} representation of the given object. It use convert 
     * some complex objects as a single line string and use {@link String#valueOf(Object))} 
     * for other objects.
     */
    private String valueOf(final Object object) {
        if ( object instanceof AuthenticatedUser )
            return valueOf((AuthenticatedUser) object);

        return String.valueOf(object);
    }

    private String valueOf(AuthenticatedUser user) {
        return user.getUsername();
    }

    /** 
     * Retrieve an argument at a given position but avoid {@link ArrayIndexOutOfBoundsException}
     * by returning {@link PrettyMessageConverter#EMPTY} string when the index 
     * is out of bounds. 
     */
    private Object getArgument(Object[] arguments, int index) {
        return (index<arguments.length)?arguments[index]:EMPTY;
    }

}

使用 logback.xml 中的这一行:

 <conversionRule conversionWord="msg" converterClass="PrettyMessageConverter" />

答案 3 :(得分:1)

我担心slf4j不能以这种方式扩展。您可以做的是实现您自己的slf4j实现,该实现充当现有实现的装饰器,为某些已知类型提供特殊处理。但你必须重新发明许多轮子。例如:您的记录器实现方法都必须以if(underLyingLogger.isXyzEnabled())开头,尽管底层记录器将在装饰器调用底层方法后再次执行完全相同的检查。

另一方面,我过去使用过的解决方法是ToString-Delegate。最简单的形式可以是匿名对象:

final SomeObject myObj = ...;
LOG.debug("foo {}", new Object(){
     public String toString(){
         return myObj.someMethod();
     }
});

在这里你也有短暂的包装器对象,但实际上你不需要渲染字符串。

因为上面的语法非常难看,我建议你提供一个带有静态辅助方法的Factory类来创建这些ToString对象。在我的例子中,我有一个名为ToStringWrapper的抽象类,它有工厂方法,用于将一个迭代与一个Guava Joiner等连接起来。