Building Semantic Logging Library as Out of Process Service

What is Semantic Logging Application Block or SLAB

The Semantic Logging Application Block is a framework for capturing and manipulating events raised by applications, and storing the typed and structured information they contain in log files or other logging stores. It takes advantage of features of the .NET Framework (version 4.5 and above) and Event Tracing for Windows (ETW). Here's an extract from MSDN:

ETW is a fast, lightweight, strongly typed, extensible logging system that is built into the Windows operating system.

The Semantic Logging Application Block consumes events raised by event sources, and provides features to help you sample, filter, correlate, format, and store these events in a wide range of target logging stores. The block can be used both in-process within your applications, or out-of-process to capture events generated by one or more processes.

How does the SLAB work

The process by which events messages are passed to the event sinks depends on whether you are using the block just to collect events within a single application, or to capture and log events from more than one application (including applications running in different locations on the network). The Semantic Logging Application Block can be used in two ways:

  • In-process: In this scenario, you just need to collect events from within a single application. The event sinks run in the same process as the application and subscribe to events exposed by a trace listener that is part of the application block.

  • Out-of-process: In this scenario, you want to maximize logging throughput and improve the reliability of logging should the application fail. Your custom event source running within the application writes events to the ETW infrastructure. However, your event sinks run within a separate logging application and subscribe to the events exposed by a trace event service, which is notified of events by ETW.

Read more information of SLAB introduction and its design.

What we will learn

  1. Creating class library and adding SLAB packages to it.
  2. Creating “EveryLogSource” custom EventSource class.
  3. Adding Event Sinks for database.
  4. Setting SLAB Out of Process logging.
  5. Consuming SLAB library in Console application to check logging.

Creating class library and adding slab packages to it

Create C# class library under Infrastructure Solution Folder with name “EveryLogSource”.

Using Nuget Package manager, install the following package: “Semantic Logging Application Block”. This will install, add reference to “EnterpriseLibrary.SemanticLogging” DLL and installs NewtonSoft.Json. Wondering why it installs JSON package because we can convert logging information to JSON format.

Semantic Logging Application Block

Creating “EVERYLOGSOURCE” custom event source class

  1. Create a folder “Events”, in that create class “EveryLoggerSource” which inherits EventSource class of System.Diagnotics.Tracing. Give an attribute name as “AdvWrksLogs” as shown below:
    1. using System.Diagnostics.Tracing;  
    2. namespace EveryLogSource.Events  
    3. {  
    4.     [EventSource(Name = "AdvWrksLogs")]  
    5.     public class EveryLoggerSource: EventSource  
    6.     {}  
    7. }  
  2. Create another folder “LogTypes”, inside it create class “LogType”. Add the following code in it. We can add more classes like Keywords, Tasks, OpCodes if we are creating more strongly typed logging framework.
    1. using System.Diagnostics.Tracing;  
    2. namespace EveryLogSource.LogTypes  
    3. {  
    4.     public class GlobalType  
    5.     {  
    6.         public const int GlobalInformational = 1;  
    7.         public const int GlobalCritical = 2;  
    8.         public const int GlobalError = 3;  
    9.         public const int GlobalLogAlways = 4;  
    10.         public const int GlobalVerbose = 5;  
    11.         public const int GlobalWarning = 6;  
    12.     }  
    13. }  
  3. Copy the following code in “EventLoggerSource” class. Its work is to write the logging to ETW either as Critical, Informational, Error, LogAlways, Verbose and Warning.
    1. using EveryLogSource.LogTypes;  
    2. using System.Diagnostics.Tracing;  
    3. namespace EveryLogSource.Events  
    4. {  
    5.     [EventSource(Name = "AdvWrksLogs")]  
    6.     public class EveryLoggerSource: EventSource  
    7.     {  
    8.         public static readonlyEveryLoggerSource Log = new EveryLoggerSource();  
    9.         [Event(GlobalType.GlobalCritical, Message = "Global Critical: {0}", Level = EventLevel.Critical)]  
    10.         public void Critical(string message)  
    11.         {  
    12.             if (IsEnabled()) WriteEvent(GlobalType.GlobalCritical, message);  
    13.         }  
    14.         [Event(GlobalType.GlobalError, Message = "Global Error {0}", Level = EventLevel.Error)]  
    15.         public void Error(string message)  
    16.         {  
    17.             if (IsEnabled()) WriteEvent(GlobalType.GlobalError, message);  
    18.         }  
    19.         [Event(GlobalType.GlobalInformational, Message = "Global Informational {0}", Level = EventLevel.Informational)]  
    20.         public void Informational(string message)  
    21.         {  
    22.             if (IsEnabled()) WriteEvent(GlobalType.GlobalInformational, message);  
    23.         }  
    24.         [Event(GlobalType.GlobalLogAlways, Message = "Global LogAlways {0}", Level = EventLevel.LogAlways)]  
    25.         public void LogAlways(string message)  
    26.         {  
    27.             if (IsEnabled()) WriteEvent(GlobalType.GlobalLogAlways, message);  
    28.         }  
    29.         [Event(GlobalType.GlobalVerbose, Message = "Global Verbose {0}", Level = EventLevel.Verbose)]  
    30.         public void Verbose(string message)  
    31.         {  
    32.             if (IsEnabled()) WriteEvent(GlobalType.GlobalVerbose, message);  
    33.         }  
    34.         [Event(GlobalType.GlobalWarning, Message = "Global Warning {0}", Level = EventLevel.Warning)]  
    35.         public void Warning(string message)  
    36.         {  
    37.             if (IsEnabled()) WriteEvent(GlobalType.GlobalWarning, message);  
    38.         }  
    39.     }  
    40. }  

