Minden alkalmazás életében előbb vagy utóbb eljön a pillanat, hogy szükség lenne naplózásra azért, hogy beleláthassunk a nagy fekete doboz működésébe a debuggeren kívüli életben is. A Log4Net egy kiváló eszköz erre a célra, ráadásul a Nuget Package Managerrel könnyedén hozzá lehet adni az alkalmazásunkhoz. Eléggé részletes a hivatalos dokumentáció arról, hogy miként kell bekonfigurálni, viszont szerintem eleinte nehezen áll össze a teljes kép, pedig viszonylag egyszerűen be lehet üzemelni.
Jelen cikk keretében nem célom a különböző szűrők, naplózási szinten és formázások használatára kitérni, csak arra, hogy miként lehet szóra bírni.
Konfigurációs fájl
A Log4Nethez az úgynevezett appendereket és azok működését egy konfigurációs fájl írja le. Egyidejűleg akár több appendert is megadhatunk, a rendszer automatikusan kezeli, hogy mindegyik meghívódjon. Természetesen a szűrők és a naplózási szintek módosíthatják, hogy egy-egy naplózó utasítás esetén melyik appender fog ténylegesen írni is valamit.
Az alábbi konfig fájl példában a megadott két appenderrel egyidejűleg írunk fájlba óránkénti bontásban és a konzolra is:
<configuration>
<log4net >
<appender name="RollingFileAppender"
type="log4net.Appender.RollingFileAppender">
<file value="log\dm-info" />
<param name="DatePattern" value=".yyyy.MM.dd-HH'.log'"/>
<appendToFile value="true" />
<rollingStyle value="Date" />
<maxSizeRollBackups value="1" />
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date | %-5level | thread:%thread | %message%newline" />
</layout>
</appender>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<target value="Console.Out" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date | %-5level | thread:%thread | %message%newline" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="DEBUG"/>
</filter>
</appender>
<root>
<level value="INFO" />
<appender-ref ref="ConsoleAppender" />
<appender-ref ref="RollingFileAppender" />
</root>
</log4net>
</configuration>
Fontos kiemelni, hogy a konfigfájlon legyen beállítva a Copy to output directory tulajdonság valamelyik Copy lehetőségre, ellenkező esetben nem fog csinálni semmit sem, viszont hibát sem fog dobni.
Inicializálás
Ahhoz, hogy egyáltalán tudjon róla, hogy milyen appenderek vannak beállítva, illetve melyik fájl tartalmazza a konfigot, az XmlConfiguratorral fel kell dolgozni a megadott konfig fájlt. Ez többféleképpen is történhet. Például meghívhatjuk az alkalmazás belépési pontjánál. Ezzel az a baj, hogy ha ki van emelve egy olyan közös projektbe a naplózás, amit több alkalmazás is használ, akkor mindegyik alkalmazásban külön szükséges hozzáadni ezt az utasítást:
log4net.Config.XmlConfigurator.Configure(
new System.IO.FileInfo(@"Path\To\Log4net.config"));
Ennél szerintem szebb és egyszerűbb megoldás, hogy ha a naplózást tartalmazó projekt Properties/AssemblyInfo.cs fájlba hozzáadjuk az alábbi utasítást. A két paramétere megadja, hogy melyik konfigurációs fájlt szeretnénk betölteni hozzá, illetve hogy kell-e frissíteni a beállításokat, hogy ha megváltozott a konfigurációs fájl tartalma. Ezzel a módszerrel a hivatkozó projektekben, alkalmazásokban később már nem kell újra és újra meghívni az XmlConfiguratort.
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "Path\To\Log4Net.config", Watch = true)]
Logger osztály és interface
Miután megvagyunk a konfigurációs fájllal és annak beolvastatásával, jöhet a tényleges logger implementálása. Önmagában a log4net használatához elegendő lenne az alábbi kódrészlet, amivel a log változó metódusaival már lehetne naplózni sorainkat:
private static readonly log4net.ILog log =
log4net.LogManager.GetLogger(typeof(Logger));
Másfelől lehet ezt még csinosítani egy wrapper osztállyal, ami már tetszőleges paraméterezésű metódusokat tartalmazhat kifelé. Az ILogger interface-re azért lehet szükség, mert így a későbbiekben egyéb naplózó logikát lehet implementálni és transzparensen használni.
public class Logger : ILogger
{
private static readonly log4net.ILog log =
log4net.LogManager.GetLogger(typeof(Logger));
#region
ILogger Members
public void LogException(Exception exception, string message)
{
if (log.IsErrorEnabled)
log.Error(
string.Format(CultureInfo.InvariantCulture, "{0}", message),
exception);
}
public void LogException(Exception exception)
{
LogException(exception,
exception.Message);
}
public void LogError(string message)
{
if (log.IsErrorEnabled)
log.Error(string.Format(CultureInfo.InvariantCulture, "{0}", message));
}
public void LogWarningMessage(string message)
{
if (log.IsWarnEnabled)
log.Warn(string.Format(CultureInfo.InvariantCulture, "{0}", message));
}
public void LogInfoMessage(string message)
{
if (log.IsInfoEnabled)
log.Info(string.Format(CultureInfo.InvariantCulture, "{0}", message));
}
public void LogDebug(string message)
{
if (log.IsDebugEnabled)
log.Debug(string.Format(CultureInfo.InvariantCulture, "{0}", message));
}
#endregion
}
A hozzátartozó ILogger interface:
public interface ILogger
{
void LogException(Exception exception);
void LogException(Exception exception, string customMessage);
void LogError(string message);
void LogDebug(string message);
void LogWarningMessage(string message);
void LogInfoMessage(string message);
}
LogHelper osztály
Jó lenne, ha elkerülhetnénk, hogy minden egyes alkalommal, ahol használjuk, példányosítani kelljen a Loggert. A metódus statikussá tétele nem megoldás, mivel az interface-t implementáló metódus nem lehet statikus. Egy lehetséges megoldás a problémára, hogy ha becsomagoljuk egy újabb wrapper osztályba, aminek a statikus metódusai az egyetlen Logger példány metódusait hívják.
public static class LogHelper
{
private static readonly ILogger logger = new Logger();
public static void LogInfo(string message)
{
logger.LogInfoMessage(message);
}
public static void LogInfo(string pattern, params object[] args)
{
LogInfo(string.Format(CultureInfo.InvariantCulture, pattern, args));
}
public static void LogDebug(string message)
{
logger.LogDebug(message);
}
public static void LogDebug(string pattern, params object[] args)
{
LogDebug(string.Format(CultureInfo.InvariantCulture, pattern, args));
}
public static void LogError(string message)
{
logger.LogError(message);
}
public static void LogError(string pattern, params object[] args)
{
LogError(string.Format(CultureInfo.InvariantCulture, pattern, args));
}
public static void LogException(Exception exception, string message)
{
logger.LogException(exception,
message);
}
public static void LogException(
Exception exception, string pattern, params object[] args)
{
string message = string.Format(CultureInfo.InvariantCulture, pattern, args);
LogException(exception, message);
}
public static void LogException(Exception ex)
{
logger.LogException(ex);
}
public static void SetThreadVariable(string key, string variable)
{
log4net.ThreadContext.Properties[key] = variable;
}
public static void SetGlobalVariable(string key, string variable)
{
log4net.GlobalContext.Properties[key] = variable;
}
}
Összegzés
Ezek után nem maradt más hátra, mint hogy használjuk a LogHelper statikus metódusait és élvezzük, ahogy termeli a bejegyzéseket a beállított helyen és formában.
Nincsenek megjegyzések:
Megjegyzés küldése