NLog is a flexible and free logging platform for .NET applications, capable of routing logs to various destinations such as files, databases, and the Visual Studio console. Integration begins with installing the NLog and NLog.Config NuGet packages.
Configuration Setup
It is advisable to maintain logging configuration in a dedicated XML file named NLog.config. Ensure the file's "Copy to Output Directory" property is set to "Copy always". The configuration structure consists of <targets>, defining where logs are stored, and <rules>, defining which log levels are directed to which targets.
Below is a configuration example demonstrating asynchronous logging to a text file, a SQL Server database, and the Visual Studio debug output.
<?xml version="1.0" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true">
<targets>
<!-- Asynchronous File Logging -->
<target name="fileTarget" xsi:type="AsyncWrapper" queueLimit="5000" overflowAction="Discard">
<target xsi:type="File" fileName="${basedir}/logs/${shortdate}.log"
layout="${longdate} | ${level:uppercase=true} | ${message} ${event-context:item=OperationType} ${event-context:item=MetricValue} ${stacktrace}" />
</target>
<!-- Asynchronous Database Logging -->
<target name="dbTarget" xsi:type="AsyncWrapper" queueLimit="5000" overflowAction="Discard">
<target type="Database" dbProvider="mssql"
connectionString="Data Source=.;Initial Catalog=AppLogDb;User ID=sa;Password=secret;">
<commandText>
INSERT INTO SystemLogs(Timestamp, Level, Message, OperationType, MetricValue, StackTrace)
VALUES(@TimeStamp, @Level, @Message, @OpType, @Metric, @Trace);
</commandText>
<parameter name="@TimeStamp" layout="${date}" />
<parameter name="@Level" layout="${level:uppercase=true}" />
<parameter name="@Message" layout="${message}" />
<parameter name="@OpType" layout="${event-context:item=OperationType}" />
<parameter name="@Metric" layout="${event-context:item=MetricValue}" />
<parameter name="@Trace" layout="${stacktrace}" />
</target>
</target>
<!-- Visual Studio Output Window -->
<target name="debugTarget" xsi:type="Debugger" layout="NLog: ${date:format=HH\:mm\:ss} | ${level:uppercase=true:padding=-5} | ${message}" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="debugTarget" />
<logger name="*" minlevel="Info" writeTo="dbTarget" />
<logger name="*" minlevel="Debug" writeTo="fileTarget" />
</rules>
</nlog>Logger Implementation
To handle custom properties like OperationType and MetricValue defined in the database target, a wrapper class using LogEventInfo is required. This allows for the injection of custom data into the log context.
public class AppLogger
{
private readonly NLog.Logger _logger;
public AppLogger(string name)
{
_logger = LogManager.GetLogger(name);
}
public static AppLogger Default { get; } = new AppLogger("DefaultLogger");
public void LogCustomEvent(LogData logData)
{
var logLevel = LogLevel.FromString(logData.Level);
var logEvent = new LogEventInfo(logLevel, _logger.Name, logData.Message);
logEvent.TimeStamp = logData.Timestamp;
logEvent.Properties["OperationType"] = logData.OperationType;
logEvent.Properties["MetricValue"] = logData.MetricValue;
_logger.Log(logEvent);
}
public void Info(string message) => _logger.Info(message);
public void Error(string message) => _logger.Error(message);
public void Debug(string message) => _logger.Debug(message);
public void Flush()
{
LogManager.Flush();
}
}The LogData model represents the structured data to be logged.
public class LogData
{
public string Level { get; set; }
public string Message { get; set; }
public string OperationType { get; set; }
public string MetricValue { get; set; }
public DateTime Timestamp { get; set; } = DateTime.Now;
public LogData(string level, string message, string opType = null, string metric = null)
{
Level = level;
Message = message;
OperationType = opType;
MetricValue = metric;
}
}Usage
The logger can be called to write standard messages or complex objects containing custom parameters.
// Standard logging
AppLogger.Default.Info("Application started.");
AppLogger.Default.Error("An error occurred during processing.");
// Custom structured logging
var customLog = new LogData("Info", "User transaction completed", "Payment", "250.00");
AppLogger.Default.LogCustomEvent(customLog);
// Ensure asynchronous queues are flushed
AppLogger.Default.Flush();Since the file and database targets are wrapped in AsyncWrapper, calling Flush() is necessary to immediately write queued messages to the destination, particularly useful when exiting the application.