Warum empfehlen Logger die Verwendung eines Loggers pro Klasse?

87

Gemäß der NLog-Dokumentation:

Die meisten Anwendungen verwenden einen Logger pro Klasse, wobei der Name des Loggers mit dem Namen der Klasse übereinstimmt.

Dies ist die gleiche Art und Weise, wie log4net funktioniert. Warum ist das eine gute Praxis?

Daniel T.
quelle
1
hmm. Anscheinend gibt es hier zwei Probleme - eines hat ein tatsächliches Protokollobjekt pro Klasse und eines hat den Namen des Protokolls, der mit dem der Klasse übereinstimmt.
Peter Recore

Antworten:

59

Mit log4net erleichtert die Verwendung eines Loggers pro Klasse das Erfassen der Quelle der Protokollnachricht (dh der Klasse, die in das Protokoll schreibt). Wenn Sie nicht einen Logger pro Klasse haben, sondern einen Logger für die gesamte App, müssen Sie auf weitere Reflexionstricks zurückgreifen, um zu wissen, woher die Protokollnachrichten stammen.

Vergleichen Sie Folgendes:

Protokoll pro Klasse

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

public void SomeMethod()
{
    _logger.DebugFormat("File not found: {0}", _filename);
}

Ein Logger pro App (oder ähnlich)

Logger.DebugFormat("File not found: {0}", _filename); // Logger determines caller

-- or --

Logger.DebugFormat(this, "File not found: {0}", _filename); // Pass in the caller

Anhand des zweiten Beispiels müsste der Logger einen Stack-Trace erstellen, um zu sehen, wer ihn aufruft, oder Ihr Code müsste immer den Aufrufer übergeben. Mit dem Logger-pro-Klasse-Stil tun Sie dies immer noch, aber Sie können dies einmal pro Klasse anstatt einmal pro Aufruf tun und ein ernstes Leistungsproblem beseitigen.

Jeremy Wiebe
quelle
Danke, das hilft, die Dinge zu klären. Wir haben den Klassennamen und die Methode nur manuell in die Nachricht eingefügt (dh "ImageCreator.CreateThumbnail () aufgerufen"), aber es ist besser, wenn der Logger damit umgehen kann.
Daniel T.
1
Nur zu Ihrer Information, es ist "besser" geworden, einen Logger pro Instanz und nicht pro Klasse (dh statisch) zu haben, da dies das Erfassen von Informationen wie Thread-Informationen erleichtert. Natürlich ist es Geschmackssache, keine "harte und schnelle Regel", aber ich wollte das einfach rauswerfen.
Will Hartung
7
@will, kannst du das etwas mehr erklären? Bei der Protokollierung mit einem Logger pro Klasse protokolliere ich immer die Thread-ID, damit der Logger die aktuellen Thread-Informationen abrufen kann. Alle anderen Thread-Informationen stehen dem Logger ebenfalls zur Verfügung.
Jeremy Wiebe
@ Jeremy Wiebe: Ist das der einzige Grund? Funktionell gibt es keine Probleme, wenn ich eine einzelne globale Variable vom Typ Logger für die gesamte App verwende?
Giorgim
1
@Giorgi Nein, das glaube ich nicht. Sie können heutzutage viele dieser Informationen mit den CallerInformation-Attributen abrufen, wodurch der eine Logger pro Klasse etwas weniger relevant wird - msdn.microsoft.com/en-us/library/hh534540.aspx
Jeremy Wiebe
15

Vorteil bei der Verwendung von "Logger pro Datei" in NLog: Sie haben die Möglichkeit, Protokolle nach Namespace und Klassennamen zu verwalten / filtern. Beispiel:

<logger name="A.NameSpace.MyClass"      minlevel="Debug" writeTo="ImportantLogs" /> 
<logger name="A.NameSpace.MyOtherClass" minlevel="Trace" writeTo="ImportantLogs" /> 
<logger name="StupidLibrary.*"          minlevel="Error" writeTo="StupidLibraryLogs" />

<!-- Hide other messages from StupidLibrary -->
<logger name="StupidLibrary.*" final="true" /> 

<!-- Log all but hidden messages -->
<logger name="*" writeTo="AllLogs" /> 

NLogger hat dazu ein sehr nützliches Code-Snippet. Das nloggerSnippet erstellt den folgenden Code:

private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

Also nur wenige Tastenanschläge und Sie haben Logger pro Klasse. Es werden Namespace und Klassenname als Name des Loggers verwendet. Um Ihrem Klassenlogger einen anderen Namen zu geben, können Sie Folgendes verwenden:

private static NLog.Logger logger = NLog.LogManager.GetLogger("MyLib.MyName");

Und wie @JeremyWiebe sagte, müssen Sie keine Tricks anwenden, um den Namen der Klasse zu ermitteln, die versucht, eine Nachricht zu protokollieren: Der Name des Loggers (normalerweise der Name der Klasse) kann einfach in einer Datei protokolliert werden (oder ein anderes Ziel) durch Verwendung ${logger}im Layout.

