Friday, July 6, 2012

Several ways to implement custom ULS logging service in Sharepoint

In Sharepoint 2010 you may implement custom logging service – inheritor of SPDiagnosticsServiceBase which will allow you to specify user friendly product name in ULS logs and add another custom behaviors. You may also use standard SPDiagnosticsService, but it will show you product name = Unknown which is not very useful. If you will search for examples, 2 popular approaches are the following:

1. singleton with static class property which creates instance of the logging service using private constructor. Example is shown in this post of Waldek Mastykarz:

   1: public class LoggingService : SPDiagnosticsServiceBase
   2: {
   3:     private LoggingService()
   4:         : base("My Logging Service", SPFarm.Local)
   5:     {
   6:     }
   7:  
   8:     private static LoggingService current;
   9:     public static LoggingService Current
  10:     {
  11:         get
  12:         {
  13:             if (current == null)
  14:             {
  15:                 current = new LoggingService();
  16:             }
  17:             return current;
  18:         }
  19:     }
  20:  
  21:     ...
  22: }

2. also uses static class property, but retrieves logging service instance from farm configuration database as shown in this example in msdn:

   1: public class LoggingService : SPDiagnosticsServiceBase
   2: {
   3:     public LoggingService()
   4:     { 
   5:     }
   6:  
   7:     public LoggingService(string name, SPFarm farm)
   8:         :base(name, farm)
   9:     {
  10:     }
  11:  
  12:     public static LoggingService Local
  13:     {
  14:         get
  15:         {
  16:             return GetLocal<LoggingService>();
  17:         }
  18:     }
  19: }

Both approaches work. Depending on your needs and priorities you may select 1st or 2nd way.

SPDiagnosticsService inherits SPPersistedObject so it is stored in serialized form in configuration database and Sharepoint deserializes it when we call GetLocal<T>() method using reflection. In order to do this it requires from logging service class 2 public constructors: parameterless and with 2 parameters (string, SPFarm) – otherwise you will get Constructor not found exception in runtime. 1st method uses constructor for creating the object, so advantage is that it is faster. Why do we need 2nd way then?

2nd way is needed if you want to make your custom logging service configurable via Central administration > Monitoring > Configure diagnostic logging. I.e. if you want to specify different trace and event severities for different categories via UI as you can do for standard categories, you should use 2nd way.

With 1st way even if you will register logging service in farm config database (by calling service.Update() method), changes in CA won’t affect your application because you will get instance via constructor each time. This instance will contain logging categories returned from its ProvideAreas method with default trace and event severities. But in CA when you select product name or particular category under it you may change these severities and it won’t affect instance created by constructor. Actually it won’t immediately affect your application even if you will try to optimize 2nd approach and will make it like this:

   1: public class LoggingService : SPDiagnosticsServiceBase
   2: {
   3:     public LoggingService()
   4:     { 
   5:     }
   6:  
   7:     public LoggingService(string name, SPFarm farm)
   8:         :base(name, farm)
   9:     {
  10:     }
  11:  
  12:     private static LoggingService current;
  13:     public static LoggingService Local
  14:     {
  15:         get
  16:         {
  17:             if (current == null)
  18:             {
  19:                 current = GetLocal<LoggingService>();
  20:             }
  21:             return current;
  22:         }
  23:     }
  24: }

In this case changes from CA will be applied only after next app pool recycling when LoggingService.current will be re-initialized.

With 2nd approach you will always get last copy of LoggingService with changes from CA. Note that if you won’t register service in config database and will try to use it, then most probably when call LoggingService.Current you will get Access denied error. It happens because internally it will try to register itself by calling SPPersistedObject.Update method and will fail while called in context of not-CA web application. This is known problem. As workaround you may call it in context of CA web app (e.g. in application layouts page). But recommended approach is to register it in feature receiver:

   1: var service = LoggingService.Current;
   2: if (service != null)
   3: {
   4:     service.Update();
   5: }

By the same way you may delete it from config database on feature deactivating event:

   1: var service = LoggingService.Current;
   2: if (service != null)
   3: {
   4:     service.Delete();
   5: }

This is all what I wanted to tell in this article. Hope it will help you when you will make choice of how to implement logging service.

No comments:

Post a Comment