Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NLog multiple loggers in same class

I have a class that should be logging as usual, but sometimes it should be logging to a specific target(eg. a file).

Here are my NLog config files:

nlog.config

<?xml version="1.0" encoding="utf-8" ?>
<!-- XSD manual extracted from package NLog.Schema: https://www.nuget.org/packages/NLog.Schema-->
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xsi:schemaLocation="NLog NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogFile="C:\Logs\logfile.log"
      internalLogLevel="Info" >

  <targets>
    <target xsi:type="File" name="logfile" fileName="C:\Logs\logfile.log"
            layout="${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}" />
    <target xsi:type="Console" name="logconsole"
            layout="${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}" />
  </targets>

  <!-- rules to map from logger name to target -->
  <rules>
    <logger name="*" minlevel="Trace" writeTo="logfile,logconsole" />
  </rules>
</nlog>

and extra.config

<?xml version="1.0" encoding="utf-8" ?>
<!-- XSD manual extracted from package NLog.Schema: https://www.nuget.org/packages/NLog.Schema-->
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xsi:schemaLocation="NLog NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogFile="C:\Logs\extra.log"
      internalLogLevel="Info" >

  <targets>
    <target xsi:type="File" name="extra" fileName="C:\Logs\extra.log"
            layout="${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}" />
  </targets>

  <!-- rules to map from logger name to target -->
  <rules>
    <logger name="*" minlevel="Trace" writeTo="extra" />
  </rules>
</nlog>

I am trying something like this:

public class Program
{

    private static IConfiguration _configuration;

    public static void Main(string[] args)
    {
        var logger = NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
        try
        {
            logger.Debug("init main");
            var environment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production";

            _configuration = new ConfigurationBuilder()
                .AddEnvironmentVariables()
                .AddJsonFile("appsettings.json", false)
                .AddJsonFile($"appsettings.{environment}.json", true)
                .Build();
            CreateHostBuilder(args).Build().Run();
        }
        catch (Exception exception)
        {
            //NLog: catch setup errors
            logger.Error(exception, "Stopped program because of exception");
            throw;
        }
        finally
        {
            // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
            NLog.LogManager.Shutdown();
        }
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host
        .CreateDefaultBuilder(args)
        .ConfigureLogging(logBuilder =>
        {
            logBuilder.ClearProviders();
            logBuilder.SetMinimumLevel(LogLevel.Trace);
        })
        .ConfigureServices((hostContext, services) =>
        {
            //serviceConfig
        })
        .UseNLog()
        .UseWindowsService();
}

And the class I want two loggers in:

public class MyClass
{
    private readonly ILogger<MyClass> _logger;
    private readonly Logger _extraLogger;

    public MyClass(ILogger<MyClass> logger)
    {
        _logger = logger;
        _extraLogger = NLogBuilder.ConfigureNLog("extra.config").GetLogger("extra");
    }

    public void doBothTypesOfLogging()
    {
        var msg = "Hello";
        _extraLogger.Info(msg);
        _logger.LogInformation("Message sendt");
    }
}

But this makes all logging done by the application(even other classes) end up in the extra.log file which is defined by the extra.config. Eg. both msg AND "Message sendt" ends up in that file.

What i want is to have

"Message sendt" in logfile.log

msg in extra.log

Is this possible somehow?

like image 848
mTv Avatar asked May 25 '26 04:05

mTv


1 Answers

Why do you want two logs on the same class? If you have a subcomponent on your class which you would like to log separatelly, then it's allright, and all you need is a proper config file... but, normally, this would be a code smell.

You could do all you want with a single config:

<?xml version="1.0" encoding="utf-8" ?>
<!-- XSD manual extracted from package NLog.Schema: https://www.nuget.org/packages/NLog.Schema-->
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xsi:schemaLocation="NLog NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogFile="C:\Logs\logfile.log"
      internalLogLevel="Info" >

