2

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?

mTv
  • 1,074
  • 17
  • 28

2 Answers2

4

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!

Loudenvier
  • 8,362
  • 6
  • 45
  • 66
  • Thanks, I'll test it asap. Maybe you have a better solution? But what im really trying to do is send this message to a SYSLOG server, so target would be of type network. We want to send only the `msg` to the SYSLOG server and nothing else. This is the reason i thought two loggers would be a good idea. Or do you have any suggestions? – mTv Dec 17 '20 at 12:16
  • 1
    @mTv I get it! I think you should have a static, globally accessible "extra" log then. You don't have to make the loggers instance variables. I'll update the answer with a proposed solution and more explanations. – Loudenvier Dec 18 '20 at 15:28
3

Instead of using NLog LogManager directly, then I suggest that you stay on ILogger-path, but call out to the ILoggerFactory like this:

public class MyClass
{
    private readonly ILogger _logger;
    private readonly ILogger _extraLogger;

    public MyClass(ILoggerFactory logfactory)
    {
        _logger = logfactory.CreateLogger(GetType().ToString());
        _extraLogger = logfactory.CreateLogger("extra");
    }

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

But I agree with the accepted answer, that one should use a single NLog.config.

Rolf Kristensen
  • 17,785
  • 1
  • 51
  • 70