breusshe breusshe - 3 months ago 32
C# Question

How to replace NLog Logger with a new instance of Logger?

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.

Answer

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 EventLogclass 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.

Comments