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);
XmlNode xn = xDoc.SelectSingleNode("//target[@type=\"Database\"]");
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["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);
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();
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);
}
LogManager.ReconfigExistingLoggers();
}
public static void EnableHigherLevel(LoggingRule rule, LogLevel logLevel)
{
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.