Ablaufverfolgung

Ablaufprotokollierung (3)

Die ersten beiden Teile der Reihe haben die Grundlagen zur Ablaufprotokollierung behandelt. Trace, Debug, Schalter, Switches und Listener sind jetzt bekannt, ebenso wie das Verhalten der Ablaufverfolgung über die app.config beeinflusst wird. Dabei wurde bislang ausschließlich mit den Bordmitteln des .NET Frameworks gearbeitet.

Wenn nun aber die Standardausgabe nicht ausreicht, ist es Zeit selbst Hand anzulegen und einen eigenen TraceListener zu schreiben! Dies ist das Thema des dritten Teils dieser Reihe.

Überblick

Auch wenn das .NET Framework schon einige Listener von Haus aus bereitstellt, so gibt es doch immer Fälle in denen dies nicht ausreicht. Sei es weil eine besondere Funktionalität benötigt wird (z. B. Übergabe an einen Web-Dienst) oder auch nur, um das Ausgabeformat anzupassen.

Im Folgenden soll ein konfigurierbarer Trace-Listener entstehen, der die Ausgabe formatiert in eine Datei schreibt und zudem noch ein paar Komfortfunktionen bietet.

Anforderungen

Folgenden Funktionsumfang wird der TraceListener haben:

  • Ausgabe in eine Textdatei
  • Konfigurierbarer Dateiname
  • Neue Ausgabedatei bei Erreichen einer bestimmten Dateigröße bzw. pro Tag/ Woche/Monat
  • Formatierte Ausgabe mit (nahezu) fester Spaltenbreite oder als CSV-Datei

Grundlagen

Bevor es an den Code geht müssen noch einige Abläufe in den Klassen TraceSource und TraceListener betrachtet werden.

Die TraceSource Klasse besitzt die Eigenschaft Listeners in der für ein TraceSource Objekt eine Auflistung der angeschlossenen TraceListener gespeichert wird. Wird im Programm nun z. B. die Methode TraceEvent aufgerufen so wird die Liste der Listener durchlaufen und die entsprechende TraceEvent Methode des jeweiligen Listeners aufgerufen.

In der TraceListener Klasse selbst enden Aufrufe der Methoden TraceEvent, TraceData und TraceTransfer in Aufrufen der (in der Basisklasse abstrakten) Methoden Write und WriteLine. Der Vollständigkeit halber sei noch erwähnt, dass die TraceTransfer Methode wiederum die TraceEvent Methode aufruft.

Ein einfaches Beispiel wird diese Abläufe verdeutlichen:

Dazu erstellen wir zunächst eine neue von TraceListener abgeleitete Klasse. Bei dieser müssen die beiden abstrakten Methoden Write(string message) sowie WriteLine(string message) überladen werden. Die Ausgabe erfolgt im Konsolenfenster; zur Unterscheidung welche Methode die Ausgabe erzeugt hat, wird die Ausgabe unterschiedlich eingefärbt.

using System;
using System.Diagnostics;

namespace Logging
{
    public class BlogTraceListener : TraceListener
    {
        public override void Write(string message)
        {
            Console.Write("Write: " + message);
        }

        public override void WriteLine(string message)
        {
            Console.ForegroundColor = ConsoleColor.White;
            Console.WriteLine("WriteLine: " + message);
            Console.ForegroundColor = ConsoleColor.Gray;
        }
    }
}

Im Beispielprogramm wird eine Instanz des neuen TraceListeners erstellt und der Listeners Kollektion des TraceSource Objekts hinzugefügt. Anschließend werden über die TraceSource einige Trace Nachrichten ausgegeben.

using System;
using System.Diagnostics;

