C # registrazione / traccia condizionale

Voglio aggiungere la registrazione o la traccia alla mia applicazione C # ma non voglio che il sovraccarico della 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 verrà registrato.

In C ++, è ansible utilizzare il preprocessore per definire macro che impediranno l’esecuzione del codice in questo modo:

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

Usato in questo modo:

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

Come lo fai in C #?

Ho letto i documenti Microsoft che spiegano le funzionalità di traccia e debug qui e affermano che l’utilizzo di #undef DEBUG e #undef TRACE rimuove tutto il codice di traccia e di 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 riesame di TRACE? O fa la chiamata, quindi decidi che non traccerà nulla?

Ad ogni modo, 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?

Per rispondere a una delle tue domande, tutte le chiamate di metodo che devono essere valutate per chiamare Trace.WriteLine (oi relativi fratelli / cugini) non vengono richiamate se Trace.WriteLine viene 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 la tua altra domanda riguardante la modifica della tua 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’object che ho passato. In questo modo l’imposta sul rendimento di runtime viene pagata solo quando la registrazione è effettivamente in corso, e ti dà la libertà di modificare la verbosità senza ricompilare la tua app.

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

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

Nota, le cose Singleton sono lasciate fuori dall’esempio sopra. 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 e i messaggi vengono gestiti sul thread di creazione originale. O potresti creare un thread speciale solo per gestire l’output di registrazione.

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

EDIT: qualcuno ha inserito questo in un commento (grazie!), Ma vale la pena notare nel corpo della risposta principale:

Tutti i metodi della class Trace sono decorati con Conditional (“TRACE”). Ho appena visto questo usando il riflettore.

Il che significa che Trace.Blah (… costoso …) scompare completamente se TRACE non è definito.

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

Se stai usando TraceSource (che credo dovresti, piuttosto che chiamare Trace direttamente perché ti dà un controllo più dettagliato sulla traccia a livello di componente in fase di esecuzione), puoi fare qualcosa del genere:

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

Ciò presuppone che tu sia in grado di isolare i parametri di traccia in un’altra funzione (cioè dipendono principalmente dai membri della class corrente piuttosto che da operazioni costose sui parametri alla funzione in cui è presente questo codice).

Il vantaggio di questo approccio è dato dal fatto che JITer si compila in base alla funzione in base alle esigenze, se il “se” viene valutato come falso, la funzione non solo non verrà chiamata, ma non sarà nemmeno JIT. 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 sarebbe utile un preprocessore simile a C (dato che potrebbe assicurarsi, ad esempio, che il parametro su ShouldTrace e l’eventuale chiamata a TraceEvent siano gli stessi), ma capisco perché C # non includilo

Anche il suggerimento di Andrew di isolare operazioni costose nei metodi .ToString degli oggetti passati a TraceEvent è buono; in quel caso, potresti ad esempio sviluppare un object che è appena usato per Trace a cui passi gli oggetti che vuoi build una costosa rappresentazione in stringa e isolare quel codice nel metodo ToString dell’object 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 ti aiuti.

hai provato una sofisticata API di registrazione come log4net ( http://logging.apache.org/log4net/index.html )?

Due di queste risposte (Andrew Arnott e Brian) hanno risposto in parte alla mia domanda. Il ConditionalAttribute applicato ai metodi di class 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 è ansible 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.

Per il tuo commento

“perché non riesco a far sembrare quella grossa chiamata simile alla mia semplice chiamata VLOG () in C ++” – Puoi aggiungere un’istruzione using come di seguito.

 utilizzando System.Diagnostics;

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

Come ho capito, rimuoverà le linee contenenti Trace, se non definisci il simbolo Trace.

Non sono sicuro, ma puoi trovare la risposta da solo.

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

(Puoi racchiudere la chiamata a Trace.WriteLineIf() con #if TRACE e #endif e testarla nuovamente per un confronto di base.)

Richiama 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.