2009-02-17 19 views
8

Desidero aggiungere la registrazione o la traccia alla mia applicazione C# ma non voglio che il sovraccarico di formattazione della stringa o dei valori di calcolo vengano registrati se il livello di dettaglio del registro è impostato in modo tale che il messaggio non essere registratoC# logging/tracing condizionale

In C++, è possibile utilizzare il preprocessore per definire le macro che impediscono il codice venga eseguito affatto come questo:

#define VLOG(level,expr) if (level >= g_log.verbosity) { g_log.output << expr; } 

usati in questo modo:

VLOG(5,"Expensive function call returns " << ExpensiveFunctionCall()); 

Come si fa a farlo in C#?

Ho letto i documenti Microsoft che spiegano le strutture di traccia e debug here e affermano che l'utilizzo di #undef DEBUG e #undef TRACE rimuove tutto il codice di traccia e debug dall'eseguibile prodotto, ma rimuove davvero l'intera chiamata ? Significato, se scrivo

System.Diagnostics.Trace.WriteLineIf(g_log.verbosity>=5,ExpensiveFunctionCall()); 

non chiamerà la mia funzione costosa se non riesco a trovare TRACE? O effettua la chiamata, quindi decide che non verrà tracciato nulla?

In ogni caso, anche se lo rimuove, questo è inferiore alla macro C++ perché non riesco a far sembrare quella grossa chiamata simile alla mia semplice chiamata VLOG() in C++ e comunque a evitare di valutare i parametri, posso? Né posso evitare il sovraccarico definendo la verbosità più bassa in fase di esecuzione come posso fare in C++, giusto?

risposta

1

