Heute habe ich mir ein paar Gedanken zum Thema Logging mit C# gemacht. Für das umfangreiche Projekt, an dem ich zur Zeit arbeite, musste ich eine einfache und flexible Möglichkeit finden, verschiedene Meldungen (Hinweis, Erfolg, Fehler) zu loggen, ohne die zahlreichen Klassen von einer bestimmten Implementierung abhängig zu machen. Meine Lösung hierzu sieht wie folgt aus (das Diagramm habe ich mit ArgoUML erstellt):
Erklärung
- Mein Entwurf basiert auf dem Singleton Logger über den an beliebiger Stelle in meinen verschiedenen Klassen einfach mittels Logger.Log(LogEintragTyp.Fehler, "Fehler!") Meldungen geloggt werden können. Hierzu ist lediglich der Verweis auf meine Assembly Logging nötig, da Log() eine statische Methode ist und Logger nicht instantiiert werden muss.
- Der konkrete Logger, der die Ausgabe der Meldugen übernimmt, wird mittels Logger.SetLogger() gesetzt (geschieht dies nicht, wird halt nichts ausgegeben).
- Das Interface ILogger muss von Klassen implementiert werden, die als Logger fungieren sollen. Meine Beispielimplementierung ConsoleLogger ist unten abgebildet.
- Diese Implementierung weist eine große Flexibilität aus, weil ich zur Laufzeit den ausgebenden Logger wechseln kann (z.B. von Konsole zu Logdatei) und dank des Structs LogEintrag einfach weitere Attribute zu den Logmeldungen hinzugefügt werden können, ohne die vorhandenen Quelltexte großartig anpassen zu müssen. In meinen verschiedenen Klassen muss ich lediglich auf meine Assembly verweisen (mittels using Logging; und kann dann mit nur einer Zeile (ohne lästige Prüfung auf null etc.) einen Logeintrag erstellen.
Und so sieht das Ergebnis aus:
Quelltexte
Der Logger
/// <summary>
/// Ein global verfügbarer Logger.
/// </summary>
public sealed class Logger
{
#region Statische Member
private static readonly Logger _instanz = new Logger();
/// <summary>
/// Loggt die übergebene Meldung.
/// </summary>
/// <param name="typ">Typ der Meldung.</param>
/// <param name="meldung">Text der Meldung.</param>
public static void Log(LogEintragTyp typ, string meldung)
{
_instanz.NeueMeldung(typ, meldung);
}
/// <summary>
/// Setzt den Logger, der zur Ausgabe der Meldungen verwendet werden soll.
/// </summary>
/// <param name="logger"></param>
public static void SetLogger(ILogger logger)
{
_instanz._logger = logger;
}
#endregion
#region Instanzmember
private ILogger _logger = null;
private List<LogEintrag> _eintraege = new List<LogEintrag>();
private void NeueMeldung(LogEintragTyp typ, string meldung)
{
LogEintrag le = new LogEintrag(typ, meldung);
this._eintraege.Add(le);
if (this._logger != null)
this._logger.NeueMeldung(le);
}
#endregion
}
Logger-Implementierung
public interface ILogger
{
/// <summary>
/// Wird aufgerufen, sobald eine neue Meldung geloggt wird.
/// </summary>
/// <param name="meldung">Die neue Meldung.</param>
void NeueMeldung(LogEintrag meldung);
/// <summary>
/// Gibt die Liste aller geloggten Meldungen zurück.
/// </summary>
/// <returns>Die Liste aller geloggten Meldungen.</returns>
List<LogEintrag> GetMeldungen();
}
Zur farbigen Ausgabe auf der Konsole habe ich die hier beschriebene Klasse verwendet: Putting colour/color to work on the console.
/// <summary>
/// Ein Logger, der die Meldungen lediglich (farbig) auf der Konsole ausgibt.
/// </summary>
public class ConsoleLogger : ILogger
{
#region ConsoleColor
/// <summary>
/// Static class for console colour manipulation.
/// (http://www.codeproject.com/csharp/console_apps__colour_text.asp)
/// </summary>
private class ConsoleColor
{
// source code of ConsoleColour from http://www.codeproject.com/csharp/console_apps__colour_text.asp
}
#endregion
#region ILogger Member
/// <summary>
/// Gibt eine neue Meldung auf der Konsole aus.
/// </summary>
/// <param name="meldung">Die neue Meldung.</param>
public void NeueMeldung(LogEintrag meldung)
{
switch (meldung.Typ)
{
case LogEintragTyp.Erfolg:
ConsoleColor.SetForeGroundColour(ConsoleColor.ForeGroundColour.Green);
break;
case LogEintragTyp.Fehler:
ConsoleColor.SetForeGroundColour(ConsoleColor.ForeGroundColour.Red);
break;
default:
break;
}
Console.WriteLine(meldung.Zeitpunkt.ToString("HH:mm:ss.ffffff") + " " + meldung.Typ.ToString() + ": " + meldung.Text);
ConsoleColor.SetForeGroundColour();
}
/// <summary>
/// Nicht implementiert.
/// </summary>
/// <returns>-</returns>
public List<LogEintrag> GetMeldungen()
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
Log-Einträge
/// <summary>
/// Mögliche Typen eines Log-Eintrags.
/// </summary>
public enum LogEintragTyp
{
/// <summary>
/// Erfolgsmeldung.
/// </summary>
Erfolg,
/// <summary>
/// Fehlermeldung.
/// </summary>
Fehler,
/// <summary>
/// Hinweis.
/// </summary>
Hinweis
}
/// <summary>
/// Eine Meldung für das Log.
/// </summary>
public struct LogEintrag
{
/// <summary>
/// Der Typ der aktuellen Meldung.
/// </summary>
public LogEintragTyp Typ;
/// <summary>
/// Der Text der aktuellen Meldung.
/// </summary>
public string Text;
/// <summary>
/// Zeitpunkt zu dem die Meldung erstellt wurde.
/// </summary>
public DateTime Zeitpunkt;
/// <summary>
/// Konstruktor.
/// </summary>
/// <param name="typ">Der Typ der neuen Meldung.</param>
/// <param name="text">Der Text der neuen Meldung.</param>
public LogEintrag(LogEintragTyp typ, string text)
{
this.Typ = typ;
this.Text = text;
this.Zeitpunkt = DateTime.Now;
}
/// <summary>
/// Gibt Typ und Text der aktuellen Meldung aus.
/// </summary>
/// <returns>Typ und Text der aktuellen Meldung.</returns>
public override string ToString()
{
return this.Typ.ToString() + ": " + this.Text;
}
}