  <targets>
    <target xsi:type="File" name="logfile" fileName="C:\Logs\logfile.log"
            layout="${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}" />
    <target xsi:type="Console" name="logconsole"
            layout="${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}" />
<target xsi:type="File" name="extra" fileName="C:\Logs\extra.log"
        layout="${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}" />
  </targets>

  <!-- rules to map from logger name to target -->
  <rules>
    <logger name="extra" minlevel="Trace" writeTo="extra" final="true" />
    <logger name="*" minlevel="Trace" writeTo="logfile,logconsole" />
  </rules>
</nlog>

Pay attention to the "final" attribute in the "extra" log rule. It'll prevent the extra logs to be written to the other logs.

There's no need to create this logger with a different config. Just do as bellow:

public class MyClass
{
    private readonly ILogger<MyClass> _logger;
    private readonly Logger _extraLogger = LogManager.GetLogger("extra");

    public MyClass(ILogger<MyClass> logger)
    {
        _logger = logger;
    }

    public void doBothTypesOfLogging()
    {
        var msg = "Hello";
        _extraLogger.Info(msg);
        _logger.LogInformation("Message sendt");
    }
}

But I think you should reconsider if you really need two loggers for the same class.

UPDATE


According to your needs you can simplify your code a lot by just using out of the box NLog features and following a more nlogish pattern. The config file is almost the same, just changed the rule for the "extra" logger to "syslog" since we'll be changing it's name in the code:

<?xml version="1.0" encoding="utf-8" ?>
<!-- XSD manual extracted from package NLog.Schema: https://www.nuget.org/packages/NLog.Schema-->
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xsi:schemaLocation="NLog NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogFile="C:\Logs\logfile.log"
      internalLogLevel="Info" >

  <targets>
    <target xsi:type="File" name="logfile" fileName="C:\Logs\logfile.log"
            layout="${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}" />
    <target xsi:type="Console" name="logconsole"
            layout="${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}" />
    <!-- This will probably be a network target -->
    <target xsi:type="File" name="extra" fileName="C:\Logs\extra.log"
            layout="${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}" />
  </targets>

  <!-- rules to map from logger name to target -->
  <rules>
    <logger name="syslog" minlevel="Trace" writeTo="extra" final="true" />
    <logger name="*" minlevel="Trace" writeTo="logfile,logconsole" />
  </rules>
</nlog>

We'll be putting the "syslog" logger in a static variable inside a helper static class:

public static class CommonLoggers
{
    public static Logger SysLogger { get; private set; } = LogManager.GetLogger("syslog");
   // you can put other centralized loggers here if you want
}

Then you should change your class code to simply:

public class MyClass
{
    // the "correct" pattern is to use a logger that follows the class name
    // put this in each class you create
    private static Logger logger = LogManager.GetCurrentClassLogger();
    
    // nothing to do on the constructor (can be removed as it's redundant)
    public MyClass() { }

    public void doBothTypesOfLogging() {
        var msg = "Hello";
        CommonLoggers.SysLogger.Info(msg);
        logger.Info("Message sent");
    }
}

The important part is to understand that NLog rules are used to filter output given the logger names, and you can do something like this for example:

  <rules>
    <logger name="SomeNamespace.Somewhere.*" minlevel="Trace" writeTo="extra"  final="true"/>
    <logger name="SomeNamespace.SomewhereElse.MyClass" minlevel="Info" writeTo="some_other_target" />
    <logger name="*" minlevel="error" writeTo="logfile" />
    <logger name="*" minlevel="info" writeTo="logconsole" />
  </rules>

This way you could TRACE all logs from all classes inside "SomeNamespace.Somewhere.*" to the "extra" target. Write all INFO logs of "MyClass" to "some_other_target". Send ERROR messages to "logfile" and INFO messages to "logconsole".

Reading the docs on rules, targets and filters will help you a lot!

This is a good article: https://www.codeproject.com/Articles/4051307/NLog-Rules-and-filters

The official docs: https://github.com/nlog/nlog/wiki/Configuration-file#rules

And this wonderful SO question: Most useful NLog configurations

Happy reading!

like image 54
Loudenvier Avatar answered May 27 '26 18:05

Loudenvier