Since the Event source class must be Singleton in application, it’s required to create wrapper kind of class whenever we create EventSource class.

Let’s create C# class file in folder “Events” and name it “EveryLoggerSourceWrapper". Copy the following code in this class.

  1. using EveryLogSource.LogTypes;  
  2. using System;  
  3. using System.Collections.Generic;  
  4. namespace EveryLogSource.Events  
  5. {  
  6.     public class EveryLoggerSourceWrapper  
  7.     {  
  8.         public void RegisterLogger(Dictionary < int, Action < string >> exectueLogDict)  
  9.         {  
  10.             exectueLogDict.Add(GlobalType.GlobalCritical, Critical);  
  11.             exectueLogDict.Add(GlobalType.GlobalError, Error);  
  12.             exectueLogDict.Add(GlobalType.GlobalInformational, Informational);  
  13.             exectueLogDict.Add(GlobalType.GlobalLogAlways, LogAlways);  
  14.             exectueLogDict.Add(GlobalType.GlobalVerbose, Verbose);  
  15.             exectueLogDict.Add(GlobalType.GlobalWarning, Warning);  
  16.         }  
  17.         public void Critical(string message)  
  18.         {  
  19.             EveryLoggerSource.Log.Critical(message);  
  20.         }  
  21.         public void Error(string message)  
  22.         {  
  23.             EveryLoggerSource.Log.Error(message);  
  24.         }  
  25.         public void Informational(string message)  
  26.         {  
  27.             EveryLoggerSource.Log.Informational(message);  
  28.         }  
  29.         public void LogAlways(string message)  
  30.         {  
  31.             EveryLoggerSource.Log.LogAlways(message);  
  32.         }  
  33.         public void Verbose(string message)  
  34.         {  
  35.             EveryLoggerSource.Log.Verbose(message);  
  36.         }  
  37.         public void Warning(string message)  
  38.         {  
  39.             EveryLoggerSource.Log.Warning(message);  
  40.         }  
  41.     }  
  42. }  
With thoughts of using IoC containers or just plain Dependency Injections, lets create interface “IEveryLogger” with one simple Log method which takes EventId (integer) and logging message.
  1. namespace EveryLogSource  
  2. {  
  3.     public interface IEveryLogger  
  4.     {  
  5.         void Log(int log, stringLogMessages);  
  6.     }  
  7. }  
Now we have Event Source class, its wrapper classes, Event Types and interface. Let’s implement this interface in logger class implementations so that we have used SLAB logging code we developed here.

This implementation is a simple logging strategy, if we need more strongly typed then we create Event Source, Wrap and use this interface so that any client will consume based on its requirement.

Create C# class “EveryLogger.cs”, implement “IEveryLogger” interface and copy this code:
  1. using EveryLogSource.Events;  
  2. using EveryLogSource.LogTypes;  
  3. using System;  
  4. using System.Collections.Generic;  
  5. namespace EveryLogSource  
  6. {  
  7.     public class EveryLogger: IEveryLogger  
  8.     {  
  9.         private readonlyDictionary < int, Action < string >> _LogDict = newDictionary < int, Action < string >> ();  
  10.         public EveryLogger()  
  11.         {  
  12.             varlogSourceWrapper = newEveryLoggerSourceWrapper();  
  13.             logSourceWrapper.RegisterLogger(_LogDict);  
  14.         }  
  15.         public void Log(int log, stringLogMessages)  
  16.         {  
  17.             if (_LogDict.ContainsKey(log))  
  18.             {  
  19.                 _LogDict[log].Invoke(LogMessages);  
  20.                 return;  
  21.             }  
  22.             _LogDict[GlobalType.GlobalWarning].Invoke(LogMessages);  
  23.         }  
  24.     }  
  25. }  
Adding event sinks for database

The Semantic Logging Application Block includes sinks that enable you to write log messages to the Console, and save log messages to a database, a Microsoft Azure storage table, and to flat files.

The following event sinks are available as part of the Semantic Logging Application Block: 
  • Console event sink: This event sink writes formatted log entries to the console.

  • Flat File event sink: This event sink writes log entries to a text file.

  • Rolling Flat File event sink: This event sink writes log entries to a text file and creates a new log file depending on the current log file age and/or size.

  • SQL Database event sink: Azure Table Storage event sink. This event sink writes log entries to Azure table storage. By default, the sink buffers the log messages.

  • SQL Database Event Sink: This event sink writes log entries to SQL Server or Azure SQL database. By default, the sink buffers the log messages.

