Using NLog.net in SOA with ILogReceiverServer
Utilize NLog.net in a Service-oriented architecture
Introduction
We have implemented NLog.net in our service oriented architecture. It can log different levels exception/message to your selected data storage.
Background
There are many articles about how to implement NLog.net but there is not a good article about how to implement ILogReceiverServer
interface inside a SOA system. The tip will show you how to do that.
This demo has 7 parts.
- The external DLL which is NLog.dll, you can get it from Nuget and put in a solution folder to share it between different projects.
-
Data storage, we will focus on using a
SqlServer
database as our log data storage.Here is the script to create the table:
SET ANSI_PADDING ON GO CREATE TABLE [dbo].[EventLogs]( [ID] [bigint] IDENTITY(1,1) NOT NULL, [ActivityID] [varchar](20) NOT NULL, [Source] [varchar](100) NOT NULL, [SubSource] [varchar](150) NOT NULL, [DateTimeStamp] [datetime] NOT NULL, [LogLevel] [varchar](20) NOT NULL, [MachineName] [varchar](50) NOT NULL, [WindowsID] [varchar](50) NOT NULL, [StackTrace] [varchar](max) NOT NULL, [Logger] [varchar](100) NOT NULL, [Exception] [varchar](max) NOT NULL, [LogMessage] [varchar](max) NOT NULL, CONSTRAINT [PK_EventLogs] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO SET ANSI_PADDING OFF
- DataAccess layer
We use the following code to insert logs.
It will take NLog's
LogEventInfo
as parameter and parse out the properties to insert.public void InsertLog(LogEventInfo ev) { try { using (SqlConnection connection = new SqlConnection(DBConn)) { SqlCommand command = new SqlCommand(CommandText, connection); foreach (DBParameter p in DBParameters) { command.Parameters.AddWithValue(p.Name, ev.Properties[p.Layout]); } try { connection.Open(); Int32 rowsAffected = command.ExecuteNonQuery(); Console.WriteLine("RowsAffected: {0}", rowsAffected); } catch (Exception ex) { logger.Error(ex); } } } catch (Exception ex) { logger.Error(ex); } }
We will build the
DBParameters
by reading the NLog.config file.protected List<DBParameter> DBParameters { get; set; } public void ReadNLogConfigure() { try { string myString = ""; using (System.IO.StreamReader myFile = new System.IO.StreamReader(AppDomain.CurrentDomain.BaseDirectory + "NLog.config")) { myString = myFile.ReadToEnd(); } myString = myString.Replace("xmlns=\"http://www.nlog-project.org/schemas/NLog.xsd\"", ""); myString = myString.Replace("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", ""); myString = myString.Replace("xsi:", ""); XmlDocument xDoc = new XmlDocument(); xDoc.LoadXml(myString); //XmlNamespaceManager nsmgr = new XmlNamespaceManager(xDoc.NameTable); //nsmgr.AddNamespace(string.Empty, "http://www.nlog-project.org/schemas/NLog.xsd"); //nsmgr.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance"); XmlNode xn = xDoc.SelectSingleNode("//target[@type=\"Database\"]");//,nsmgr); DBParameters = new List<DBParameter>(); if (xn != null) { DBConn = xn.Attributes["connectionString"].Value; CommandText = xn.Attributes["commandText"].Value; foreach (XmlNode xp in xn.SelectNodes("parameter")) { DBParameter dp = new DBParameter(); dp.Name = xp.Attributes["name"].Value; //dp.Layout = xp.Attributes["layout"].Value.Replace("${", "").Replace("}", ""); dp.Layout = xp.Attributes["name"].Value.Replace("@", ""); DBParameters.Add(dp); } } } catch (Exception ex) { logger.Error(ex); } }
NLogService
WCF service.We have two services.
LogReceiverServer
: This will accept the log request from client.public class LogReceiverServer : ILogReceiverServer { public void ProcessLogMessages(NLogEvents nevents) { var events = nevents.ToEventInfo("Client."); Console.WriteLine("in: {0} {1}", nevents.Events.Length, events.Count); LogDataBaseAccess logToDB = new LogDataBaseAccess(); foreach (var ev in events) { var logger = LogManager.GetLogger(ev.LoggerName); //logger.Log(ev); logToDB.InsertLog(ev); } } }
NLogConfigService
: This will let you config log level from server to client.public class NLogConfigService : INLogConfigService { public string GetLogLevel(string userID) { if (userID == "Grant") return "Error"; else return "Trace"; } }
So we can control which level we want the client to be.
NLogReceiverServiceHost
This is the WCF host for our service by a Windows Form application.
private void F_Main_Load(object sender, EventArgs e) { try { StringBuilder sb = new StringBuilder(); //host the service here ServiceHost host = new ServiceHost(typeof(NLogService.LogReceiverServer)); host.Open(); sb.Append("LogReceiverServer is running"+Environment.NewLine); host = new ServiceHost(typeof(NLogService.NLogConfigService)); host.Open(); sb.Append("NLogConfigService is running" + Environment.NewLine); lbServiceStatus.Text = sb.ToString(); } catch (Exception ex) { MessageBox.Show(ex.Message); } }
NLogDemo
This is the client side Windows Form application.
private void cmdLogAll_Click(object sender, EventArgs e) { logger.Trace("This is a Trace message"); logger.Debug("This is a Debug message"); logger.Info("This is an Info message"); logger.Warn("This is a Warn message"); logger.Error("This is an Error message"); logger.Fatal("This is a Fatal error message"); } private void cmdLogException_Click(object sender, EventArgs e) { try { int i = 10; int j = 0; int k = i / j; } catch (Exception ex) { logger.ErrorException("This is an Error message", ex); } }
- Utility
This module provides a function to let you reconfig client's log level.
public static void Reconfigure(LogLevel logLevel) { foreach (var rule in LogManager.Configuration.LoggingRules) { rule.DisableLoggingForLevel(LogLevel.Trace); rule.DisableLoggingForLevel(LogLevel.Debug); rule.DisableLoggingForLevel(LogLevel.Info); rule.DisableLoggingForLevel(LogLevel.Warn); rule.DisableLoggingForLevel(LogLevel.Error); rule.DisableLoggingForLevel(LogLevel.Fatal); EnableHigherLevel(rule, logLevel); } //Call to update existing Loggers created with GetLogger() or //GetCurrentClassLogger() LogManager.ReconfigExistingLoggers(); } public static void EnableHigherLevel(LoggingRule rule, LogLevel logLevel) { //Trace - Very detailed log messages, potentially of a high frequency and volume //Debug -Less detailed and/or less frequent debugging messages //Info - Informational messages //Warn - Warnings which don't appear to the user of the application //Error - Error messages //Fatal - Fatal error messages. After a fatal error, the application usually terminates. switch (logLevel.ToString()) { case "Trace": rule.EnableLoggingForLevel(LogLevel.Trace); rule.EnableLoggingForLevel(LogLevel.Debug); rule.EnableLoggingForLevel(LogLevel.Info); rule.EnableLoggingForLevel(LogLevel.Warn); rule.EnableLoggingForLevel(LogLevel.Error); rule.EnableLoggingForLevel(LogLevel.Fatal); break; case "Debug": rule.EnableLoggingForLevel(LogLevel.Debug); rule.EnableLoggingForLevel(LogLevel.Info); rule.EnableLoggingForLevel(LogLevel.Warn); rule.EnableLoggingForLevel(LogLevel.Error); rule.EnableLoggingForLevel(LogLevel.Fatal); break; case "Info": rule.EnableLoggingForLevel(LogLevel.Info); rule.EnableLoggingForLevel(LogLevel.Warn); rule.EnableLoggingForLevel(LogLevel.Error); rule.EnableLoggingForLevel(LogLevel.Fatal); break; case "Warn": rule.EnableLoggingForLevel(LogLevel.Warn); rule.EnableLoggingForLevel(LogLevel.Error); rule.EnableLoggingForLevel(LogLevel.Fatal); break; case "Error": rule.EnableLoggingForLevel(LogLevel.Error); rule.EnableLoggingForLevel(LogLevel.Fatal); break; case "Fatal": rule.EnableLoggingForLevel(LogLevel.Fatal); break; default: break; } }
Points of Interest
Use NLog in SOA system.