In part 1 of ULS logging tips (http://blogs.technet.com/b/speschka/archive/2011/01/23/tips-for-uls-logging.aspx) I included a some code to add a custom ULS logging Area and demonstrated how to use it within your project.  After working with it a bit I noticed a few things:

1.       There was some code in there that didn’t jive up with the latest SDK – basically some code pieces implemented that the SDK implied I should not need, and some stuff that I was doing slightly differently than the SDK example (which, by the way, needs to be updated and I will work on that separately).

2.       Logging would only occur when I set the TraceSeverity to Medium; there was not an effective way to layer in different trace levels.

3.       The most super annoying part – when I tried to invoke my custom class that is required for my custom Area, it would fail when I tried doing any logging during a POST activity.  It triggered the all too common error about not being able to update an SPWeb during an POST unless setting the AllowUnsafeUpdates property to true.  Now setting that on an SPWeb during a log event, just to make my log event work, seemed borderline insane (my apologies to all the insane people out there by the way).  So I decided there must be a better way.

In this posting then, I’m going to improve upon the previous example and add to it along the way.  Here’s what we’re going to show:

1.       An improved class for the custom Area – it actually slims down some in this version.

2.       Integration with Configure diagnostic logging in the Monitoring section of Central Admin.  This integration will further allow support for configuring tracing levels on the custom Area and Category.

3.       Registration information – a very simple way to both register and unregister the custom ULS logging class so it integrates with Central Admin, and can be removed from Central Admin.

To begin with, let’s look at the new streamlined version of the class.  There are many similarities between this and the version I showed in the first posting, but this is a little slimmer and simpler.  Here’s what it looks like now.

    [Guid("833B687D-0DD1-4F17-BF6A-B64FBC1AC6A8")]

    public class SteveDiagnosticService : SPDiagnosticsServiceBase

    {

 

        private const string LOG_AREA = "Steve Area";

 

 

        public enum LogCategories

        {

            SteveCategory

        }  

 

 

        public SteveDiagnosticService()

        {

        } 

 

 

        public SteveDiagnosticService(string name, SPFarm parent)

            : base(name, parent)

        {

        } 

 

 

        public static SteveDiagnosticService Local

        {

            get

            {

                return SPDiagnosticsServiceBase.GetLocal<SteveDiagnosticService>();

            }

        }  

 

 

 

        public void LogMessage(ushort id, LogCategories LogCategory,

TraceSeverity traceSeverity, string message,

params object[] data)

        {

 

            if (traceSeverity != TraceSeverity.None)

            {

                SPDiagnosticsCategory category

                 = Local.Areas[LOG_AREA].Categories[LogCategory.ToString()];

                Local.WriteTrace(id, category, traceSeverity, message, data);

            }

 

        }

 

 

        protected override IEnumerable<SPDiagnosticsArea> ProvideAreas()

        {

yield return new SPDiagnosticsArea(LOG_AREA, 0, 0, false,

new List<SPDiagnosticsCategory>()

{

new

SPDiagnosticsCategory(LogCategories.AzureConfig.ToString(),

                     TraceSeverity.Medium, EventSeverity.Information, 1,

                     Log.LOG_ID)

                     });

        }

    }

 

Here are the important changes from the first version:

1.        Add the Guid attribute to the class:

[Guid("833B687D-0DD1-4F17-BF6A-B64FBC1AC6A8")]

 

I added a Guid attribute to the class because SharePoint requires it in order to uniquely identify it in the configuration database.

2.       Changed the default constructor:

        public SteveDiagnosticService()

        {

        } 

 

Now it’s just a standard empty constructor.  Before I called the other overload for the constructor that takes a name for the service and an SPFarm.  Just less code is all, which is a good thing when you can get away with it.

3.       Deleted the HasAdditionalUpdateAccess override.  Again, turned out I wasn’t really using it, so continuing with the “less is more” theme I removed it.

 

4.       Shortened the ProvideAreas method up significantly; now it matches the same pattern that is used in the SDK:

 

yield return new SPDiagnosticsArea(LOG_AREA, 0, 0, false,

new List<SPDiagnosticsCategory>()

{

new

SPDiagnosticsCategory(LogCategories.AzureConfig.ToString(),

                     TraceSeverity.Medium, EventSeverity.Information, 1,

                     Log.LOG_ID)

                     });

So that addressed problem #1 above – my code is now a little cleaner and meaner.  The other problems – lack of tracing levels, throwing an exception when logging during a POST, and integration with central admin – were all essentially fixed by taking the code a bit further and registering.  The example in the SDK is currently kind of weak in this area but I was able to get it to do what we need.  To simplify matters, I created a new feature for my assembly that contains my custom ULS class I described above.  I added a feature receiver and it, I register the custom ULS assembly during the FeatureInstalled event, and I unregister it during the FeatureUninstalling event.  This was a good solution in my case, because I made my feature a Farm scoped feature so it auto activates when the solution is added and deployed.  As it turns out, the code to do this register and unregister is almost criminally simple; here it is:

public override void FeatureInstalled(SPFeatureReceiverProperties properties)

{

try

       {

SteveDiagnosticsService.Local.Update();

}

catch (Exception ex)

       {

throw new Exception("Error installing feature: " + ex.Message);

}

}

 

 

public override void FeatureUninstalling(SPFeatureReceiverProperties properties)

{

try

       {

SteveDiagnosticsService.Local.Delete();

}

catch (Exception ex)

{

throw new Exception("Error uninstalling feature: " + ex.Message);

}

}

 

The other thing to point out here is that I was able to refer to “SteveDiagnosticService” because I a) added a reference to the assembly with my custom logging class in my project where I packaged up the feature and b) I added a using statement to my feature receiver class for the assembly with my custom logging class.

By registering my custom ULS logging class I get a bunch of benefits:

·         I no longer get any errors about updating SPWeb problems when I write to the ULS log during a POST

·         My custom logging Area and Category shows up in central admin so I can go into Configure diagnostic logging and change the trace and event level.  For example, when it’s at the default level of Medium tracing, all of my ULS writes to the log that are of a TracingSeverity.Medium are written to the log, but those that are TracingSeverity.Verbose are not.  If I want to have my TracingSeverity.Verbose entries start showing up in the log, I simply go Configure diagnostics logging and change the Trace level in there to Verbose.

Overall the solution is simpler and more functional.  I think this is one of those win-win things I keep hearing about.

P.S.  I want to record my protest vote here for LaMarcus Aldridge of the Portland Trailblazers.  His failure to get added to the Western Conference NBA All Star team is tragically stupid.  Okay, enough personal commentary, I know y’all are looking for truly Share-n-Dipity-icous info when you come here.  Hope this is useful.