Ordnung im Log: Erweiterbarer Logging-Service für Custom Solutions
avatar

Bei der Entwicklung von Custom Solutions unter SharePoint ist eine Möglichkeit zur Diagnose von Problemen und Fehlern und zur Unterstützung bei der Entwicklung in Form eines Logging natürlich genauso unerlässlich wie bei jeder anderen professionellen Software auch. Eigene Entwicklungen und Methoden für ein Logging unter .NET sind natürlich denkbar und in Form von Bibliotheken wie z.B. log4net auch reichlich vorhanden, aber unter SharePoint sollte ein anderer Weg eingeschlagen werden.

SharePoint und der ULS

Im Falle von SharePoint bietet sich die Verwendung des ULS (Unified Logging Service) an, gerade auch aus dem Grund weil SharePoint selbst dort seine Logging-Informationen ablegt und so alle relevanten Informationen an einer zentralen Stelle landen.
Außerdem bietet SharePoint schon die nötigen Mittel um eigene Logging-Komponenten direkt in seinem Logging-System zu registrieren und damit sowohl das ULS in Form eines Logs auf Textdatei-Basis (auch “Tracing” genannt) zu nutzen, als auch Diagnose und Fehlermeldungen über das Event-System von Windows zugänglich zu machen.

Dieser Blogpost soll aber nur die Möglichkeiten der ULS Implementation von SharePoint beschreiben, das Bereitstellen von Windows -Events ist ein eigenes Thema.

Die Verwendung des SharePoint Logging-Service bzw. des ULS bringt einige Vorteile mit sich:

  • Administratoren haben alle wichtigen Informationen in einheitlicher Form an einer zentralen Stelle und können bewährte Software zum Monitoring verwenden
  • Entwickler können auf ein bewährtes und stabiles System zurückgreifen und können das Logging schneller integrieren
  • SharePoint / ULS bieten Möglichkeiten zur granularen Einstellung von Log-Details auf Basis von Kategorien für gezielte Diagnosen

Möglichkeiten mit dem SharePoint Logging-Service

Der Logging-Service von SharePoint bietet grundsätzlich die Möglichkeit Log-Einträge mit Hilfe von Metadaten zu kategorisieren und zu bestimmen mit welchem Detailgrad (und Häufigkeit) Einträge der jeweiligen Kategorien letztendlich in die ULS Log-Dateien des SharePoint Servers geschrieben werden. Anhand dieses “Trace Level” (oder analog “Event Level” für Windows Events) können Administratoren und Entwickler gezielter Informationen erhalten und dabei vermeiden zu umfangreiche und speicherfressende Sammlungen an Log-Dateien zu erhalten. Weiterhin enthalten Log-Einträge des ULS zusätzliche Informationen um gezielt nach Einträgen suchen zu können und auch zusammenhängende Einträge (z.B. pro Webpage-Aufruf) anhand von Correlations-IDs ausfindig zu machen.

Ein typischer Ausschnitt aus einem ULS Log kann folgendermaßen aussehen:

SNAGHTML39d568

Interessant für Custom Solutions und deren Komponenten ist hierbei die Möglichkeit eigene Werte für die “Product”-, “Category”- und “EventID”-Metadaten zu registrieren und zu verwenden um Einträge gezielter ausfindig und sie im Sinne des “Trace Level” konfigurierbar zu machen.

  • Product: “Logging-Bereich” unter SharePoint
  • Category: Logging-Kategorie der einzelnen Logging-Bereiche
  • EventID: numerischer Wert zur Unterscheidung von unterschiedlichen Einträgen einer Logging-Kategorie
  • Level: Detailgrad und Art des Logeintrags (Fehler, Information etc.; Gegenstück zu “Trace Level”)

Erweiterbarer Logging-Service für Custom Solutions

Man kann den Logging-Service von SharePoint in Custom Solutions durch die Verwendung der Server-API mit eigenem Code auf zwei Arten nutzen:

  • Statisch mit Hilfe eines allgemeinen Service-Objekts
    • Vorteil: extrem einfach zu implementieren
    • Nachteil: es können keine benutzerdefinierten Kategorien verwendet werden und das Trace-Level von Einträgen ist nicht gezielt konfigurierbar
  • Dynamisch mit Hilfe eines registrierten benutzerdefinierten Objekts
    • Vorteil: es werden benutzerdefinierten Kategorien verwendet und das Trace-Level von Einträgen ist gezielt konfigurierbar
    • Nachteil: aufwändiger zu implementieren

Um zu demonstrieren wie die Möglichkeiten des ULS voll genutzt werden können wird hier nur die zweite Variante aufgezeigt.

Mit Hilfe eines “dynamischen” benutzerdefinierten Logging-Service kann dann auch folgendes erreicht werden:

SNAGHTML1abc0e

Eine volle Integration der eigenen Logging-Bereiche und –Kategorien inklusive Konfigurationsmöglichkeit in der Zentraladministration von SharePoint!

