Registrazione di ClassName e MethodName mediante log4net per un progetto .NET

Ho cercato un modo per registrare nomi di classi e nomi di metodi come parte della mia infrastruttura di logging. Ovviamente mi piacerebbe renderlo semplice da usare e veloce in fase di esecuzione. Ho letto molto sulla registrazione dei nomi delle classi e dei metodi, ma ho incontrato 2 temi.

  1. Questo log4net utilizza un’eccezione internamente genera per generare uno stack frame e diventa costoso se lo si utilizza in genere per tutte le registrazioni.
  2. Confusione. C’è molta letteratura là fuori. Ho provato un sacco di esso e non ho ottenuto qualcosa di utile.

Se mi umori per un secondo, mi piacerebbe resettare.

Ho creato una class come questa nel mio progetto

public static class Log { private static Dictionary _loggers = new Dictionary(); private static bool _logInitialized = false; private static object _lock = new object(); public static string SerializeException(Exception e) { return SerializeException(e, string.Empty); } private static string SerializeException(Exception e, string exceptionMessage) { if (e == null) return string.Empty; exceptionMessage = string.Format( "{0}{1}{2}\n{3}", exceptionMessage, (exceptionMessage == string.Empty) ? string.Empty : "\n\n", e.Message, e.StackTrace); if (e.InnerException != null) exceptionMessage = SerializeException(e.InnerException, exceptionMessage); return exceptionMessage; } private static ILog getLogger(Type source) { lock (_lock) { if (_loggers.ContainsKey(source)) { return _loggers[source]; } ILog logger = log4net.LogManager.GetLogger(source); _loggers.Add(source, logger); return logger; } } public static void Debug(object source, object message) { Debug(source.GetType(), message); } public static void Debug(Type source, object message) { getLogger(source).Debug(message); } public static void Info(object source, object message) { Info(source.GetType(), message); } public static void Info(Type source, object message) { getLogger(source).Info(message); } 

  private static void initialize() { XmlConfigurator.Configure(); } public static void EnsureInitialized() { if (!_logInitialized) { initialize(); _logInitialized = true; } } } 

(Se questo codice sembra familiare è perché è preso in prestito dagli esempi!)

In ogni caso, per tutto il mio progetto utilizzo righe come questa per registrare:

  Log.Info(typeof(Program).Name, "System Start"); 

Bene, questo tipo di opere. Ancora più importante, ottengo il nome della class ma nessun nome di metodo. Meno, importante, sto inquinando il mio codice con questo “tipo di” spazzatura. Se copio e incollo uno snippet di codice tra i file, ecc. Il framework di registrazione mentirà!

Ho provato a giocare con PatternLayout (% C {1}. {M}) ma non ha funzionato (tutto ciò che è stato è stato scrivere “Log.Info” nel log – perché tutto è in routing tramite i metodi statici Log.X !). Inoltre, dovrebbe essere lento.

Quindi, qual è il modo migliore, data la mia configurazione e il mio desiderio di essere semplice e veloce?

Apprezzare qualsiasi aiuto in anticipo.

log4net (e NLog) espongono entrambi un metodo di registrazione che rende ansible “avvolgere” i loro logger e ottenere comunque informazioni corrette sul sito di chiamata. In sostanza, il logger log4net (o NLog) deve essere informato del tipo che forma il “confine” tra il codice di registrazione e il codice dell’applicazione. Penso che chiamino questo “tipo di logger” o qualcosa di simile. Quando le librerie ricevono le informazioni sul sito di chiamata, risalgono lo stack di chiamate finché il MethodBase.DeclaringType è uguale (o forse AssignableFrom) al “tipo di logger”. Il prossimo stack frame sarà il codice di chiamata dell’applicazione.

