2012-07-02 12 views
7

Cuando hace algo como LOG.debug("Exported {}.", product) en slf4j eventualmente llamará aString() en los argumentos, p. Ej. product.slf4j sin toString()

Por ciertas razones, no puedo anular toString() en todas las clases que quiero usar como argumentos. Algunas clases provienen de jar de terceros, otras tendrán su llamada a toString() en otros contextos, también, donde la información que deseo imprimir en mi declaración de registro no está disponible.

Sin embargo, tengo una clase para depuración que tiene un método DebugFormatter.format(Object) que tiene una larga cascada de ejemplos que selecciona la rutina para encontrar información de depuración útil sobre ese objeto.

Mi pregunta es: ¿es posible configurar slf4j para que llame a un método estático en lugar de toString()?

Por supuesto, podría llamar a mi método de formato en el objeto antes de pasarlo como un parámetro al Logger.debug(), pero luego se ejecutará incluso cuando el registrador respectivo no esté habilitado. Así que tuve que rodearlo con if (LOG.isDebugEnabled()), lo que significa que se perdió todo el punto de tener argumentos en debug().

+0

No ha mencionado el marco de registro subyacente. ¿Es log4j? ¿volver a iniciar sesión? – Ceki

+0

Es log4j. Estoy planeando cambiar a logback, sin embargo. – Wolfgang

+0

He aceptado la respuesta de Andrew como respuesta oficial porque es más fácil de implementar que la de Ceki. También es independiente del marco subyacente. Además, permite hacer que el formateo sea opcional, o incluso elegir entre diferentes formateadores. Sin embargo, para otros usuarios, la respuesta de Ceki podría ser mejor porque es más transparente y ahorra recursos. – Wolfgang

Respuesta

9

Puede crear una cuña, tomando su objeto y llamando al DebugFormatter.format() desde su función toString(). Algo como esto:

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); 
    } 
} 

Con la importación estática adecuado, su estado de registro se convierte en esto:

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

Esto tiene un poco más de riesgo de pasar el objeto en recta, pero es una sobrecarga pequeña, constante - y el objeto creado será muy efímero.

+0

Esa es una bonita idea que no pensé acerca de. Si el registrador está deshabilitado, no se llama al formateador. El único inconveniente es que crearía el objeto envoltorio inútilmente. No estoy seguro de cuánto impacto tiene. – Wolfgang

+0

@ Andrew, lo siento, leí mal su respuesta. +1 –

1

Me temo que slf4j no fue construido para ser extendido de tal manera. Lo que podría do es implementar su propia implementación slf4j que actúa como un decorador en torno a las implementaciones existentes, proporcionando un manejo especial para algunos tipos conocidos. Pero tendrías que reinventar muchas ruedas. Por ejemplo: sus métodos de implementación del registrador tendrían que comenzar con if(underLyingLogger.isXyzEnabled()), aunque el registrador subyacente realizará la misma comprobación una vez que el decorador haya llamado a los métodos subyacentes.

Por otro lado, una solución que he utilizado en el pasado es un ToString-Delegate. La forma más fácil simplemente puede ser un objeto anónimo:

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

Aquí también tiene vida corta objetos envolventes, pero que en realidad no hacer que las cuerdas hasta que tenga que hacerlo.

porque la sintaxis anterior es muy fea, le sugiero que proporcione una clase de fábrica con métodos estáticos de ayuda para crear estos objetos ToString. En mi caso, tuve una clase abstracta llamada ToStringWrapper, y tiene métodos de fábrica para unir un iterable con un Guava Joiner, etc.

+0

¿Cómo es eso diferente de la idea de Andrew? Excepto por que su notación es mucho más corta ... * Editar: * Con la edición, su idea ahora es más o menos la misma que la de Andrew. :-) – Wolfgang

+0

@ Wolfgang Sí, me di cuenta de que había leído mal su respuesta. Pensé que su método 'forDebug' ya tenía el formato –

+1

El comportamiento de SLF4J depende del tipo de enlace. – Ceki

5

Si el marco de trabajo de registro subyacente es una implementación nativa como , el toString() llama al los argumentos se realizan mediante el marco de trabajo de registro y no por SLF4J. De esto se desprende que puede invocar DebugFormatter.format (o) cuando el mensaje de registro se emite/imprime creando un custom converter. Más específicamente, crearía un convertidor reemplazando el mensaje% msg /%.

Para implementaciones no nativas, la llamada toString() la realiza SLF4J. Por lo tanto, se aplican las respuestas proporcionadas por Andrew y Sean.

+0

Eso es más o menos lo que estaba buscando. ¡Estupendo! Pero significa que tengo que realizar el reemplazo variable, ¿verdad? Echaré un vistazo cómo se hace en logback y lo probaré. Por ahora (estamos en log4j), elegiré la solución de Andrew y Sean. – Wolfgang

3

Para aquellos que quieren escribir una costumbre Logback Convereter, aquí está el código de un solo he escrito para evitar volcado completo de un objeto de legado:

... 
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; 
    } 

} 

Con esta línea de su logback.xml :

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