Using the “Manage NuGet packages”, install the “Semantic Logging – Sql Server Database Sink”.

Let’s examine what gets installed after this:

  • SemanticLogging.Database – Persisting the logging into SQL Server database.
  • TransientFaultHandling – Retry logic mechanisms for more resilient logging.

In the folder “EnterpriseLibrary.SemanticLogging.Database.2.0.1406.1”, we found SQL scripts to create a database for persisting all the logging information. It’s highly recommended to run these scripts in SQL server either manually or through batch file provided (please check connection details before running).

Table

Setting SLAB out of process logging

Till now we have created class library for generic logging using SLAB, install database to store all the logging information. It’s time to create OUT OF PROCESS setting for semantic logging.

  • Out of process enables us to keep the logging infrastructure out application being used.
  • Multiple applications can log information just by adding Event Source without restarting anything.
  • It can be long running either as console or Windows Services. We will run that as windows services.
  • We can use multiple sinks for same applications i.e. logging information can be stored in files as well as database.

Install OUT OF PROCESS for SLAB using the Nuget packages. After installation if you just observe packages folder (this folder contains all Nuget packages). We can see folder “EnterpriseLibrary.SemanticLogging.Service.2.0.1406.1”, go to Tools folder and open SemanticLogging-svc.xml.

SemanticLogging-svc.xml – Entries for sinks details, filters and formatters.

As we want to log information to store in database we need to add database sink details. Check this for reference:

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2.     <configuration xmlns="http://schemas.microsoft.com/practices/2013/entlib/semanticlogging/etw" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.microsoft.com/practices/2013/entlib/semanticlogging/etw SemanticLogging-svc.xsd">  
  3.         <!-- Optional settings for fine tuning performance and Trace Event Session identification-->  
  4.         <traceEventService/>  
  5.         <!-- Sinks reference definitons used by this host to listen ETW events -->  
  6.         <sinks>  
  7.             <sqlDatabaseSink name="logDb" instanceName="AdvWrksLogging" connectionString="Data Source=.;Initial Catalog=Logging;Integrated Security=True">  
  8.                 <sources>  
  9.                     <!-- The name attribute is from the EventSource.Name Property -->  
  10.                     <eventSource name="AdvWrksLogs" level="LogAlways" /> </sources>  
  11.             </sqlDatabaseSink>  
  12.         </sinks>  
  13.     </configuration>  
SLAB needs more additional files for working as OUT OF PROCESS, to accomplish this it has provided “install-packages.ps1”, a power shell script for installing all dependencies for it to run properly.

Just open Power Shell command prompt, run the “install-packages.ps1” in command line. This will install all dependencies for us to run SLAB as Out of Process. Refer below image.

Sometimes script execution is blocked, run the command (red line indicated) to override restriction. Ensure you revert back the settings. Refer using the Set-ExecutionPolicy.

script execution

Once everything is installed using powershell script, we are ready to host SLAB as an OUT OF PROCESS. We can run it in CONSOLE or as WINDOWS SERVICE.

We will run it as console window here, running it as windows service is fairly simple. 
  • As Console: SemanticLogging-svc.exe –console.
  • As Windows Service: SemanticLogging-svc.exe –service.

We will see similar console display if we succeed in it.

console display

Consuming SLAB library in console application to check logging

Now that we have SLAB out of process running which consumes “AdvWrksLogs” Event source, it’s time to consume that in console application, we can do this in any application like ASP.NET MVC, WPF,  etc.

Just copy these files after you create dummy console application to test. Make sure you reference “EveryLog” class library and Semantic logging DLL.

  1. using System;  
  2. using EveryLogSource;  
  3. using EveryLogSource.LogTypes;  
  4. namespace AdWrksConsoleTest  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             IEveryLogger _logger = newEveryLogger();  
  11.             _logger.Log(GlobalType.GlobalInformational, "Hello World");  
  12.             _logger.Log(GlobalType.GlobalError, "This is an ERROR !!");  
  13.             _logger.Log(GlobalType.GlobalWarning, "Hello World, This a WARNING");  
  14.             Console.ReadLine();  
  15.         }  
  16.     }  
  17. }  
It’s time to check in Logging database, if we have got logging information. Open “Logging” database, query traces table. We would get these rows.

Result

Note from MSDN

In the out-of-process scenario, both the application that generates log messages and the Out-of-Process Host application that collects the messages must run on the same physical computer. In a distributed application, you must install the Out-of-Process Host application on every computer. To collate log messages in this scenario, each instances of the Out-of-Process host application can write them to a suitable single destination such as a database, Azure table storage, or—if you create or obtain suitable custom extensions for the block—to other stores such as ElasticSearch and Splunk.