namespace Logging
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Beispiel 14");
            Console.WriteLine("");

            SourceSwitch loggingSwitch = new SourceSwitch("testSwitch");
            loggingSwitch.Level = SourceLevels.All;
            BlogTraceListener listener = new BlogTraceListener();

            TraceSource loggingSource = new TraceSource("MeineTraceSource");

            loggingSource.Switch = loggingSwitch;
            loggingSource.Listeners.Add(listener);

            loggingSource.TraceInformation("Logging: Eintrag mit TraceInformation");
            loggingSource.TraceEvent(TraceEventType.Information, 1, "Logging: Eintrag mit TraceEvent/Information");
            loggingSource.TraceEvent(TraceEventType.Verbose, 2, "Logging: Eintrag mit TraceEvent/Verbose");
            loggingSource.TraceEvent(TraceEventType.Warning, 3, "Logging: Eintrag mit TraceEvent/Warning" + Environment.NewLine + "und Zeilenumbruch");

            try
            {
                int i = 0;
                int r = 5 / i;
            }
            catch (Exception ex)
            {
                loggingSource.TraceData(TraceEventType.Critical, 4, ex);
                loggingSource.TraceData(TraceEventType.Error, 5, new object[] {ex, loggingSource, new DateTime(DateTime.Now.Ticks) } );
            }

            loggingSource.TraceTransfer(6, "Logging: Eintrag mit TraceTransfer", Guid.NewGuid());

            loggingSource.Close();

            Console.ReadLine();
        }
    }
}

Wird das Beispielprogramm ausgeführt, erhält man folgende Ausgabe:

Logging_14 - Write und WriteLine

Dabei fällt auf, dass die Ausgaben stets durch einen Aufruf von Write mit Ablaufverfolgungsinformationen (Event Typ und ID) sowie einem Aufruf von WriteLine mit der eigentlichen Nachricht erfolgen.

Daraus folgt, dass allein mit dem Überschreiben der Write/WriteLine Methoden die gewünschte Funktionalität nicht zu erreichen ist, sondern dass auch TraceData, TraceEvent und TraceTransfer angepasst werden müssen.

Die app.config Datei

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.diagnostics>
    <sources>
      <source name="MeineTraceSource" switchName="MeinSchalter">
        <listeners>
          <add name="blogTraceListener" />
          <remove name="Default" />
        </listeners>
      </source>
    </sources>
    <sharedListeners>
      <add name="blogTraceListener" type="Logging.BlogTraceListener, Logging" initializeData="C:TempLogFile-{0}-{1}-{2:D8}-{3:D8}.log;T,FIX" />
    </sharedListeners>
    <switches>
      <add name="MeinSchalter" value="Information,ActivityTracing" />
    </switches>
  </system.diagnostics>
</configuration>

Der neue Listener wird wie üblich im Abschnitt eingebunden. Auch wenn die Parameter schon durch die Standard Listener bekannt sind, werfen wir diesmal einen genaueren Blick auf sie:

name=blogTraceListener:
Wie bei allen anderen Listenern – das Kind braucht einen Namen.

type=Logging.BlogTraceListener, Logging:
Hier muss der vollständig gekennzeichnete Typname den Listeners angegeben werden.
Hinweis: Für alle Beispielprojekte wurde in den Projekteigenschaften für Assemblyname und Standardnamespace Logging eingetragen.

initializeData=C:TempLogFile-{0}-{1}-{2:D8}-{3:D8}.log;T;FIX:
Die hier angegebene Zeichenkette wird an den Konstruktor des Listeners übergeben.

Die BlogTraceListener-Klasse

Um also den neuen Listener mit den angegebenen Daten zu initialisieren muss der Konstruktor TraceListener(string name) implementiert werden.

public BlogTraceListener(string initializeData)
{
    initData(initializeData);
}

In der Variable initializeData wird genau der String-Wert übergeben, der in der app.config angegeben wurde; im Beispiel oben also “C:TempLogFile-{0}-{1}-{2:D8}-{3:D8}.csv;T;EXCSV&quot. initData verarbeitet dann diesen String, um Variablen zu initialisieren, den Dateinamen aufzubauen, erste Informationen in die Log-Datei zu schreiben, etc. Details dazu können den Kommentaren im Quellcode entnommen werden.

