Archive

Archive for the ‘Event Logging’ Category

SharePoint: Simple Event Logger

June 3rd, 2013 No comments

    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:
EventLogger Records

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);
        }
    }
}
Related posts:

SharePoint: Cannot open log for source. You may not have write access.

April 21st, 2012 No comments

    In our SharePoint applications we actively use writing into Application Event Log. After adding a new Windows 2008 Server R2 machine to our SP 2010 farm, we was getting the exception “System.ComponentModel.Win32Exception: Access is denied” with the description “Cannot open log for source {*}. You may not have write access.” Apparently, the given error is caused by writing to log when it’s called under an ordinary user with limited rights, who, however, can view web pages. I tried to provide Authenticated Users group with Full Control to the [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\eventlog] registry key, with no success though.

The workaround is add or modify the magic CustomSD value under the registry key [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\eventlog\Application]. So,

  1. Open Registry Editor (click Start, then Run, then type regedit and click Ok);
  2. Locate the [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\eventlog\Application] key in the registry tree;
  3. If the CustomSD value doesn’t exist, create it (right click on Application key, then click New -> String Value and set CustomSD name). Then set value data to O:BAG:SYD:(A;;0x3;;;AU) (right click on CustomSD, then click Modify, type the O:BAG:SYD:(A;;0x3;;;AU) and click Ok). The result should look as shown on the picture below:
    Create CustomSD Value
  4. If the CustomSD value already exists, append (A;;0x3;;;AU) to the value data (right click on CustomSD, then click Modify, type the (A;;0x3;;;AU) at the end of value data and click Ok). After appending, the resultant value data would be similar to:

    O:BAG:SYD:(D;;0xf0007;;;AN)(D;;0xf0007;;;BG)(A;;0x f0007;;;SY)(A;;0x7;;;BA)(A;;0x7;;;SO)(A;;0x3;;;IU)(A;;0x3;;;SU)(A;;0x3;;;S-1-5-3)(A;;0x3;;;AU)

The CustomSD registry value describes which accounts have the read/write/clear permissions to Application Event Log. The format of the value data corresponds to Security Descriptor Definition Language (SDDL), so (A;;0x3;;;AU) consists of

  • A – SDDL_ACCESS_ALLOWED or ACCESS_ALLOWED_ACE_TYPE, one of ACE types;
  • 0x3 – ELF_LOGFILE_WRITE (0x2) & ELF_LOGFILE_READ (0x1), the access rights to the EventLog;
  • AU – Authenticated Users group;

It looks funny that direct giving permissions for Authenticated Users group haven’t had effect, while the EventLog‘s security is controlled by the CustomSD registry value.

Update: If a web application supports anonymous access, you’d better replace AU in (A;;0x3;;;AU) with WD, where WD is Everyone or a group that includes all users. So, the final version in this case is (A;;0x3;;;WD).

Related posts: