Slab Logging or Semantic Logging is part of the Microsoft Enterprise Library 6. In this post, a wrapper framework will be used to log and use EventSource classes. The EventSource can then be used with an IoC container without requiring static constructors. We don’t want to create a wrapper class for each EventSource. Static methods will be avoided. The logging assembly is not coupling to the other assemblies and can be replaced easily if required. Automatic testing is also less complicated.
Code: https://github.com/damienbod/SlabLoggingWithIoCContainer
The application is a simple MVC 5 application. Unity bootstrapper is added for the dependency injection.
We also add the Semantic Logging.
Now we can create a new class library which will be used to define our log EventSources. We want a simple interface. As we need to define a unique EventId for each Event, will will also use this to set the log. Then we pass the message.
namespace Damienbod.Slab { public interface ISlabLogger { void Log(int log, string message); } }
A wrapper class is required for each event source because the EventSource classes must be a singleton within an application.
using System; using System.Collections.Generic; using Damienbod.Slab.Events; namespace Damienbod.Slab.Loggers { public class GlobalLogger { public void RegisterLogger(Dictionary<int, Action<string>> exectueLogDict) { exectueLogDict.Add(GlobalType.GlobalCritical, Critical); exectueLogDict.Add(GlobalType.GlobalError, Error); exectueLogDict.Add(GlobalType.GlobalInformational, Informational); exectueLogDict.Add(GlobalType.GlobalLogAlways, LogAlways); exectueLogDict.Add(GlobalType.GlobalVerbose, Verbose); exectueLogDict.Add(GlobalType.GlobalWarning, Warning); } public void Critical(string message) { GlobalEvents.Log.Critical(message); } public void Error(string message) { GlobalEvents.Log.Error(message); } public void Informational(string message) { GlobalEvents.Log.Informational(message); } public void LogAlways(string message) { GlobalEvents.Log.LogAlways(message); } public void Verbose(string message) { GlobalEvents.Log.Verbose(message); } public void Warning(string message) { GlobalEvents.Log.Warning(message); } } }
This class uses application unique EventIds and has a special method RegisterLogger. This method adds Actions to a Dictionary using the EventIds as the key.
public void RegisterLogger(Dictionary<int, Action<string>> exectueLogDict) { exectueLogDict.Add(GlobalType.GlobalCritical, Critical); exectueLogDict.Add(GlobalType.GlobalError, Error); exectueLogDict.Add(GlobalType.GlobalInformational, Informational); exectueLogDict.Add(GlobalType.GlobalLogAlways, LogAlways); exectueLogDict.Add(GlobalType.GlobalVerbose, Verbose); exectueLogDict.Add(GlobalType.GlobalWarning, Warning); }
So lets create a EventSource class:
using System.Diagnostics.Tracing; namespace Damienbod.Slab.Events { [EventSource(Name = "GlobalEvents")] public class GlobalEvents : EventSource { public static readonly GlobalEvents Log = new GlobalEvents(); private const int logInfoMessageEventId = 9; [Event(logInfoMessageEventId, Message = "{0}", Level = EventLevel.Informational)] public void LogInfoMessage(string message) { if (IsEnabled()) WriteEvent(logInfoMessageEventId, message); } [Event(GlobalType.GlobalCritical, Message = "Global Critical: {0}", Level = EventLevel.Critical)] public void Critical(string message) { GlobalEvents.Log.LogInfoMessage("dddd"); if (IsEnabled()) WriteEvent(GlobalType.GlobalCritical, message); } [Event(GlobalType.GlobalError, Message = "Global Error {0}", Level = EventLevel.Error)] public void Error(string message) { if (IsEnabled()) WriteEvent(GlobalType.GlobalError, message); } [Event(GlobalType.GlobalInformational, Message = "Global Informational {0}", Level = EventLevel.Informational)] public void Informational(string message) { if (IsEnabled()) WriteEvent(GlobalType.GlobalInformational, message); } [Event(GlobalType.GlobalLogAlways, Message = "Global LogAlways {0}", Level = EventLevel.LogAlways)] public void LogAlways(string message) { if (IsEnabled()) WriteEvent(GlobalType.GlobalLogAlways, message); } [Event(GlobalType.GlobalVerbose, Message = "Global Verbose {0}", Level = EventLevel.Verbose)] public void Verbose(string message) { if (IsEnabled()) WriteEvent(GlobalType.GlobalVerbose, message); } [Event(GlobalType.GlobalWarning, Message = "Global Warning {0}", Level = EventLevel.Warning)] public void Warning(string message) { if (IsEnabled()) WriteEvent(GlobalType.GlobalWarning, message); } } }
This is then used in the implementation of the Logger interface. We can define n-Logger implementations or if needed n-Logger interfaces with different implementations. These classes can then register any EventSources required in the RegisterLog method.
using System; using System.Collections.Generic; using Damienbod.Slab.Loggers; using Microsoft.Build.Framework; namespace Damienbod.Slab { public class WebLogger : ISlabLogger { public WebLogger() { RegisterLog(); } private readonly Dictionary<int, Action<string>> _exectueLogDict = new Dictionary<int, Action<string>>(); private void RegisterLog() { var globalLogger = new GlobalLogger(); globalLogger.RegisterLogger(_exectueLogDict); var controllerLogger = new ControllerLogger(); controllerLogger.RegisterLogger(_exectueLogDict); } public void Log(int log, string message) { if (_exectueLogDict.ContainsKey(log)) { _exectueLogDict[log].Invoke(message); return; } _exectueLogDict[GlobalType.GlobalWarning].Invoke(message); } } }
The Log method justs gets the action from the Dictionary and invokes it.
public void Log(int log, string message) { if (_exectueLogDict.ContainsKey(log)) { _exectueLogDict[log].Invoke(message); return; } _exectueLogDict[GlobalType.GlobalWarning].Invoke(message); }
To use the logger, it is registered with unity.
public static void RegisterTypes(IUnityContainer container) { container.RegisterType<ISlabLogger, WebLogger>(); }
And it is used in the HomeController:
private readonly ISlabLogger _logger; public HomeController(ISlabLogger logger) { _logger = logger; } public ActionResult About() { ViewBag.Message = "Your application description page."; _logger.Log(GlobalType.GlobalCritical, "Hello World"); return View(); }
In the Log method the const ints are used and not raw ints.
namespace Damienbod.Slab { // Every const has a unique id. public class GlobalType { public const int GlobalInformational = 1; public const int GlobalCritical = 2; public const int GlobalError = 3; public const int GlobalLogAlways = 4; public const int GlobalVerbose = 5; public const int GlobalWarning = 6; } public class WebType { public const int ControllerInformational = 7; public const int ControllerCritical = 8; public const int ControllerError = 9; public const int ControllerLogAlways = 10; public const int ControllerVerbose = 11; public const int ControllerWarning = 12; } }
This logger is simple to use in any injection container. If a Logger was required in an attribute, property injection could be used.
The application uses SLAB OUT-OF-PROCESS Logging, that’s why only the EventSource classes and the Logs are required.
Links:
Enterprise Library 6, Semantic Logging, Part 1, database listener
Enterprise Library 6, Semantic Logging, Part 2, OUT-OF-PROCESS
Enterprise Library 6, Semantic Logging, Part 3, Getting into the details
Enterprise Library 6, Semantic Logging, Part 4 advantages, customising
http://msdn.microsoft.com/en-us/library/dn170426%28v=pandp.60%29.aspx
… [Trackback]
[…] Read More Infos here: damienbod.wordpress.com/2013/10/25/slab-logging-with-an-ioc-container/ […]