Die formatierte Ausgabe in die Protokolldatei erfolgt ausschließlich mit der nachfolgenden Methode writeLine().

private void writeLine(string message, string category, long id, DateTime dateTime, int threadId, int processId)
{
	fileOpen();

	checkSwitchReason();

	if (category.Length > maxCategoryNameLength)
		category = category.Substring(0, maxCategoryNameLength);

	string formatString;
	if (outputFormat == OutputFormat.CSV || outputFormat == OutputFormat.ExcelCSV)
	{
		if (outputFormat == OutputFormat.ExcelCSV)
			formatString = messageFormatStringCSV.Replace(",", ";");
		else
			formatString = messageFormatStringCSV;

		category = string.Format(""{0}"", category);
		if (message.IndexOfAny(new char[] { '"', ',', ';' }) != -1)
			message = message.Replace(""", """");
		message = string.Format(""{0}"", message);
	}
	else
		formatString = messageFormatStringFixedFirstLine;

	if (outputFormat == OutputFormat.CSV || outputFormat == OutputFormat.ExcelCSV)
	{
		logFileStream.WriteLine(formatString,
			category.ToUpper(),
			id,
			threadId,
			processId,
			dateTime.ToString("yyyy-MM-dd HH:mm:ss"),
			message);
	}
	else
	{
		string[] lines = message.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
		bool firstLine = true;

		foreach (string line in lines)
		{
			if (firstLine)
			{
				logFileStream.WriteLine(formatString,
					category.ToUpper(),
					id,
					threadId,
					processId,
					dateTime.ToString("yyyy-MM-dd HH:mm:ss"),
					line);
				firstLine = false;
			}
			else
				logFileStream.WriteLine(messageFormatStringFixedLines, line);
		}
	}

	loggedMessages++;

	fileClose();
}

Der Ablauf ist denkbar einfach:

  1. Protokolldatei öffnen
  2. Prüfen, ob die Protokolldatei gewechselt werden soll – wenn ja:
    1. Informationen zum Wechsel in die Protokolldatei schreiben
    2. Datei schließen und verschieben
    3. Neue Protokolldatei öffnen
  3. Je nach Ausgabeformat die Information in die Protokolldatei schreiben
  4. Protokolldatei schließen

Nun zum “eigentlichen” Protokollieren, d. h. zu Write/WriteLine und den verschiedenen Trace Methoden. Da ja ausschließlich writeLine() für die Ausgabe in die Datei verantwortlich ist, erfolgt in diese Methoden ein entsprechender Aufruf.

Write

Da eine der Anforderungen an den Listener ist, dass jeder Trace Eintrag in einer eigenen Zeile steht, werden sämtliche Write-Methoden so überschrieben, dass sie das entsprechende WriteLine Pendant aufrufen:

public override void Write(string message)
{
    this.WriteLine(message);
}

public override void Write(string message, string category)
{
    this.WriteLine(message, category);
}

public override void Write(object o)
{
    this.WriteLine(o);
}

public override void Write(object o, string category)
{
    this.WriteLine(o, category);
}

WriteLine

Der Aufruf von writeLine() erfolgt in der Methode WriteLine(string message, string category). Die anderen WriteLine-Methoden rufen eben diese Methode auf.

public override void WriteLine(string message)
{
    this.WriteLine(message, defaultCategory);
}

public override void WriteLine(string message, string category)
{
    int threadId = Thread.CurrentThread.ManagedThreadId;
    int processId = Process.GetCurrentProcess().Id;

    this.writeLine(message, category, 0, DateTime.Now, threadId, processId);
}

public override void WriteLine(object o)
{
    this.WriteLine(o, defaultCategory);
}

public override void WriteLine(object o, string category)
{
    this.WriteLine(o.ToString(), category);
}

TraceData, TraceEvent und TraceTransfer

Wie weiter oben schon beschrieben müssen die Methoden überschrieben werden, da die Ausgabe in der Basisklasse über mehrere Write/WriteLine Aufrufe erfolgt und somit nicht die Anforderungen nicht erfüllt. Die überschriebenen Methoden rufen daher direkt writeLine() auf.

public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data)
{
    this.writeLine(source + ": " + data.ToString(), eventType.ToString(), id, eventCache.DateTime, Convert.ToInt32(eventCache.ThreadId), eventCache.ProcessId);
}