Due di queste risposte (Andrew Arnott e Brian's) hanno risposto in parte alla mia domanda. Il ConditionalAttribute applicato ai metodi di classe Trace e Debug comporta la rimozione di tutte le chiamate ai metodi se TRACE o DEBUG non sono # indefinite, inclusa la costosa valutazione dei parametri. Grazie!

Per la seconda parte, se è possibile rimuovere completamente tutte le chiamate in fase di runtime, non in fase di compilazione, ho trovato la risposta nel log4net fac. Secondo loro, se si imposta una proprietà readonly al momento dell'avvio, il runtime compila tutte le chiamate che non superano il test! Questo non ti permette di cambiarlo dopo l'avvio, ma va bene, è meglio che rimuoverli in fase di compilazione.

0

per il tuo commento

"perché non riesco a fare quel grande aspetto brutto come il mio invito semplice VLOG() chiamata in C++" - Si potrebbe aggiungere un'istruzione using come nell'esempio qui sotto.

 
using System.Diagnostics; 

.... 
Trace.WriteLineIf(.....) 

Da quanto ho capito, rimuoverà le righe che contengono traccia, se si undefine il simbolo Trace.

+0

Il prefisso lungo non è la parte brutta ma il fatto che devo esporre g_log.verbosity e anche il nome della funzione WriteLineIf dappertutto. Voglio avere la mia funzione come VLOG (5, ...). –

0

Non sono sicuro, ma è possibile trovare la risposta da soli.

Rendilo una funzione DAVVERO costosa (come Thread.Sleep(10000)) e ora la chiamata. Se impiega molto tempo, chiama comunque la tua funzione.

(si può avvolgere la chiamata Trace.WriteLineIf() con #if TRACE e #endif e testare nuovamente per un confronto di base.)

9

Per rispondere a una delle domande, tutte le chiamate di metodo che deve valutare, al fine di richiamare Trace.WriteLine (oi suoi fratelli/cugini) non vengono chiamati se Trace.WriteLine è compilato. Quindi vai avanti e metti le tue costose chiamate di metodo direttamente come parametri alla chiamata Trace e verrà rimosso in fase di compilazione se non definisci il simbolo TRACE.

Ora per l'altra domanda relativa alla modifica della verbosità in fase di esecuzione. Il trucco qui è che Trace.WriteLine e metodi simili prendono 'params object [] args' per i loro argomenti di formattazione delle stringhe. Solo quando la stringa viene effettivamente emessa (quando la verbosità è impostata sufficientemente alta) il metodo chiama ToString su quegli oggetti per ottenere una stringa da essi. Quindi, un trucco che gioco spesso è passare oggetti e non stringhe completamente assemblate a questi metodi, e lasciare la creazione della stringa nel ToString dell'oggetto che ho passato.In questo modo l'imposta sulle prestazioni di runtime viene pagata solo quando si verifica effettivamente la registrazione e ti dà la libertà di modificare la verbosità senza dover ricompilare la tua app.

+0

Perché anche le stringhe non verranno compilate? – sharptooth

+0

Se stai chiedendo perché questo oggetto ToString non viene compilato, lo fa, se usi Trace e non definisci la costante TRACE. Questo trucco funziona solo se si utilizza un metodo che non viene compilato. Avrei potuto essere più chiaro Scusate. –

+0

Questo è abbastanza divertente ma sembra essere vero. Il test: 'var i = 0; Debug.WriteLine (i ++); Console.WriteLine (" i = "+ i);' – gatopeich

2

ConditionalAttribute è il vostro migliore amico. La chiamata verrà completamente rimossa (come se i siti di chiamata fossero # if'd) quando il #define non è impostato.

EDIT: (grazie) qualcuno ha messo questo in un commento, ma degno di nota nel corpo principale risposta:

Tutti i metodi della classe Trace sono decorate con condizionale ("TRACE"). Ho appena visto questo usando il riflettore.

Ciò significa che Trace.Blah (... costoso ...) scompare completamente se TRACE non è definito.

+0

Tutti i metodi della classe Trace sono decorati con Conditional ("TRACE"). Ho appena visto questo usando il riflettore. – shahkalpesh

+0

Ecco come funziona! Ora, se solo potessi farlo in fase di esecuzione, sarei molto felice. –

0

Invocherà la chiamata costosa perché potrebbe avere effetti collaterali desiderati.

Quello che puoi fare è decorare il tuo metodo costoso con un attributo [Conditional ("TRACE")] o [Conditional ("DEBUG")]. Il metodo non verrà compilato nell'eseguibile finale se la costante DEBUG o TRACE non è definita, né le chiamate per eseguire il metodo costoso.

2

Una soluzione che ha funzionato per me sta utilizzando una classe singleton. Può esporre le tue funzioni di registrazione e puoi controllarne il comportamento in modo efficiente. Chiamiamo la classe 'AppLogger'. Lei è un esempio

public class AppLogger 
{ 
    public void WriteLine(String format, params object[] args) 
    { 
     if (LoggingEnabled) 
     { 
      Console.WriteLine(format, args); 
     } 
    } 
} 

Nota, la roba di Singleton è esclusa dall'esempio precedente. Ci sono tonnellate di buoni esempi fuori dai tubi. La cosa interessante è come supportare il multi-threading. L'ho fatto in questo modo: (abbreviato per brevità, hahahaha)

public static void WriteLine(String format, params object[] args) 
{ 
    if (TheInstance != null) 
    { 
     TheInstance.TheCreatingThreadDispatcher.BeginInvoke( Instance.WriteLine_Signal, format, args); 
    } 
} 

In questo modo, qualsiasi thread può accedere ei messaggi vengono gestiti sul thread creazione originale. O potresti creare un thread speciale solo per gestire l'output di registrazione.

+0

Questa soluzione è inutile e priva di valore senza il ConditionalAttribute che i metodi di traccia hanno. Senza che venga definito TRACE, qualsiasi metodo invoca un metodo in Trace non verrà compilato e il metodo costoso non verrà mai chiamato, dove come nel tuo, verrà chiamato ogni volta. – Samuel

+2

No, non è inutile e senza valore (è prety severo). Si noti che la chiamata a ConsoleWriteline (o qualsiasi altro metodo di registrazione scelto) si trova all'interno di un'istruzione condizionale. Quindi paghi solo il prezzo se la registrazione è abilitata. – Foredecker

+0

Sono con Foredecker qui, la risposta che dà è equivalente all'esempio che l'OP dà nella sua domanda. Il problema con ConditionalAttribute è che l'OP sembra voler controllarlo in fase di runtime. Se la riga è stata rimossa dal compilatore, non sarà disponibile per l'attivazione in produzione. –

2

Tutte le informazioni su Conditional (Trace) sono buone, ma presumo che la vera domanda è che si desidera lasciare le chiamate Trace nel codice di produzione ma (di solito) disabilitarle in fase di esecuzione a meno che non si verifichi un problema.

Se si utilizza TraceSource (che ritengo si dovrebbe, piuttosto che chiamare Trace direttamente perché offre un controllo più dettagliato sulla traccia a livello di componente in fase di esecuzione), è possibile fare qualcosa di simile :

if (Component1TraceSource.ShouldTrace(TraceEventType.Verbose)) 
    OutputExpensiveTraceInformation() 

Questo presuppone che è in grado di isolare i parametri di traccia in un'altra funzione (ad esempiodipendono per lo più da membri della classe corrente piuttosto che da operazioni costose sui parametri della funzione in cui è presente questo codice).

Il vantaggio di questo approccio è che JITer si basa su una funzione funzione per funzione come è necessario, se il "se" viene valutato come falso, la funzione non solo non verrà chiamata, non sarà nemmeno essere JITed. Il rovescio della medaglia è (a) che hai separato la conoscenza del livello di tracciamento tra questa chiamata e la funzione OutputExpensiveTraceInformation (quindi se ad esempio cambi TraceEventType in TraceEventType.Information, ad esempio, non funzionerà perché non sarai mai chiamalo anche se TraceSource non è abilitato per la traccia del livello Verbose in questo esempio) e (b) è più codice da scrivere.

Questo è un caso in cui sembrerebbe che un preprocessore simile a C possa essere d'aiuto (poiché potrebbe garantire, ad esempio, che il parametro su ShouldTrace e l'eventuale chiamata a TraceEvent coincidano), ma capisco perché C# non include quello

Il suggerimento di Andrew di isolare operazioni costose nei metodi .ToString degli oggetti che si passano a TraceEvent è anche buono; in quel caso, potresti ad esempio sviluppare un oggetto che è appena usato per Trace a cui passi gli oggetti che vuoi costruire una costosa rappresentazione in stringa e isolare quel codice nel metodo ToString dell'oggetto trace piuttosto che farlo nel elenco dei parametri alla chiamata TraceEvent (che causerà l'esecuzione anche se TraceLevel non è abilitato in fase di esecuzione).

Spero che questo aiuti.