Ecco un esempio di come accedere tramite NLog da un wrapper (log4net sarebbe simile – guarda i documenti di log4net per l’interfaccia di ILogger (non ILog):

  LogEventInfo logEvent = new LogEventInfo(level, _logger.Name, null, "{0}", new object[] { message }, exception); _logger.Log(declaringType, logEvent); 

Dove dichiarType è una variabile membro impostata come qualcosa di simile:

  private readonly static Type declaringType = typeof(AbstractLogger); 

E “AbstractLogger” è il tipo di wrapper del tuo logger. Nel tuo caso, probabilmente sarebbe simile a questo:

  private readonly static Type declaringType = typeof(Log); 

Se NLog ha bisogno di ottenere le informazioni sul sito di chiamata (a causa degli operatori del sito di chiamata nel layout), naviga nello stack finché il MethodBase.DeclaringType per il frame corrente è uguale (o AssignableFrom) che dichiaraType. Il frame successivo nello stack sarà il sito di chiamata effettivo.

Ecco un codice che funzionerà per la registrazione con un logger log4net “avvolto”. Utilizza l’interfaccia di log4net ILogger e passa il tipo di logger “wrapping” per preservare le informazioni sul sito di chiamata. Non devi compilare una class / struttura di eventi con questo metodo:

  _logger.Log(declaringType, level, message, exception); 

Ancora, “declaringType” è il tipo del tuo wrapper. _logger è il logger log4net, Level è il valore log4net.LogLevel, il messaggio è il messaggio, l’eccezione è l’eccezione (se presente, null altrimenti).

Per quanto riguarda l’inquinamento dei siti di chiamata con Typeof (qualunque cosa), penso che tu sia bloccato con questo se vuoi usare un singolo object “Log” statico. In alternativa, all’interno dei metodi di registrazione dell’object “Log”, è ansible ottenere il metodo di chiamata come la risposta accettata in questo post

Come posso trovare il metodo che ha chiamato il metodo corrente?

Quel collegamento mostra come ottenere il chiamante immediatamente precedente. Se avevi bisogno di ottenere il metodo che ha chiamato la funzione di logging, ma il tuo lavoro viene eseguito un paio di livelli più in profondità, dovrai aumentare lo stack di un numero di frame anziché di un solo frame.

Prendendo tutto questo insieme, scriveresti il ​​tuo metodo Debug qualcosa del genere (di nuovo, questo è in termini di NLog perché è quello che ho di fronte a me):

 public static void Debug(object message) { MethodBase mb = GetCallingMethod(); Type t = mb.DeclaringType; LogEventInfo logEvent = new LogEventInfo(LogLevel.Debug, t.Name, null, "{0}", new object [] message, null); ILogger logger = getLogger(t) As ILogger; logger.Log(declaringType, logEvent) } 

Si noti che probabilmente su StackOverflow non si troveranno molte persone che raccomanderebbero di scrivere una funzione wrapper di registrazione come questa (che ottiene in modo esplicito il metodo di chiamata per ogni chiamata di registro). Non posso dire che lo raccomando, ma risponde più o meno alla domanda che hai chiesto. Se si desidera utilizzare un object “Log” statico, sarà necessario passare esplicitamente il tipo in ogni sito di chiamata di registrazione (per ottenere il logger di class corretto) o sarà necessario aggiungere il codice all’interno della chiamata di registrazione per spostarsi all’interno del impilare e scoprire queste informazioni per conto tuo. Non penso che nessuna di queste opzioni sia particolarmente attraente.

Ora, avendo detto tutto questo, potresti prendere in considerazione l’utilizzo di log4net o NLog direttamente piuttosto che aggiungere questo complicato (e non necessariamente affidabile) codice per ottenere le informazioni sul sito di chiamata. Come sottolinea Matthew, NLog fornisce un modo semplice per ottenere il logger per la class corrente. Per ottenere il logger per la class corrente usando log4net, lo farebbe in ogni class:

 private static readonly log4net.ILog log = log4net.LogManager.GetLogger( System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 

in questo modo con NLog:

  private static readonly NLog.logger log = NLog.LogManager.GetCurrentClassLogger(); 

Questo è un uso abbastanza comune.

Se non si desidera dipendere da una particolare implementazione di registrazione, è ansible utilizzare una delle astrazioni di registrazione disponibili come Common.Logging (NET) o Simple Logging Facade (SLF) .

Anche se non si utilizza una di queste astrazioni, scaricare l’origine per Common.Logging e osservare l’astrazione per log4net. Mostrerà esattamente come avvolgere un logger log4net in modo tale che le informazioni sul sito di chiamata siano preservate (e disponibili per gli operatori di layout).

Preferisco uno schema come il seguente, che funziona con Log4Net e API simili:

 class MyClass { private static readonly ILog logger = LogManager.GetLogger(typeof(MyClass)); void SomeMethod(...) { logger.Info("some message"); ... if (logger.IsInfoEnabled) { logger.Info(... something that is expensive to generate ...); } } } 

Alcune osservazioni:

  • Con questo modello, stai valutando typeof(MyClass) solo una volta – rispetto al campione in cui stai chiamando object.GetType () su ogni chiamata di registrazione, indipendentemente dal fatto che il livello di registrazione corrispondente sia abilitato. Non è un grosso problema, ma in generale è auspicabile che la registrazione abbia un sovraccarico minimo.

  • Hai ancora bisogno di usare typeof, e assicurati di non avere il nome della class sbagliato quando usi copy / paste. Preferisco vivere con questo, perché l’alternativa (ad esempio LogManager.GetCurrentClassLogger di NLog come descritto nella risposta di Matthew Ferreira) richiede di ottenere uno StackFrame che ha un overhead delle prestazioni e richiede che il codice chiamante disponga dell’authorization UnmanagedCode. Per inciso, penso che sarebbe bello se C # fornisse una syntax in fase di compilazione per fare riferimento alla class corrente – qualcosa come la macro _ _ della class _ C ++.

  • Abbandonerei qualsiasi tentativo di ottenere il nome del metodo corrente per tre motivi. (1) Vi è un sovraccarico significativo delle prestazioni e la registrazione dovrebbe essere veloce. (2) Inlining significa che potresti non ottenere il metodo che pensi di ottenere. (3) Impone il requisito dell’authorization UnmanagedCode.

Ho anche fatto delle ricerche su questo e credo che l’unico modo per farlo in modo efficiente sia quello di avvolgere le funzioni di logging, per quanto odio farlo:

 public static void InfoWithCallerInfo(this ILog logger, object message, Exception e = null, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { if (!logger.IsInfoEnabled) return; if (e == null) logger.Info(string.Format("{0}:{1}:{2} {3}", sourceFilePath, memberName, sourceLineNumber, message)); else logger.Info(string.Format("{0}:{1}:{2} {3}", sourceFilePath, memberName, sourceLineNumber, message), e); } 

Gli appunti:

  • Questa è la funzione wrapper che ho scritto su ILog :: Info, ne avresti bisogno anche attorno agli altri livelli di registrazione.
  • Ciò richiede le Informazioni sul chiamante, disponibili solo a partire da. 4.5. L’up-side è che queste variabili vengono sostituite in fase di compilazione con stringhe letterali, quindi è tutto molto efficiente.
  • Per semplicità ho lasciato il parametro sourceFilePath com’è, probabilmente preferiresti formattarlo (tagliare la maggior parte / tutto il suo percorso)

So che hai già un codice che dipende da log4net, ma hai pensato a un altro framework di registrazione che potrebbe soddisfare meglio le tue esigenze? Io personalmente uso NLog per le mie applicazioni. Permette codice come questo:

 class Stuff { private static readonly Logger logger = LogManager.GetCurrentClassLogger(); // ... void DoStuff() { logger.Info("blah blah"); } } 

Per impostazione predefinita, NLog aggiungerà il nome della class e il nome del metodo ai messaggi registrati. Ha un’API molto simile a log4net e include sia la configurazione XML che quella programmatica. Potrebbe valerne la pena.