Und damit nicht jede Custom Solution einen eigenen SharePoint-Service definieren und registrieren muss, was im Prinzip nur für unnötigen Overhead sorgt, ist ein erweiterbarer Logging-Service der das übernimmt natürlich ein idealer Grundstein für Eigenentwicklungen. Ziel des erweiterbaren Logging-Service ist es schlussendlich anderen Custom Solutions zu ermöglichen mit wenig Aufwand und Code ihre eigenen Kategorien registrieren und verwenden zu können.

Implementierung des Logging-Service

Das Kernelement eines benutzerdefinierten Logging-Service stellt eine Klasse dar welche von SPDiagnosticsServiceBase ableitet:

[Guid("93C134D4-C0B4-4FB6-87F4-ECEC56E5FA72")]
public class LoggingService : SPDiagnosticsServiceBase
{
    public static readonly string ServiceName = "Custom Logging Service";
 
    [Persisted]
    public List<LoggingArea> RegisteredAreas = new List<LoggingArea>();
 
    public LoggingService()
        : base(ServiceName, SPFarm.Local)
    {
    }
 
    protected override IEnumerable<SPDiagnosticsArea> ProvideAreas()
    {
        return GetDiagnosticsAreaList(RegisteredAreas);
    }
 
    public static LoggingService Current
    {
        get 
        {
            return SPFarm.Local.Services.GetValue<LoggingService>(ServiceName); 
        }
    }
 
    ...
    ...
}

Außer der Basisklasse sind in dem Codeausschnitt noch weitere wichtige Elemente definiert:

  • Attribut: [Guid(„xxxxx“)]
    • Wird in diesem Fall zur eindeutigen Identifizierung der Service-Klasse in der SharePoint-DB verwendet
  • Atttribut: [Persisted]
    • Veranlasst SharePoint den Wert der Eigenschaft dauerhaft zu speichern (die Werte von “normalen” Eigenschaften sind für diese Service-Klasse nicht persistent das sie von SharePoint regelmäßig de-/serialisiert wird)
  • Klasse: LoggingArea
    • Benutzerdefinierte SPPersistedObject-Klasse zur Verwaltung der registrierten Logging-Bereiche und –Kategorien
  • Methode: ProvideAreas()
    • Einzige abstrakte Methode der SPDiagnosticsServiceBase-Klasse; muss von der benutzerdefinierten Service-Klasse implementiert werden und dient zur Registrierung sämtlicher Logging-Bereiche und –Kategorien des Service
  • Eigenschaft: Current
    • Statische Eigenschaft mit der anderer Code leicht auf die Instanz des Logging-Service für die aktuelle Farm zugreifen kann

Erweiterte Konfiguration und Persistenz

Eine weitere benötigte Klasse ist wie schon erwähnt die Helferklasse LoggingArea:

public class LoggingArea : SPPersistedObject
{
	[Persisted]
	public string AreaName = string.Empty;
	[Persisted]
	public string[] CategoryNames = new string[0];

	public LoggingArea()
	{           
	}

	public LoggingArea(string name, SPPersistedObject parent, Guid id)
		: base(name, parent, id)
	{            
	}

	public LoggingArea(string name, SPPersistedObject parent)
		: base(name, parent)
	{            
	}       
}