CoperNick
quelle
5

Ich kann einige Gründe für diese Wahl sehen.

  • Sie wissen immer, woher eine bestimmte Protokollanweisung stammt, wenn Sie den Namen des Protokollierers in Ihr Protokollausgabeformat aufnehmen.
  • Sie können steuern, welche Protokollanweisungen auf einer fein abgestimmten Ebene angezeigt werden, indem Sie bestimmte Protokollierer ein- oder ausschalten oder deren Ebene festlegen.
Peter Recore
quelle
4

Es gibt auch einen Leistungsvorteil bei NLog. Die meisten Benutzer werden verwenden

Logger logger = LogManager.GetCurrentClassLogger()

Das Nachschlagen der aktuellen Klasse aus dem Stack-Trace erfordert etwas (aber nicht viel) Leistung.

julianisch
quelle
3

In den meisten Fällen bietet der Name der Klasse einen guten Namen für den Logger. Beim Scannen der Protokolldateien können Sie die Protokollnachricht anzeigen und direkt einer Codezeile zuordnen.

Ein gutes Beispiel, bei dem dies nicht der beste Ansatz ist, sind die SQL-Protokolle von Hibernate. Es gibt einen gemeinsam genutzten Logger mit dem Namen "Hibernate.SQL" oder ähnliches, bei dem verschiedene Klassen Raw-SQL in eine einzelne Logger-Kategorie schreiben.

Eric Hauser
quelle
2

Aus entwicklungspolitischer Sicht ist es am einfachsten, wenn Sie nicht jedes Mal ein Logger-Objekt erstellen müssen. Wenn Sie dies jedoch nicht tun, sondern es mithilfe von Reflexion dynamisch erstellen, wird die Leistung verlangsamt. Um dies zu lösen, können Sie den folgenden Code verwenden, der den Logger dynamisch asynchron erstellt:

using NLog;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WinForms
{
    class log
    {

        public static async void Log(int severity, string message)
        {
            await Task.Run(() => LogIt(severity, message));
        }

        private static void LogIt(int severity, string message)
        {
            StackTrace st = new StackTrace();
            StackFrame x = st.GetFrame(2);     //the third one goes back to the original caller
            Type t = x.GetMethod().DeclaringType;
            Logger theLogger = LogManager.GetLogger(t.FullName);

            //https://github.com/NLog/NLog/wiki/Log-levels
            string[] levels = { "Off", "Trace", "Debug", "Info", "Warn", "Error", "Fatal" };
            int level = Math.Min(levels.Length, severity);
            theLogger.Log(LogLevel.FromOrdinal(level), message);

        }
    }
}
as9876
quelle
1

Zwei Gründe fallen mir sofort ein:

  1. Ein separates Protokoll für jede Klasse erleichtert das Zusammenfassen aller Protokollmeldungen / Fehler, die sich auf eine bestimmte Klasse beziehen.
  2. Wenn Sie ein Protokoll innerhalb einer Klasse haben, können Sie interne Details protokollieren, auf die außerhalb der Klasse möglicherweise nicht zugegriffen werden kann (z. B. privater Status, Informationen zur Implementierung einer Klasse usw.).
Dan Tao
quelle
2
Sie haben einen Logger in der Klasse, unabhängig davon, ob er auf Klassenebene oder global definiert ist. Globale Logger befinden sich aus Sicht der Sichtbarkeit nicht außerhalb der Klasse. Sie verweisen immer noch auf den globalen Logger innerhalb der betreffenden Klasse, damit Sie die volle Sichtbarkeit haben.
Robert
0

Wahrscheinlich, weil Sie Methoden protokollieren möchten, die nur für die Klasse sichtbar sind, ohne die Kapselung zu unterbrechen, ist es auch einfach, die Klasse in einer anderen Anwendung zu verwenden, ohne die Protokollierungsfunktion zu beeinträchtigen.

Rodrick Chapman
quelle
1
Es macht es schwierig, diese Klasse in einer anderen Anwendung zu verwenden. Sie müssen auf die Protokollierungsbibliothek verweisen, ob es Ihnen gefällt oder nicht.
Piotr Perak
0

Erleichtert die Konfiguration von Appendern nach Namespace oder Klasse.

dotjoe
quelle
0

Wenn Sie NLOG verwenden, können Sie die Aufrufstelle in der Konfiguration angeben. Dadurch werden der Klassenname und die Methode aufgezeichnet, in der sich die Protokollierungsanweisung befand.

<property name="CallSite" value="${callsite}" />

Sie können dann eine Konstante für Ihren Loggernamen oder den Assemblynamen verwenden.

Haftungsausschluss: Ich weiß nicht, wie NLOG diese Informationen sammelt. Meine Vermutung wäre eine Reflexion, daher müssen Sie möglicherweise die Leistung berücksichtigen. Es gibt einige Probleme mit Async-Methoden, wenn Sie NLOG v4.4 oder höher nicht verwenden.

user6271979
quelle