Gemäß 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 Funktionsweise von log4net. Warum ist das eine gute Praxis?
Mit log4net ist es mit einem Logger pro Klasse leicht, die Quelle der Protokollnachricht zu erfassen (dh die Klasse, die in das Protokoll schreibt). Wenn Sie nicht über einen Logger pro Klasse verfügen, stattdessen jedoch einen Logger für die gesamte App, müssen Sie weitere Reflektionstricks verwenden, um zu erfahren, woher die Protokollnachrichten kommen.
Vergleichen Sie folgendes:
using System.Reflection;
private static readonly ILog _logger =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public void SomeMethod()
{
_logger.DebugFormat("File not found: {0}", _filename);
}
Logger.DebugFormat("File not found: {0}", _filename); // Logger determines caller
-- or --
Logger.DebugFormat(this, "File not found: {0}", _filename); // Pass in the caller
Im zweiten Beispiel müsste der Logger einen Stack-Trace erstellen, um zu sehen, wer ihn aufgerufen hat, oder der Code muss immer im Aufrufer übergeben werden. Mit dem Logger-per-Class-Stil können Sie dies immer noch tun, aber Sie können es einmal pro Klasse und nicht einmal pro Anruf tun und ein schwerwiegendes Leistungsproblem beseitigen.
Vorteil für die Verwendung von "Logger pro Datei" in NLog: Sie haben die Möglichkeit, Protokolle nach Namespace und Klassennamen zu verwalten. 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 bietet dazu ein sehr nützliches Code-Snippet. Das nlogger
-Snippet erstellt den folgenden Code:
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
Also nur wenige Tastenanschläge und Sie haben einen Logger pro Klasse. Es verwendet Namespace und Klassennamen als Namen des Loggers. 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 verwenden, um den Namen der Klasse zu erhalten, die versucht, eine Nachricht zu protokollieren (oder ein anderes Ziel) mithilfe von ${logger}
im Layout.
Ich kann einige Gründe für diese Wahl sehen.
In den meisten Fällen liefert der Name der Klasse einen guten Namen für den Logger. Beim Scannen der Protokolldateien können Sie die Protokollnachricht sehen und direkt einer Codezeile zuordnen.
Ein gutes Beispiel, wo 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 einzige Logger-Kategorie schreiben.
Es gibt auch einen Leistungsvorteil bei NLog. Die meisten Benutzer werden verwenden
Logger logger = LogManager.GetCurrentClassLogger()
Das Nachschlagen der aktuellen Klasse aus der Stack-Ablaufverfolgung erfordert einige (aber nicht viel) Leistung.
Aus Sicht der Entwicklung ist es am einfachsten, wenn Sie nicht jedes Mal ein Logger-Objekt erstellen müssen. Wenn Sie dies jedoch nicht tun, sondern dynamisch mithilfe von Reflektionen erstellen, wird die Leistung beeinträchtigt. 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);
}
}
}
Zwei Gründe fallen sofort ein:
Wahrscheinlich, weil Sie Methoden protokollieren möchten, die nur für die Klasse sichtbar sind, ohne die Kapselung zu beschädigen. Dies macht es auch einfacher, die Klasse in einer anderen Anwendung zu verwenden, ohne die Protokollierungsfunktion zu beeinträchtigen.
Ermöglicht die einfache Konfiguration von Appendern nach Namespace oder Klasse.
Wenn Sie NLOG verwenden, können Sie die Callsite in der Konfiguration angeben. Dadurch werden der Klassenname und die Methode aufgezeichnet, an der sich die Protokollierungsanweisung befunden hat.
<property name="CallSite" value="${callsite}" />
Sie könnten 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, so dass Sie möglicherweise die Leistung berücksichtigen müssen. Es gibt einige Probleme mit Async-Methoden, wenn Sie NLOG v4.4 oder höher nicht verwenden.