13

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

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

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

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

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

4

4 回答 4

11

您可以创建一个 shim,获取您的对象并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));

这确实比直接传递对象稍微多一些开销,但它是一个小的、恒定的开销——并且创建的对象将非常短暂。

于 2012-07-02T14:54:37.100 回答
6

如果底层日志框架是原生实现,例如,则对参数的 toString() 调用由日志框架而不是SLF4J 执行。因此,您可以通过创建自定义转换器在实际输出/打印日志消息时调用 DebugFormatter.format(o) 。更具体地说,您将创建一个转换器来替换 %msg/%message。

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

于 2012-07-03T09:55:55.903 回答
4

对于那些想要编写自定义 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" />
于 2012-08-29T09:45:27.630 回答
2

恐怕 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 等连接可迭代对象的工厂方法。

于 2012-07-02T15:09:10.407 回答