SharePoint: Simple Event Logger
Errors, warnings and info messages in all my SharePoint applications are being logged to the Application Event Log. For that I use a simple class tritely named EventLogger and listed later in this post. As for the moment, a couple of words about the EventLogger are stated below.
If necessary, the EventLogger registers a source in the Application Event Log once any its method is called for the first time (see the static constructor). The event logging uses the information stored in the Eventlog registry key. So, when dealing with the Application Event Log, we have to be ready to get exception about a lack of rights to read from or write to the registry. Because of that, the EventLogger initially tries adding a new source under the current user account and then, in case of failure, repeats the same under the application pool account (SPSecurity.RunWithElevatedPrivileges) that is supposed to have all suitable permissions.
Due to the same reason, whenever a user different from the application pool account writes anything to the log, he will likely get an exception which is reporting that the current user doesn’t have write access. To guard users from that, we as administrators have to do some manual work, namely, to add the CustomSD value to the [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\eventlog\Application] registry key how it’s described in the article SharePoint: Cannot open log for source. You may not have write access. If a SharePoint application supports anonymous access, use WD (all users) instead of AU (authenticated users). Also it’s very important to understand that the appropriate CustomSD must be added on all machines of a SharePoint farm. An alternative way is to wrap the writing to the log in SPSecurity.RunWithElevatedPrivileges. Remember, however, that the SPSecurity.RunWithElevatedPrivileges is quite resource-consuming and excessive for such frequent operation as event logging. So, use the SPSecurity.RunWithElevatedPrivileges as an extreme measure and only when the previous approach with CustomSD didn’t help for some reasons.
Another feature of the EventLogger is that, as a backup plan, it writes to the SharePoint Trace Log through the Unified Logging Service (see the WriteToHiveLog method). In other words, if the EventLogger doesn’t manage to write a message to the Application Event Log, it tries appending the message to the ULS Log stored in the file system and accessible, for example, through the ULS Viewer.
Logging an error or warning based on the passed exception, the EventLogger forms the final text, using the exception’s message along with the message of the inner exception (if any) and stack trace.
Below is a combined example that demonstrates how to use the EventLogger to log errors, warnings and info.
using dotNetFollower; ... EventLogger.WriteInfo("How to use the EventLogger"); EventLogger.WriteError("Sorry, couldn't perform this operation!"); // OR EventLogger.WriteWarning("Sorry, couldn't perform this operation!"); try { // the next line throws an exception SPList spList = SPContext.Current.Web.Lists["Not existing list"]; } catch (Exception ex) { EventLogger.WriteError(ex); // OR EventLogger.WriteWarning(ex); }
Below is depicted what those records look like in the Windows Event Viewer:
Ok, it’s about time for the EventLogger listing:
using System; using System.Diagnostics; using Microsoft.SharePoint; using Microsoft.SharePoint.Administration; namespace dotNetFollower { public static class EventLogger { private const string SOURCE = "dotNetFollower"; // put here your own source name /// <summary> /// Writes an error message /// </summary> /// <param name="errorText">Error message</param> public static void WriteError(string errorText) { WriteWithinTryCatch(errorText, EventLogEntryType.Error); } /// <summary> /// Writes an error message /// </summary> /// <param name="ex">Exception</param> public static void WriteError(Exception ex) { WriteWithinTryCatch(GetExceptionFormatted(ex), EventLogEntryType.Error); } /// <summary> /// Writes a warning message /// </summary> /// <param name="text">Warning message</param> public static void WriteWarning(string text) { WriteWithinTryCatch(string.Format("Warning: {0}", text), EventLogEntryType.Warning); } /// <summary> /// Writes a warning message /// </summary> /// <param name="ex">Exception</param> public static void WriteWarning(Exception ex) { WriteWithinTryCatch(GetExceptionFormatted(ex), EventLogEntryType.Warning); } /// <summary> /// Writes an info message /// </summary> /// <param name="text">Info message</param> public static void WriteInfo(string text) { WriteWithinTryCatch(string.Format("Information: {0}", text), EventLogEntryType.Information); } /// <summary> /// Creates the appropriate source in Event Logs, if necessary /// </summary> public static void EnsureLogSourceExist() { if (!EventLog.SourceExists(SOURCE)) EventLog.CreateEventSource(SOURCE, "Application"); } /// <summary> /// Returns an error message based on a passed exception. Includes an inner exception (if any) and stack trace /// </summary> /// <param name="ex">Exception</param> /// <returns>Formed error message</returns> public static string GetExceptionFormatted(Exception ex) { return string.Format("Error: {0} (Inner Exception: {1})\t\nDetails: {2}", ex.Message, ex.InnerException != null ? ex.InnerException.Message : string.Empty, ex.StackTrace); } static EventLogger() { bool error = false; Action action = delegate { try { // register source in Event Logs EnsureLogSourceExist(); } catch { error = true; } }; // try under current user action(); if(error) // try under application pool account SPSecurity.RunWithElevatedPrivileges(() => action()); } private static void WriteWithinTryCatch(string message, EventLogEntryType type) { try { // To allow users (authenticated only or all of them) writing to Event Log, // follow the steps described in the article // http://dotnetfollower.com/wordpress/2012/04/sharepoint-cannot-open-log-for-source-you-may-not-have-write-access/ // If it doesn't help for some reason, uncomment the line with SPSecurity.RunWithElevatedPrivileges and // comment the other one. Note, however, that the use of SPSecurity.RunWithElevatedPrivileges is // resource-consuming and looks excessive for such frequent operation as event logging. //SPSecurity.RunWithElevatedPrivileges(() => EventLog.WriteEntry(SOURCE, message, type)); EventLog.WriteEntry(SOURCE, message, type); } catch { WriteToHiveLog(message, type); } } private static void WriteToHiveLog(string message, EventLogEntryType type) { EventSeverity eventSeverity = type == EventLogEntryType.Error ? EventSeverity.Error : (type == EventLogEntryType.Warning ? EventSeverity.Warning : EventSeverity.Information); var category = new SPDiagnosticsCategory(SOURCE, TraceSeverity.Unexpected, eventSeverity); SPDiagnosticsService.Local.WriteTrace(0, category, TraceSeverity.Unexpected, message, null); } } }