public override void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, params object[] data)
{
    string message = "";

    foreach (object obj in data)
    {
if (message != "")
    message += ", ";
message += obj.ToString();
    }
    this.writeLine(source + ": " + message, eventType.ToString(), id, eventCache.DateTime, Convert.ToInt32(eventCache.ThreadId), eventCache.ProcessId);
}

public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id)
{
    this.TraceEvent(eventCache, source, eventType, id, "");
}

public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message)
{
    if ((eventType == TraceEventType.Error || eventType == TraceEventType.Critical) && eventCache.Callstack != "")
    {
if (message != "")
    message += Environment.NewLine;
message += eventCache.Callstack;
    }
    this.writeLine(source + ": " + message, eventType.ToString(), id, eventCache.DateTime, Convert.ToInt32(eventCache.ThreadId), eventCache.ProcessId);
}

public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
{
    if (args != null)
this.TraceEvent(eventCache, source, eventType, id, string.Format(format, args));
    else
this.TraceEvent(eventCache, source, eventType, id, format);
}

public override void TraceTransfer(TraceEventCache eventCache, string source, int id, string message, Guid relatedActivityId)
{
    TraceEventType eventType = TraceEventType.Transfer;
    this.writeLine(source + ": " + relatedActivityId.ToString() + ": " + message, eventType.ToString(), id, eventCache.DateTime, Convert.ToInt32(eventCache.ThreadId), eventCache.ProcessId);
}

Der BlogTraceListener im Einsatz

Im folgenden Beispielprogramm kommt der neue Listener dann zum Einsatz:

using System;
using System.Diagnostics;

namespace Logging
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Beispiel 15");
            Console.WriteLine("");

            TraceSource loggingSource = new TraceSource("MeineTraceSource");

            loggingSource.TraceInformation("Logging: Eintrag mit TraceInformation");
            loggingSource.TraceInformation("Logging: Eintrag mit TraceInformation");
            loggingSource.TraceEvent(TraceEventType.Information, 1, "Logging: Eintrag mit TraceEvent/Information");
            loggingSource.TraceEvent(TraceEventType.Verbose, 2, "Logging: Eintrag mit TraceEvent/Verbose");
            loggingSource.TraceEvent(TraceEventType.Warning, 3, "Logging: Eintrag mit TraceEvent/Warning" + Environment.NewLine + "und Zeilenumbruch");

            try
            {
                int i = 0;
                int r = 5 / i;
            }
            catch (Exception ex)
            {
                loggingSource.TraceData(TraceEventType.Critical, 4, ex);
                loggingSource.TraceData(TraceEventType.Error, 5, new object[] { ex, loggingSource, new DateTime(DateTime.Now.Ticks) });
            }

            loggingSource.TraceTransfer(6, "Logging: Eintrag mit TraceTransfer", Guid.NewGuid());

            loggingSource.Close();

            Console.ReadLine();
        }
    }
}

Lässt man das Programm laufen, findet man im Verzeichnis C:Temp die Protokolldatei. Diese sieht dann in etwa wie folgt aus:

TRACE        0000 00010 04336 2013-05-08 16:34:32 Trace gestartet - DebugVersion
TRACE        0000 00010 04336 2013-05-08 16:34:32 initializeData="C:TempLogFile-{0}-{1}-{2:D8}-{3:D8}.log;T,FIX"
TRACE        0000 00010 04336 2013-05-08 16:34:32 Dateiwechsel täglich
TRACE        0000 00010 04336 2013-05-08 16:34:32 Assembly:  1.0.0.0
TRACE        0000 00010 04336 2013-05-08 16:34:32 Framework: 4.0.30319.18034
TRACE        0000 00010 04336 2013-05-08 16:34:32 Anwender:  urunkel
TRACE        0000 00010 04336 2013-05-08 16:34:32 Domain:    RUNKEL
TRACE        0000 00010 04336 2013-05-08 16:34:32 Hostname:  VM-DEV
TRACE        0000 00010 04336 2013-05-08 16:34:32 -------------------------------------------------------------------------
TRACE        0000 00010 04336 2013-05-08 16:34:32 Thread:    10
TRACE        0000 00010 04336 2013-05-08 16:34:32 Process:   4336
TRACE        0000 00010 04336 2013-05-08 16:34:32 Session:   1
TRACE        0000 00010 04336 2013-05-08 16:34:32 -------------------------------------------------------------------------
TRACE        0000 00010 04336 2013-05-08 16:34:32 
INFORMATION  0000 00010 04336 2013-05-08 14:34:32 MeineTraceSource: Logging: Eintrag mit TraceInformation
INFORMATION  0000 00010 04336 2013-05-08 14:34:32 MeineTraceSource: Logging: Eintrag mit TraceInformation
INFORMATION  0001 00010 04336 2013-05-08 14:34:32 MeineTraceSource: Logging: Eintrag mit TraceEvent/Information
WARNING      0003 00010 04336 2013-05-08 14:34:32 MeineTraceSource: Logging: Eintrag mit TraceEvent/Warning
                                                  und Zeilenumbruch
CRITICAL     0004 00010 04336 2013-05-08 14:34:32 MeineTraceSource: System.DivideByZeroException: Es wurde versucht, durch 0 (null) zu teilen.
                                                     bei Logging.Program.Main(String[] args) in C:UsersuwruSkyDriveDokumenteLoggingTeil 3Logging_Samples_CSLogging_15Program.cs:Zeile 24.
ERROR        0005 00010 04336 2013-05-08 14:34:32 MeineTraceSource: System.DivideByZeroException: Es wurde versucht, durch 0 (null) zu teilen.
                                                     bei Logging.Program.Main(String[] args) in C:UsersuwruSkyDriveDokumenteLoggingTeil 3Logging_Samples_CSLogging_15Program.cs:Zeile 24., System.Diagnostics.TraceSource, 08.05.2013 16:34:32
TRANSFER     0006 00010 04336 2013-05-08 14:34:32 MeineTraceSource: 73c0943a-4e5a-47dc-8436-e999b8b9d92e: Logging: Eintrag mit TraceTransfer
TRACE        0000 00002 04336 2013-05-08 16:34:34 Trace beendet

Wir passen nun in der app.config den initializeData Parameter an

    <sharedListeners>
      <add name="blogTraceListener" type="Logging.BlogTraceListener, Logging" initializeData="C:TempLogFile-{0}-{1}-{2:D8}-{3:D8}.csv;T,EXCSV" />
    </sharedListeners>

und starten das Programm dann noch einmal. Diesmal erhält man folgende Datei:

