2012-07-02 6 views
7

Quando si esegue qualcosa come LOG.debug("Exported {}.", product) in slf4j, si chiamerà infine toString() sugli argomenti, ad es. product.slf4j senza toString()

Per determinati motivi, non è possibile eseguire l'override di toString() su tutte le classi che desidero utilizzare come argomenti. Alcune classi provengono da contenitori di terze parti, altre avranno il loro toString() chiamato in altri contesti, dove le informazioni che voglio stampare nella mia dichiarazione di registro non sono disponibili.

Tuttavia, ho una classe per scopi di debug che ha un metodo DebugFormatter.format(Object) che ha una lunga cascata di instanceofs che seleziona la routine per trovare alcune utili informazioni di debug su quell'oggetto.

La mia domanda è: è possibile configurare slf4j in modo che chiami un metodo statico anziché toString()?

Ovviamente, potrei chiamare il mio metodo di formattazione sull'oggetto prima di passarlo come parametro a Logger.debug() ma poi sarebbe eseguito anche quando il rispettivo logger non è abilitato. Così ho dovuto circondarlo con if (LOG.isDebugEnabled()) il che significa che il punto intero di avere argomenti in debug() è stato perso.

+0

Non hai menzionato il framework di registrazione sottostante. È log4j? logback? – Ceki

+0

È log4j. Sto pensando di passare al logback, comunque. – Wolfgang

+0

Ho accettato la risposta di Andrew come risposta ufficiale perché è più semplice da implementare rispetto a Ceki. È anche indipendente dal framework sottostante. Inoltre consente di rendere la formattazione opzionale, o addirittura di scegliere tra diversi formattatori. Tuttavia, per altri utenti, la risposta di Ceki potrebbe adattarsi meglio perché è più trasparente e consente di risparmiare risorse. – Wolfgang

risposta

9

È possibile creare uno spessore prendendo il proprio oggetto e chiamando DebugFormatter.format() dalla sua funzione toString(). Qualcosa di simile a questo:

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 l'importazione statica appropriato, la sua dichiarazione di registrazione diventa questo:

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

questo ha un po 'più in alto rispetto passando l'oggetto in rettilineo, ma è una piccola, luminosa costante - e l'oggetto creato sarà molto breve.

+0

Questa è un'idea abbastanza carina che non ho pensato di. Se il logger è disabilitato, il formattatore non viene chiamato. L'unico inconveniente è che creerei l'oggetto wrapper inutilmente. Non sono sicuro di quanto impatto abbia. – Wolfgang

+0

@Andrew scusa, ho letto male la tua risposta. +1 –

1

Temo che slf4j non sia stato costruito per essere esteso in tal modo. Che cosa è possibile implementare la propria implementazione slf4j che funge da decoratore attorno alle implementazioni esistenti, fornendo una gestione speciale per alcuni tipi noti. Ma dovresti reinventare molte ruote. Ad esempio: i metodi di implementazione del logger dovrebbero iniziare con if(underLyingLogger.isXyzEnabled()), sebbene il logger sottostante esegua nuovamente lo stesso controllo dopo che il decoratore ha chiamato i metodi sottostanti.

D'altra parte, una soluzione che ho utilizzato in passato è un delegato ToString. La forma più semplice può essere solo un oggetto anonimo:

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

Qui hai anche oggetti wrapper di breve durata, ma in realtà non rendere lo Strings fino a quando non è necessario.

poiché la sintassi precedente è molto brutta, suggerisco di fornire una classe Factory con metodi di supporto statici per creare questi oggetti ToString. Nel mio caso ho avuto una classe astratta chiamata ToStringWrapper e ha metodi factory per partecipare a un iterable con un Guava Joiner ecc.

+0

Com'è diverso dall'idea di Andrew? Tranne che la sua notazione è molto più breve ... * Modifica: * Con la modifica, la tua idea ora è praticamente la stessa di Andrew. :-) – Wolfgang

+0

@Wolfgang si, ho appena realizzato di aver letto male la sua risposta. Ho pensato che il suo metodo 'forDebug' già avesse formattato il comportamento di –

+1

SLF4J dipende dal tipo di binding. – Ceki

5

Se il framework di registrazione sottostante è un'implementazione nativa come , la chiamata toString() sul gli argomenti vengono eseguiti dal framework di registrazione e da non da da SLF4J. Ne consegue che è possibile richiamare DebugFormatter.format (o) quando il messaggio di registro viene effettivamente stampato/stampato creando uno custom converter. Più specificamente, si creerebbe un convertitore che sostituisce il messaggio% msg /%.

Per le implementazioni non native, la chiamata toString() viene effettuata da SLF4J. Pertanto, si applicano le risposte fornite da Andrew e Sean.

+0

Questo è praticamente quello che stavo cercando. Grande! Ma significa che devo eseguire la sostituzione della variabile, giusto? Daremo un'occhiata a come è fatto in logback e provalo. Per ora (siamo su log4j), andrò con la soluzione di Andrew e Sean. – Wolfgang

3

Per chi vuole scrivere un personalizzato Logback Convereter, ecco il codice di quello che ho scritto per evitare dump completo di un oggetto di eredità:

... 
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 questa formazione dal logback.xml :

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