SharePoint Logging and Microsoft’s Trace Log Provider

Microsoft has a posted example of how to log to the Trace Log here.

So how does one use this in their application?  The below code is how I plugged it into our SharePoint development framework.  I’d love to hear how you are using it.

Step 1:

I modified the Microsoft’s TraceProvider’s RegisterTraceProvider and UnregisterTraceProvider methods as follows:

        private static readonly object RegisterLock = new object();
private static bool isRegistered = false;
public static unsafe void RegisterTraceProvider()
{
lock (RegisterLock)
{
if (!isRegistered)
{
SPFarm farm = SPFarm.Local;
Guid traceGuid = farm.TraceSessionGuid;
uint result = NativeMethods.RegisterTraceGuids(ControlCallback, null, ref traceGuid, 0, IntPtr.Zero, null, null, out hTraceReg);
System.Diagnostics.Debug.Assert(result == NativeMethods.ERROR_SUCCESS);
isRegistered = true;
}
}
}
 
public static void UnregisterTraceProvider()
{
lock (RegisterLock)
{
if (isRegistered)
{
uint result = NativeMethods.UnregisterTraceGuids(hTraceReg);
System.Diagnostics.Debug.Assert(result == NativeMethods.ERROR_SUCCESS);
isRegistered = false;
}
}
}

Step 2:

I created our own TraceLogProvider object extending our BaseLogProvider which implements our custom ILogProvider interface.  This provider object makes the appropriate calls into Microsoft’s TraceProvider, but now exposes a friendly logging interface to use within our application.

    public class TraceLogProvider : BaseLogProvider, IDisposable
{
public string ApplicationName { get; set; }
 
public void Log(string msg, LogSeverity severity)
{
var traceSeverity = GetTraceSeverity(severity);
 
TraceProvider.RegisterTraceProvider();
 
string[] lines = msg.Split(new string[] { Environment.NewLine },
StringSplitOptions.RemoveEmptyEntries);
foreach (string line in lines)
{
TraceProvider.WriteTrace(0, traceSeverity, Guid.Empty, "Your Company Name",
ApplicationName, "General", line);
}
}
 
private TraceProvider.TraceSeverity GetTraceSeverity(LogSeverity severity)
{
switch (severity)
{
case LogSeverity.Info:
return TraceProvider.TraceSeverity.InformationEvent;
case LogSeverity.Warn:
return TraceProvider.TraceSeverity.WarningEvent;
default:
return TraceProvider.TraceSeverity.CriticalEvent;
}
}
 
#region IDisposable Members
 
public virtual void Dispose()
{
TraceProvider.UnregisterTraceProvider();
}
 
#endregion
}

And for reference, our BaseLogProvider is shown below:

    public abstract class BaseLogProvider : ILogProvider
{
#region ILogProvider Members
 
public virtual void Info(string msg)
{
Log(msg, LogSeverity.Info);
}
 
public virtual void Info(Exception e)
{
Log(e, LogSeverity.Info);
}
 
public virtual void Info(string msg, Exception e)
{
Log(msg, e, LogSeverity.Info);
}
 
public virtual void Warn(string msg)
{
Log(msg, LogSeverity.Warn);
}
 
public virtual void Warn(Exception e)
{
Log(e, LogSeverity.Warn);
}
 
public virtual void Warn(string msg, Exception e)
{
Log(msg, e, LogSeverity.Warn);
}
 
public virtual void Error(string msg)
{
Log(msg, LogSeverity.Error);
}
 
public virtual void Error(Exception e)
{
Log(e, LogSeverity.Error);
}
 
public virtual void Error(string msg, Exception e)
{
Log(msg, e, LogSeverity.Error);
}
 
public virtual void Log(Exception e, LogSeverity severity)
{
Log(FormatException(e), severity);
}
 
public virtual void Log(string msg, Exception e, LogSeverity severity)
{
Log(msg + "
" + FormatException(e), severity);
}
 
public abstract void Log(string msg, LogSeverity severity);
 
#endregion
 
protected virtual string FormatException(Exception ex)
{
StringBuilder stringBuilder = new StringBuilder();
 
stringBuilder.AppendLine(ex.ToString());
if (ex.InnerException != null)
{
stringBuilder.AppendLine(ex.InnerException.ToString());
if (ex.InnerException.InnerException != null)
{
stringBuilder.AppendLine(ex.InnerException.InnerException.ToString());
}
}
 
return stringBuilder.ToString();
}
}
 