"TRACE";0;11;6712;2013-05-08 16:37:15;"Trace gestartet - DebugVersion"
"TRACE";0;11;6712;2013-05-08 16:37:15;"initializeData=""C:TempLogFile-{0}-{1}-{2:D8}-{3:D8}.csv;T,EXCSV"""
"TRACE";0;11;6712;2013-05-08 16:37:15;"Dateiwechsel täglich"
"TRACE";0;11;6712;2013-05-08 16:37:15;"Assembly:  1.0.0.0"
"TRACE";0;11;6712;2013-05-08 16:37:15;"Framework: 4.0.30319.18034"
"TRACE";0;11;6712;2013-05-08 16:37:15;"Anwender:  urunkel"
"TRACE";0;11;6712;2013-05-08 16:37:15;"Domain:    RUNKEL"
"TRACE";0;11;6712;2013-05-08 16:37:15;"Hostname:  VM-DEV"
"TRACE";0;11;6712;2013-05-08 16:37:15;"-------------------------------------------------------------------------"
"TRACE";0;11;6712;2013-05-08 16:37:15;"Thread:    11"
"TRACE";0;11;6712;2013-05-08 16:37:15;"Process:   6712"
"TRACE";0;11;6712;2013-05-08 16:37:15;"Session:   1"
"TRACE";0;11;6712;2013-05-08 16:37:15;"-------------------------------------------------------------------------"
"TRACE";0;11;6712;2013-05-08 16:37:15;""
"INFORMATION";0;11;6712;2013-05-08 14:37:15;"MeineTraceSource: Logging: Eintrag mit TraceInformation"
"INFORMATION";0;11;6712;2013-05-08 14:37:15;"MeineTraceSource: Logging: Eintrag mit TraceInformation"
"INFORMATION";1;11;6712;2013-05-08 14:37:15;"MeineTraceSource: Logging: Eintrag mit TraceEvent/Information"
"WARNING";3;11;6712;2013-05-08 14:37:15;"MeineTraceSource: Logging: Eintrag mit TraceEvent/Warning
und Zeilenumbruch"
"CRITICAL";4;11;6712;2013-05-08 14:37:15;"MeineTraceSource: System.DivideByZeroException: Es wurde versucht, durch 0 (null) zu teilen.
   bei Logging.Program.Main(String[] args) in D:devblogLoggingTeil 3Logging_Samples_CSLogging_16Program.cs:Zeile 24."
"ERROR";5;11;6712;2013-05-08 14:37:15;"MeineTraceSource: System.DivideByZeroException: Es wurde versucht, durch 0 (null) zu teilen.
   bei Logging.Program.Main(String[] args) in D:devblogLoggingTeil 3Logging_Samples_CSLogging_16Program.cs:Zeile 24., System.Diagnostics.TraceSource, 08.05.2013 16:37:15"
"TRANSFER";6;11;6712;2013-05-08 14:37:15;"MeineTraceSource: 2eb08cc9-0a2f-4236-9943-9b2ebe30bbcd: Logging: Eintrag mit TraceTransfer"
"TRACE";0;2;6712;2013-05-08 16:37:16;"Trace beendet"

Diese CSV-Datei lässt sich dann auch problemlos mit Excel öffnen.

Trace-Ausgabe in Excel

Bleibt noch die Frage, wozu auch Write/WriteLine überschreiben, werden diese von TraceData, TraceEvent und TraceTransfer doch gar nicht mehr aufgerufen? Ganz einfach: werden für die Ausgabe Debug oder Trace (statt einer TraceSource) benutzt, ruft beispielsweise Trace.Write() die Write() Methode des Listeners auf.

Wie das dann funktioniert zeigt das nächste Beispiel:

using System;
using System.Diagnostics;

namespace Logging
{
    class Program
    {
        static void Main(string[] args)
        {
            BlogTraceListener listener = new BlogTraceListener("C:\Temp\TraceFile-{0}-{1}-{2:D8}-{3:D8}.log;T,FIX");
            Trace.Listeners.Add(listener);
            Trace.Listeners.Remove("Default");
            Trace.AutoFlush = true;

            Console.WriteLine("Beispiel 17");

            TraceSwitch traceSwitch = new TraceSwitch("MeinSchalter", "Ablaufprotokollierung mit Schalter");
            traceSwitch.Level = TraceLevel.Info;

            Trace.WriteLine("Beispiel 17 gestartet: " + DateTime.Now.ToString());
            Trace.Write("=========================================");

            Trace.WriteLineIf(traceSwitch.TraceError, "Logging: Eintrag mit TraceLevel Error");
            Trace.WriteLineIf(traceSwitch.TraceWarning, "Logging: Eintrag mit TraceLevel Warning");
            Trace.WriteLineIf(traceSwitch.TraceInfo, "Logging: Eintrag mit TraceLevel Info");
            Trace.WriteLineIf(traceSwitch.TraceVerbose, "Logging: Eintrag mit TraceLevel Verbose");

            Console.ReadLine();
        }
    }
}

Die Ausgabe sieht dann wie folgt aus:

TRACE        0000 00008 05640 2013-05-08 16:41:18 Trace gestartet - DebugVersion
TRACE        0000 00008 05640 2013-05-08 16:41:18 initializeData="C:TempTraceFile-{0}-{1}-{2:D8}-{3:D8}.log;T,FIX"
TRACE        0000 00008 05640 2013-05-08 16:41:18 Dateiwechsel täglich
TRACE        0000 00008 05640 2013-05-08 16:41:18 Assembly:  1.0.0.0
TRACE        0000 00008 05640 2013-05-08 16:41:18 Framework: 4.0.30319.18034
TRACE        0000 00008 05640 2013-05-08 16:41:18 Anwender:  urunkel
TRACE        0000 00008 05640 2013-05-08 16:41:18 Domain:    RUNKEL
TRACE        0000 00008 05640 2013-05-08 16:41:18 Hostname:  VM-DEV
TRACE        0000 00008 05640 2013-05-08 16:41:18 -------------------------------------------------------------------------
TRACE        0000 00008 05640 2013-05-08 16:41:18 Thread:    8
TRACE        0000 00008 05640 2013-05-08 16:41:18 Process:   5640
TRACE        0000 00008 05640 2013-05-08 16:41:18 Session:   1
TRACE        0000 00008 05640 2013-05-08 16:41:18 -------------------------------------------------------------------------
TRACE        0000 00008 05640 2013-05-08 16:41:18 
NONE         0000 00008 05640 2013-05-08 16:41:18 Beispiel 17 gestartet: 08.05.2013 16:41:18
NONE         0000 00008 05640 2013-05-08 16:41:18 =========================================
NONE         0000 00008 05640 2013-05-08 16:41:18 Logging: Eintrag mit TraceLevel Error
NONE         0000 00008 05640 2013-05-08 16:41:18 Logging: Eintrag mit TraceLevel Warning
NONE         0000 00008 05640 2013-05-08 16:41:18 Logging: Eintrag mit TraceLevel Info
TRACE        0000 00002 05640 2013-05-08 16:41:20 Trace beendet

Fazit

Neben dem Grundverständnis für die Ablaufprotokollierung in den Teilen Eins und zwei wurde in diesem letzten Teil der Blog-Reihe ein eigener TraceListener erstellt. Dabei soll der hier vorgestellte Listener wiederum nur Grundlage sein. Erweiterte Funktionalitäten wie z. B. Thread-Sicherheit und vieles, vieles mehr wurden dabei nicht berücksichtigt.

Wie so oft lohnt es auch sich etwas umzuschauen, um das Rad nicht neu erfinden zu müssen. Es gibt unzählige und wirklich exzellente (OpenSource) Logging-Frameworks. NLog, log4net, ObjectGuy Framework oder der Logging Application Block der Enterprise Library, nur um einige wenige zu nennen.

Eine Übersicht über zahlreiche Logging Frameworks findet man z. B. hier. Dort gibt es auch einen Vergleich eines kommerziellen (das des Seitenbetreibers) mit einigen der bekanntesten OpenSource Frameworks.

Dateien:
Sourcecode der Beispiele (C#)
Sourcecode der Beispiele (VB.NET)
Ablaufprotokollierung Teil 3 im PDF-Format

Veröffentlicht in .NET, How To und verschlagwortet mit , , , , , , , .