Tips for ULS Logging

UPDATE 2-4-2011: I recommend taking a look at the updated example for this at https://blogs.technet.com/b/speschka/archive/2011/02/04/tips-for-uls-logging-part-2.aspx . The new example is better and more functional.

When I added some ULS logging to a recent project I noticed one annoying little side effect. In the ULS logs the Area was showing up as “Unknown”. I realize that some aspects of this have been covered other places but I just wanted to pull together a quick post that describes what I found to be the most expedient way to deal with it (certainly much less painful than some of the other solutions I’ve seen described). One thing worth pointing out is that if you don’t want to do this at all, I believe the Logging Framework that the best practices team puts up on CodePlex may do this itself. But I usually write my own code as much as possible so I’ll briefly describe the solution here.

The first thing to note is that most of the SDK documentation operates on the notion of creating a new SPDiagnosticsCategory. The constructor for a new instance of the class allows you to provide the Category name, and when you do that you will definitely see any custom Category name you want to use show up in the ULS log in the Category column. In most cases if you’re doing your own custom logging you also want to have a custom Area to go hand in hand with your custom Category(s). Unfortunately, the SDK puts you through a considerably more complicated exercise to make that happen, because you cannot use a simple constructor to create a new Area and use it – you have to write your own class that derives from SPDiagnosticsServiceBase.

The way I’ve chosen to implement the whole thing is to create one CS file that contains both my logging class and my diagnostic service base class. I’ll start first with the diagnostic base class – below I’ve pasted in the entire class, and then I’ll walk through the things that are worth noting:

    public class SteveDiagnosticService : SPDiagnosticsServiceBase

    {

 

        private const string LOG_AREA = "Steve Area";

 

 

        public enum LogCategories

        {

            SteveCategory

        }

 

 

 

        public SteveDiagnosticService()

            : base("Steve Diagnostics Service", SPFarm.Local)

        {

        }

 

 

 

        public SteveDiagnosticService(string name, SPFarm parent)

            : base(name, parent)

        {

        }

 

 

 

        protected override bool HasAdditionalUpdateAccess()

        {

            return true;

        }

 

 

        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()

        {

 

            List<SPDiagnosticsCategory> categories = new List<SPDiagnosticsCategory>();

 

            categories.Add(new SPDiagnosticsCategory(

  LogCategories.SteveCategory.ToString(),

             TraceSeverity.Medium, EventSeverity.Information));

 

            SPDiagnosticsArea area = new SPDiagnosticsArea(

             LOG_AREA, 0, 0, false, categories);

 

            List<SPDiagnosticsArea> areas = new List<SPDiagnosticsArea>();

 

            areas.Add(area);

 

            return areas;

        }

    }

 

Let’s look at the interesting parts now:

private const string LOG_AREA = "Steve Area";

Here’s where I define what the name of the Area is that I’m going to write to the ULS log.

public enum LogCategories

{

SteveCategory

}  

This is the list of Categories that I’m going to add to my custom Area. In this case I only have one category I’m going to use with this Area, but if you wanted to several of them you would just expand the contents of this enum.

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);

}

}

 

This is one of the two important methods we implement – it’s where we actually write to the ULS log. The first line is where I get the SPDiagnosticCategory, and I make reference to the Area it belongs to when I do that. In the second line I’m just calling the base class method on the local SPDiagnosticsServiceBase class to write to the ULS log, and as part of that I pass in my Category which is associated with my Area.

protected override IEnumerable<SPDiagnosticsArea> ProvideAreas()

{

 

List<SPDiagnosticsCategory> theCategories = new List<SPDiagnosticsCategory>();

 

theCategories.Add(new SPDiagnosticsCategory(

  LogCategories.SteveCategory.ToString(),

             TraceSeverity.Medium, EventSeverity.Information));

 

SPDiagnosticsArea theArea = new SPDiagnosticsArea(

             LOG_AREA, 0, 0, false, theCategories);

 

List<SPDiagnosticsArea> theArea = new List<SPDiagnosticsArea>();

 

theArea.Add(area);

 

return theArea;

}

 

In this override I return to SharePoint all of my custom Areas. In this case I’m only going to use my one Area, so that’s all I send back. Also, as I mentioned above, I’m only using one custom Category with my Area. If I wanted to use multiple custom Categories then I would 1) add them to the enum I described earlier and 2) add each one to my theCategories List instance I’ve defined in this method.

That’s really the main magic behind adding a custom Area and getting it to show up in the appropriate column in the ULS logs. The logging class implementation is pretty straightforward too, I’ll paste in the main part of it here and then explain a little further:

    public class Log

    {

 

        private const int LOG_ID = 11100;

 

 

    public static void WriteLog(string Message, TraceSeverity TraceLogSeverity)

        {

            try

            {

           //in this simple example, I’m always using the same category

              //you could of course pass that in as a method parameter too

              //and use it when you call SteveDiagnosticService

                SteveDiagnosticService.Local.LogMessage(LOG_ID,

                    SteveDiagnosticService.LogCategories.SteveCategory,

                    TraceLogSeverity, Message, null);

            }

            catch (Exception writeEx)

            {

                //ignore

                Debug.WriteLine(writeEx.Message);

            }

        }

    }

 

This code is then pretty easy to implement from my methods that reference it. Since WriteLog is a static method my code is just Log.WriteLog(“This is my error message”, TraceSeverity.Medium); for example. In this example then, in the ULS log it creates an entry in the Area “Steve Area” and the Category “SteveCategory” with the message “This is my error message”.

Tips for ULS Logging.docx