Es handelt es sich hierbei grundlegend um ein SPPersistedObject der SharePoint-API und dient zum persistenten Speichern von Daten in der SharePoint Config-DB. Auch bei der Service-Klasse selbst handelt es sich um ein SPPersistedObject und beide Klassen unterliegt dementsprechend dessen Vor- und Nachteilen (detaillierte Informationen zum SPPersistedObject gibt es HIER). In diesem Fall dient die Klasse zum Abspeichern der benötigten Informationen für alle benutzerdefinierten Logging-Bereiche.

  • Eine weitere wichtige Methode der Service-Klasse regelt das Hinzufügen neuer Logging-Bereiche durch anderen Code und Solutions und deren Registrierung in SharePoint-System:
    public static void RegisterLoggingArea(string areaName, string[] categoryNames)
    {
    	// persistentes Objekt erstellen und hinzufügen
    	LoggingArea areaInfo = new LoggingArea(areaName, Current);
    	areaInfo.AreaName = areaName;
    	areaInfo.CategoryNames = categoryNames;
    	areaInfo.Update();
    	Current.RegisteredAreas.Add(areaInfo);
    
    	// Logging-Service re-installieren 
    	Current.Delete();
    	LoggingService service = new LoggingService();
    	service.Update();
    }

    Hier in dieser Methode wird auch ein kleines Problem gelöst: die ProvideAreas()-Methode der Klasse wird nur bei Installation eines Service-Objekts aufgerufen! Damit also neue Logging-Bereiche erfolgreich registriert werden muss der Service einfach re-installiert werden, d.h. das alte Objekt gelöscht und ein neues angelegt werden.

    Die letzte wichtige Methode dient zum Auslesen der gespeicherten Informationen über die Logging-Bereiche für deren Registrierung:

    private static List<SPDiagnosticsArea> GetDiagnosticsAreaList(List<LoggingArea> areaInfos)
    {
        List<SPDiagnosticsArea> areas = new List<SPDiagnosticsArea>();
     
        foreach (LoggingArea areaInfo in areaInfos)
        {
            List<SPDiagnosticsCategory> categories = new List<SPDiagnosticsCategory>();
     
            foreach (string category in areaInfo.CategoryNames)
            {
                categories.Add(new SPDiagnosticsCategory(category, TraceSeverity.Medium, EventSeverity.Information));
            }
     
            areas.Add(new SPDiagnosticsArea(areaInfo.AreaName, categories));
        }
     
        return areas;
    }

    Weitere Methoden welche z.B. für das grundlegende Installieren des Service etc. nötig sind werden hier aus Gründen des Umfangs nicht besprochen.

    Verwendung des Logging-Service

    Damit nun auch komfortabel auf den Logging-Service und seine Funktionalitäten zugegriffen werden kann bietet sich die Erstellung einer statischen Utility-Klasse an welche alle wichtigen Funktionen kapselt und weitere Funktionen, z.B. zur Formatierung von Log-Nachrichten liefern kann.

    public static class Log
    {
        public static void Information(string areaName, string categoryName, string message, params object[] data)
        {
            WriteEntry(areaName, categoryName, TraceSeverity.Medium, message, data);
        }
        public static void Information(string areaName, string categoryName, Exception exception, string message, params object[] data)
        {
            Information(areaName, categoryName, GetExtendedMessage(message, exception), data);
        }
     
        public static void Error(string areaName, string categoryName, string message, params object[] data)
        {
            WriteEntry(areaName, categoryName, TraceSeverity.Unexpected, message, data);
        }
        public static void Error(string areaName, string categoryName, Exception exception, string message, params object[] data)
        {
            Error(areaName, categoryName, GetExtendedMessage(message, exception), data);
        }
     
        private static string GetExtendedMessage(string message, Exception exception)
        {
            string extMessage = message;
            if (exception != null)
                extMessage += " Exception: " + exception.Message;
            return extMessage;
        }
     
        private static void WriteEntry(string areaName, string categoryName, TraceSeverity severity, string message, params object[] data)
        {
            SPDiagnosticsCategory category = LoggingService.GetAreaCetagory(areaName, categoryName);
            LoggingService.Current.WriteTrace(1, category, severity, message, data);
        }
    }
    
    

    HINWEIS: Hier in Zeile 32 könnte noch die angezeigte EventID genauer definiert werden, beispielhaft wird hier aber nur “1” verwendet.

    Bevor aber Log-Einträge erstellt werden können muss noch ein Logging-Bereich hinzugefügt und registriert werden. Das geschieht am besten in einem Feature-Event-Receiver der Custom Solutions welche das Logging verwenden sollen:

    public override void FeatureActivated(SPFeatureReceiverProperties properties)
    {
        // eigenen Logging-Bereich inkl. Kategorien registrieren 
        string logArea = "Custom Solution 1";
        string[] logCategories = new string[] { "Runtime", "TimerJobs", "WebServices" };
        LoggingService.RegisterLoggingArea(logArea, logCategories);
    }
    
    

    Log-Einträge können dann von anderem Code aus sehr einfach und vor allem schön kurz erstellt werden:

    Log.Error("Custom Solution 1", "Runtime", "Das ist leider eine Fehlernachricht!");

    Und schon hat mein ein einheitliches und komfortables Logging unter SharePoint erreicht was sowohl Administratoren als auch Entwicklern zugute kommt und die Möglichkeiten des ULS vollständig ausnutzen kann. Und nebenbei hat man auch noch einen wiederverwend- und erweiterbaren Service für die Verwendung mit anderen Custom Solutions bereitgestellt!

    Verlinkte Beiträge:

    Daten unter SharePoint speichern – SPPersistedObject

    Quellen:

    http://msdn.microsoft.com/en-us/library/ff647362.aspx – Using Event and Trace Logs in SharePoint

    http://msdn.microsoft.com/de-de/library/ff512738(v=office.14).aspx – Übersicht über den vereinheitlichten Protokollierungsdienst (Unified Logging Service, ULS)

    http://msdn.microsoft.com/en-us/library/windows/desktop/aa964766(v=vs.85).aspx – Windows Events

  • 3 Gedanken zu “Ordnung im Log: Erweiterbarer Logging-Service für Custom Solutions
    avatar

    1. Pingback: Ordnung im Log: Erweiterbarer Logging-Service für Custom Solutions - SharePoint Blogs in German - Bamboo Nation

    2. Danke für den sehr hilfreichen Post. Endlich mal ist an einer Stelle von A bis Z beschrieben, wie man einem Custom Logger sauber implementiert.
      Nur leider fehlt der Code für die Helferklasse „LoggingArea“. Es ist stattdessen zweimal der Code für die Methode „RegisterLoggingArea“ im Post vorhanden.

    Schreibe einen Kommentar