    public enum LogSeverity
    {
        Info,
        Warn,
        Error
    }

SharePoint Security Validation and Unsafe Updates

Seeing this? The security validation for this page is invalid. I am sure this error has good intentions, but it is showing its face far too often. Hristo Pavlov has written a good explanation on AllowUnsafeUpdates here.

If you read my previous post and are attempting to update list items as the System User, you will likely run into this problem.  Setting AllowUnsafeUpdates to true for the duration of the update has allowed me to get around this exception.  Here is the helper method I am using to accomplish this:

        public static void DoUnsafeUpdate(SPWeb web, Action action)
{
bool allowUnsafeUpdates = web.AllowUnsafeUpdates;
web.AllowUnsafeUpdates = true;
action();
web.AllowUnsafeUpdates = allowUnsafeUpdates;
}

Couple this with the helper methods in my previous post, and you now can do both together:

        public static void DoUnsafeUpdateAsSystemUser(SPWeb web, SPWebAction action)
{
DoAsSystemUser(web, delegate(SPWeb systemWeb)
{
DoUnsafeUpdate(systemWeb, delegate()
{
action(systemWeb);
});
});
}

SharePoint Security “Do As System User”

Scenario

You are writing a custom web part or control and want to be able to access a list or resource within your code that the current user otherwise does not have permissions to.  For me, this happens quite frequently.  For example:

  • Reading info from a list dedicated to storing Application Settings that only an administrator can read and edit
  • Modifying permissions on a list item after creating it
  • Modifying a list item other than the specific item in context
  • Updating Web properties
  • Kicking off a workflow from code as a result of a users action (event handlers are also a good place to do this, which already run under the context of the System user)

What Didn’t Work

SPSecurity.RunWithElevatedPriveleges – this only impersonates the user account running the thread in order to access network resources, etc.   But if you are accessing SharePoint resources using the SPContext.Current.Web, your SPWeb object is still limited to the permission set of the original user initiating the request.

What Did Work

Create a new site using the Site.SystemAccount UserToken and then open the web using this Site.  Here are two helper methods I am using for doing just this:

        public delegate void SPWebAction(SPWeb web);
 
public static void DoAsSystemUser(SPWeb web, SPWebAction action)
{
SPUser systemUser = web.Site.SystemAccount;
DoAsUser(web, systemUser, action);
}
 
public static void DoAsUser(SPWeb web, SPUser user, SPWebAction action)
{
// If we are already running as the given User Token, just pass the web along
if (web.CurrentUser != null &&
web.CurrentUser.UserToken.CompareUser(user.UserToken))
{
action(web);
return;
}
 
using (SPSite site = new SPSite(web.Site.ID, user.UserToken))
{
using (SPWeb userWeb = site.OpenWeb(web.ID))
{
action(userWeb);
}
}
}

Two more things that still need to be addressed are Security Validation and Unsafe Updates, which I will talk about in the next post.

Top 5 Most Useful SharePoint Links of the Month

So I just completed and released to production my first approval workflow application using ASP.Net forms and Visual Studio Workflow Designer. I have combed through hundreds of web pages working out many many kinks. These are the top 5 links for this month (measured on subjective scale of usefulness to me for completing my project):

Get Public Key Token Visual Studio Trick

SharePoint Workflow Basics

Design and Bind ASPX Forms to Workflow

Locked Workflow tasks Bug

WSS WebService DISCO and WSDL Generator