Reputation: 59
So, just started using NLog. I'm doing a programmatic implementation where I'm trying to setup a class that I can import into any project. The class has two methods: CreateLogger() and GenerateLog(). Here is the class in its entirety:
using System;
using NLog;
using NLog.Config;
using NLog.Targets;
namespace LogEngine
{
/// <summary>
/// Create an instance of NLog for use on a per class level.
/// </summary>
internal sealed class EventLog
{
#region Internal Methods
/// <summary>
/// Generates the NLog.Logger object that will control logging facilities in this program.
/// </summary>
/// <returns>
/// static reference to a <see cref="NLog.Logger" /> object.
/// </returns>
internal static Logger CreateLogger(string baseDir = @"${basedir}\")
{
// Setup log configuration object and new file and screen output targets.
var config = new LoggingConfiguration();
var screenTarget = new ConsoleTarget();
config.AddTarget("screen", screenTarget);
var fileTarget = new FileTarget();
config.AddTarget("file", fileTarget);
screenTarget.Layout = @"${newline}${message}";
var MinScreenOutput = new LoggingRule("*", LogLevel.Fatal, screenTarget);
config.LoggingRules.Add(MinScreenOutput);
// Set the properties for the file output target.
fileTarget.FileName = baseDir + @"${appdomain:format={1\}} logs\${shortdate}.log";
fileTarget.Layout = @"${longdate} ${pad:padcharacter=~:padding=29:inner= ${level:uppercase=true}}"
+ @" ${pad:padcharacter=~:padding=30:inner= Event ID\: ${event-properties:item=EventCode}}"
+ @"${newline}${message} ${when:when=level == 'Error':inner=${newline}Class / Method\:"
+ @"${pad:padding=9:inner=}${callsite:fileName=true:includeSourcePath=false:skipFrames=1}"
+ @"${newline}Exception\:${pad:padding=14:inner=}${exception}}${newline}";
// Define what sort of events to send to the file output target.
var MinOutputDebug = new LoggingRule("*", LogLevel.Debug, fileTarget);
config.LoggingRules.Add(MinOutputDebug);
// Set the configuration for the LogManager
LogManager.Configuration = config;
// Get the working instance of the logger.
return LogManager.GetLogger("LogEngine");
}
/// <summary>
/// Passes one log entry to the destination logger.
/// </summary>
/// <param name="log">
/// The <see cref="NLog.Logger" /> object to write the log entry to.
/// </param>
/// <param name="eventId">
/// Four character unique event ID as <see cref="System.String" />.
/// </param>
/// <param name="level">
/// The <see cref="NLog.LogLevel" /> value.
/// </param>
/// <param name="message">
/// The message to save to the log file as <see cref="System.String" />.
/// </param>
/// <param name="ex">
/// If this is an error log event, pass it as an <see cref="System.Exception" /> object.
/// </param>
internal static void GenerateLog(Logger log, string eventId, LogLevel level, string message, Exception ex = null)
{
// Values used for all events.
LogEventInfo logEvent = new LogEventInfo();
logEvent.Properties["EventCode"] = eventId;
logEvent.Level = level;
logEvent.Message = message;
// If we have an error log, make sure the exception is passed.
if (level.Equals(LogLevel.Error))
logEvent.Exception = ex;
// Actually write the log entry.
log.Log(logEvent);
if (level.Equals(LogLevel.Error) || level.Equals(LogLevel.Fatal))
System.Environment.Exit(Convert.ToInt32(eventId));
}
#endregion Internal Methods
}
}
In the CreateLogger() method, you'll see there is a default parameter. So, how things work is when I call CreateLogger() in my program at the start of my class, I pass no parameters and the ${basedir} value is used to generate initial logging:
internal class Program
{
#region Private Fields
private static Logger log = EventLog.CreateLogger();
#endregion Private Fields
...
However, during execution, I need to change the logging location from ${basedir} to a value that I pull from a SQL database. Here is how I do that:
if (!path.Equals(null))
{
sArgs.path = path.ToString().Trim();
//NLog.Config.SimpleConfigurator.ConfigureForFileLogging(sArgs.path + @"Logs\log1.txt", LogLevel.Debug);
//LogManager.Shutdown();
//LogManager.ReconfigExistingLoggers();
log = EventLog.CreateLogger(sArgs.path);
LogManager.ReconfigExistingLoggers();
}
"path" is an object returned by a call to SQLCommand.ExecuteScalar(). It is the replacement for ${basedir} that I need to connect my Logger to. If the path is not null, then I convert it to a string and store it into a singleton class instantiated as "sArgs". There are some commented out code here to show how I've been trying to resolve this issue.
Okay, so what I'm seeing is in the last code block (when I set "log" to a new instance generated by CreateLogger(sArgs.path)) I can see that my logging path in the log object is actually updating. But, when I get to the first opportunity to log an event, it is still using the old Logger instance (so ${basedir} is still in use, not sArgs.path).
My question is, what am I missing that is keeping the change to "log" that I can plainly see while stepping my code in the debugger from actually becoming the location for the Logger object? Or is it that I'm doing the EventLog class completely wrong?
Thank you for any insights you can provide to this problem.
Upvotes: 1
Views: 3935
Reputation: 59
Thank you, @Riann for your help and added comments. I actually got this working as a singleton class without affecting the ability of ${callsite} to know the actual method and line where the error is caught. Here is how I did it starting with the updated EventLog class:
using System;
using NLog;
using NLog.Config;
using NLog.Targets;
namespace LogEngine
{
/// <summary>
/// Create an instance of NLog for use on a per class level.
/// </summary>
internal sealed class EventLog
{
#region Constructors
static EventLog()
{
}
private EventLog()
{
this.CreateLogger();
}
#endregion Constructors
#region Singleton Objects
internal static EventLog logger { get { return _logger; } }
private static readonly EventLog _logger = new EventLog();
#endregion Singleton Objects
#region Private Fields
private static Logger _log;
#endregion Private Fields
#region Internal Methods
/// <summary>
/// Generates the NLog.Logger object that will control logging facilities in this program.
/// </summary>
internal void CreateLogger(string baseDir = @"${basedir}\")
{
// Setup log configuration object and new file and screen output targets.
var config = new LoggingConfiguration();
var screenTarget = new ConsoleTarget();
config.AddTarget("screen", screenTarget);
var fileTarget = new FileTarget();
config.AddTarget("file", fileTarget);
screenTarget.Layout = @"${newline}${message}";
var MinScreenOutput = new LoggingRule("*", LogLevel.Fatal, screenTarget);
config.LoggingRules.Add(MinScreenOutput);
// Set the properties for the file output target.
fileTarget.FileName = baseDir + @"${appdomain:format={1\}} logs\${shortdate}.log";
fileTarget.Layout = @"${longdate} ${pad:padcharacter=~:padding=29:inner= ${level:uppercase=true}}"
+ @" ${pad:padcharacter=~:padding=30:inner= Event ID\: ${event-properties:item=EventCode}}"
+ @"${newline}${message} ${when:when=level == 'Error':inner=${newline}Class / Method\:"
+ @"${pad:padding=9:inner=}${callsite:fileName=true:includeSourcePath=false:skipFrames=1}"
+ @"${newline}Exception\:${pad:padding=14:inner=}${exception}}"
+ @"${when:when=level == 'Fatal':inner=${newline}Class / Method\:"
+ @"${pad:padding=9:inner=}${callsite:fileName=true:includeSourcePath=false:skipFrames=1}"
+ @"${newline}Exception\:${pad:padding=14:inner=}${exception}}${newline}";
// Define what sort of events to send to the file output target.
var MinOutputDebug = new LoggingRule("*", LogLevel.Debug, fileTarget);
config.LoggingRules.Add(MinOutputDebug);
// Set the configuration for the LogManager
LogManager.Configuration = config;
// Get the working instance of the logger.
_log = LogManager.GetLogger("LogEngine");
}
/// <summary>
/// Passes one log entry to the destination logger and associated exception information.
/// </summary>
/// <remarks>
/// Use this form of the method when <see cref="NLog.LogLevel.Error" /> or
/// <see cref="NLog.LogLevel.Fatal" /> is used.
/// </remarks>
/// <param name="caller">
/// <see cref="System.String" /> holding information about the calling method.
/// </param>
/// <param name="eventId">
/// Four character unique event ID as <see cref="System.String" />.
/// </param>
/// <param name="level">
/// The <see cref="NLog.LogLevel" /> value.
/// </param>
/// <param name="message">
/// The message to save to the log file as <see cref="System.String" />.
/// </param>
/// <param name="ex">
/// If this is an error log event, pass it as an <see cref="System.Exception" /> object.
/// </param>
internal void GenerateLog(string eventId, LogLevel level, string message, Exception ex)
{
// Values used for all events.
LogEventInfo logEvent = new LogEventInfo();
logEvent.Properties["EventCode"] = eventId;
logEvent.Level = level;
logEvent.Message = message;
logEvent.Exception = ex;
// Actually write the log entry.
_log.Log(logEvent);
if (level.Equals(LogLevel.Error) || level.Equals(LogLevel.Fatal))
Environment.Exit(Convert.ToInt32(eventId));
}
/// <summary>
/// Passes one log entry to the destination logger.
/// </summary>
/// <remarks>
/// Use this form of the method when <see cref="NLog.LogLevel.Warn" /> or
/// <see cref="NLog.LogLevel.Info" /> is used.
/// </remarks>
/// <param name="caller">
/// <see cref="System.String" /> holding information about the calling method.
/// </param>
/// <param name="eventId">
/// Four character unique event ID as <see cref="System.String" />.
/// </param>
/// <param name="level">
/// The <see cref="NLog.LogLevel" /> value.
/// </param>
/// <param name="message">
/// The message to save to the log file as <see cref="System.String" />.
/// </param>
internal void GenerateLog(string eventId, LogLevel level, string message)
{
// Values used for all events.
LogEventInfo logEvent = new LogEventInfo();
logEvent.Properties["EventCode"] = eventId;
logEvent.Level = level;
logEvent.Message = message;
// Actually write the log entry.
_log.Log(logEvent);
}
#endregion Internal Methods
}
}
Basically, aside from the Contructor, Singleton Objects, and Private Fields regions, all of which is new, I had to change my calls in CreateLogger() and GenerateLog() to affect the private field _log. I did also make GenerateLogger() an overloaded method, but that doesn't affect overall usage of the EventLog class.
In each of my other classes, I only had to change how I open my logger. I was going to do this at the method level, but decided to do it at class level:
internal class Program
{
#region Private Fields
private static readonly EventLog log = EventLog.logger;
#endregion Private Fields
...
So, for any method I'm in, if I want to change my logging path, I just ask my log instance to call CreateLogger(path):
if (!path.Equals(null))
{
sArgs.path = path.ToString().Trim();
log.CreateLogger(sArgs.path);
}
Any future calls, no matter what class I'm in, I just need to call GenerateLog() and I'll have the right log path.
Upvotes: 0
Reputation: 1601
For each and every class where you use private static Logger log = EventLog.CreateLogger();
, you need to change the baseDir whenever you do not want the default baseDir defined in you EventLog
class to be used.
You did not provide the code for your singleton class sArgs, or any other samples of the classes where you want to use your EventLog.
Using your code, I only changed your EventLog
to EventLogger
and also the default CreateLogger
to internal static Logger CreateLogger(string baseDir = @"C:\Temp\NLog\Default\")
:
using System;
using NLog;
namespace ConsoleApplication1
{
class Program
{
private static Logger log = EventLogger.CreateLogger();
static void Main(string[] args)
{
EventLogger.GenerateLog(log, "1", LogLevel.Debug, "Default", null);
log = EventLogger.CreateLogger(@"C:\Temp\NLog\New\");
LogManager.ReconfigExistingLoggers();
EventLogger.GenerateLog(log, "2", LogLevel.Debug, "New", null);
Class1.DoSomething();
Console.WriteLine("Press ENTER to exit");
Console.ReadLine();
}
}
}
And Class1:
using NLog;
namespace ConsoleApplication1
{
public static class Class1
{
private static Logger log = EventLogger.CreateLogger();
public static void DoSomething()
{
EventLogger.GenerateLog(log, "3", LogLevel.Debug, "Class1.DoSomething", null);
}
}
}
Running the code result in the following output:
Log EventId 1 will be written to C:\Temp\NLog\Default\ConsoleApplication1.vshost.exe\logs
Log EventId 2 will be written to C:\Temp\NLog\New\ConsoleApplication1.vshost.exe\logs
and LogEventId 3 in Class1.cs will be written to C:\Temp\NLog\Default\ConsoleApplication1.vshost.exe\logs
because in Class1.cs when the log was initialised, the default path was used. If you want to change the baseDir of the log in Class1.cs (and subsequent classes) you will need to update the paths individually.
Hope this helps.
Upvotes: 0