Aus der Praxis – SecurityException beim loggen mit EventLogAppender von log4net
avatar

Beim Einbau von log4net in einen Windows Dienst bin ich auf das folgende Problem gestoßen: Was, wenn keine Konfigurationsdatei für log4net besteht. Bei einer SharePoint Entwicklung würde es sich anbieten in diesem Fall ins SharePoint Log zu schreiben (sowohl die Log Nachrichten, als auch die Warnung, dass keine Konfiguration vorhanden ist). Bei einem Windows Dienst fiel mir das Windows Event Log ein.

Also einfach, wenn die Datei nicht gefunden wird, den BasicConfigurator von log4net nutzen und einen neuen EventLogAppender übergeben, richtig?

Falsch! Leider bekam ich über diesen Weg gar keine Log Ausgaben, auch keine Fehlermeldung, einfach nichts.Könnte ich bitte eine Exception haben?

Also habe ich das Ganze als Konsolenprogramm nachgebaut und ein wenig getestet.

private static readonly log4net.ILog log = log4net.LogManager.GetLogger(
    System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
static void Main(string[]args) {
    var appender = new log4net.Appender.EventLogAppender();
    appender.Layout = new log4net.Layout.PatternLayout(
        "%d [Thread: %t]%n%n[%-5p Message] %m%n%n[%-p] %l%n");
    appender.SecurityContext = log4net.Util.NullSecurityContext.Instance;
    appender.ApplicationName = "Log4NetDemo.exe";
    appender.LogName="Application";
    log4net.Config.BasicConfigurator.Configure(appender);
    log.Error("Test Error");
    log.Warn("Test Warn");
    log.Info("Test Info");
    log.Debug("Test Debug");
}

Im Konsolenprogramm bekam ich dann immerhin eine Exception und zwar direkt in die Konsole. Warum? Weil Appender in log4net einen ErrorHandler haben, der für im Appender auftretende Exceptions aufgerufen wird. Der Standard Handler ist hier der OnlyOnceErrorHandler, der die erste Exception per Console.Error.WriteLine und Trace.WriteLine ausgibt und alle weiteren ignoriert.

Und was ist nun schief gelaufen?

Es handelt sich um eine SecurityException mit folgendem Text: “The source was not found, but some or all event logs could not be searched. Inaccessible logs: Security” (deutsche Meldung: “Die Quelle wurde nicht gefunden, aber einige oder alle Ereignisprotokolle konnten nicht durchsucht werden. Protokolle, auf die kein Zugriff möglich war: Security.”)

Die folgenden beiden Artikel kommen dabei zu dem Konsens, dass sich unter Windows Vista, Windows XP SP 2 und Windows Server 2003 die Berechtigungen geändert haben. Für den Zugriff auf das Security Log sind nun Administratorenrechte nötig. Zum Anlegen einer neuen Event Source müssen aber alle Logs geprüft werden, ob diese Source dort schon existiert. (Anmerkung: das gleiche gilt mittlerweile natürlich auch für Windows 7 und Windows Server 2008)

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=293617

http://social.msdn.microsoft.com/Forums/en-US/windowsgeneraldevelopmentissues/thread/5bef59bc-a28f-4e6d-8ddb-730e12764162

Was ist also passiert? Es handelte sich um den ersten Zugriff dieser Anwendung auf das Event Log. Daher würde EventLog.WriteEntry die Source “Log4NetDemo.exe” für das Event Log “Application” anlegen. Das geht jedoch nicht ohne administrative Rechte.

Best Practice ist hier, die Source beim Setup anzulegen, da das Setup vom Administrator ausgeführt wird und somit ausreichend Berechtigung besitzt. Hierfür kann man in den ProjectInstaller des Windows Dienstes eine Instanz der Klasse EventLogInstaller hinzufügen und konfigurieren.Wie kann ich die Fehlersuche für die Zukunft vereinfachen?

Ein noch offenes Problem ist aber das folgende: Was, wenn aus irgend einem Grund keine Berechtigung existiert und log4net nicht ins Event Log schreiben kann. Die Exception bekommen wir bei einem Windows Dienst nicht zu Gesicht, da sie vom OnlyOnceErrorHandler abgefangen wird. Dafür habe ich mir den folgenden ErrorHandler gebaut:

class ThrowExceptionErrorHandler : log4net.Core.IErrorHandler
{
    public void Error (string message)
    {
        throw new log4net.Core.LogException(message); 
    }
    public void Error (string message, Exception e)
    {
        throw new log4net.Core.LogException(message, e);
    }
    public void Error (string message, Exception e, log4net.Core.ErrorCode errorCode)
    {
        throw new log4net.Core.LogException(message, e);
    }
}

Diesen weise ich vor dem BasicConfigurator.Configure zu. Wenn also eine Exception auftritt wird sie geworfen, der Service stürzt ab (voraussichtlich im Startvorgang) und der Service Control Manager schreibt die Exception ins Event Log (ja, der darf das). Diese Exception würde bei der ersten geloggten Nachricht auftreten, danach kann wieder der Standard Handler eingehängt werden.

appender.ErrorHandler = new ThrowExceptionErrorHandler();
// ...
appender.ErrorHandler = new log4net.Util.OnlyOnceErrorHandler(appender.GetType().Name);

Schreibe einen Kommentar