3

I have a .NET 5.0 console application with Serilog nugget. The serilog part of appsettings.json looks like this :

"Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ],
"WriteTo": [
  {
    "Name": "Console",
    "Args": {
      "outputTemplate": "[{Timestamp:HH:mm:ss.fff} [{Level}] {SourceContext} {Message}{NewLine}{Exception}",
      "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console"
    }
  },
  {
    "Name": "File",
    "Args": {
      "path": "/Logs/SharedTreatment_logs.txt",
      "outputTemplate": "{Timestamp:G} {SourceContext} [{Level}] {Message}{NewLine:1}{Exception:1}",
      "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog",
      "fileSizeLimitBytes": 1000000,
      "rollOnFileSizeLimit": "true",
      "shared": "true",
      "flushToDiskInterval": 3
    }

  }
]

It is loaded with this code :

    Serilog.Debugging.SelfLog.Enable(Console.Error);
    
                var configuration = new ConfigurationBuilder()
                                .AddJsonFile("appsettings.json")
                                .Build();
    
                var logger = loggerConfiguration.ReadFrom.Configuration(configuration).CreateLogger();
                
                Log.Logger = logger?.ForContext<T>() ?? throw new ArgumentNullException(nameof(logger));

Host.CreateDefaultBuilder(args).UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration))

Each class in the solution got a ILogger injection for example ILogger<MyClass> logger.

The question is how I create a new logger that uses another log configuration section from the appsettings.json to log to another file in such a class?

Update : After a lot of seraching and testing I think I found a way to do this. Im still however not sure it is the right way or the best way?

I have to create 2 sub-loggers, one for the main logging that excludes files and one that only log files like this :

{
  "Serilog": {
    "Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
    "MinimumLevel": "Debug",
    "Enrich": [ "FromLogContext" ],
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "outputTemplate": "[{Timestamp:HH:mm:ss.fff} [{Level}] {SourceContext} {Message}{NewLine}{Exception}",
          "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console"
        }
      },
      {
        "Name": "Logger",
        "Args": {
          "configureLogger": {
            "Filter": [
              {
                "Name": "ByIncludingOnly",
                "Args": {
                  "expression": "@p['LogToFile']"
                }
              }
            ],
            "WriteTo": [
              {
                "Name": "File",
                "Args": {
                  "path": "Logs/SharedTreatment_logs_Second.txt",
                  "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
                }
              }
            ]
          }
        }
      },
      {
        "Name": "Logger",
        "Args": {
          "configureLogger": {
            "Filter": [
              {
                "Name": "ByExcluding",
                "Args": {
                  "expression": "@p['LogToFile']"
                }
              }
            ],
            "WriteTo": [
              {
                "Name": "File",
                "Args": {
                  "path": "Logs/SharedTreatment_logs.txt",
                  "outputTemplate": "{Timestamp:G} {SourceContext} [{Level}] {Message}{NewLine:1}{Exception:1}",
                  "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog",
                  "fileSizeLimitBytes": 1000000,
                  "rollOnFileSizeLimit": "true",
                  "shared": "true",
                  "flushToDiskInterval": 3
                }
              }
            ]
          }
        }
      }


    ]
  }
}

This is how the C# code looks like :

static void Main(string[] args)
        {
           
            Console.WriteLine("Hello World!");
            Log.Logger = new LoggerConfiguration().DefaultLoggerSetup<Program>();
            using (LogContext.PushProperty("LogToFile", true))
            {
                Log.Information("LogToFileTest");
            }

            Log.Information("Just log");
            Log.CloseAndFlush();

        }

Two files is creaed and the filtered logs is where they should be.

Banshee
  • 15,376
  • 38
  • 128
  • 219
  • There is an answer on [link](https://stackoverflow.com/questions/46516359/filter-serilog-logs-to-different-sinks-depending-on-context-source). You configure 2 sinks (this can be done with a Json configuration) and a filter expression. – Hazrelle Nov 29 '21 at 17:27

3 Answers3

5

You don't have to use another ILogger to inject separately. Serilog handles it by itself. Just use sub-loggers and add some WriteTo.Logger . Then you can use ILogger somewhere inside your code and it automatically saves logs according to your configuration.

For example:

Log.Logger = new LoggerConfiguration()
    .WriteTo.Logger(lc => lc
        .Filter.ByExcluding(Matching.FromSource("AnySource"))
        .WriteTo.File("first.txt", new JsonFormatter()))
    .WriteTo.Logger(lc => lc
        .Filter.ByIncludingOnly(Matching.FromSource("AnySource"))
        .WriteTo.File("second.txt", new JsonFormatter()))
    .WriteTo.Logger(config => config
        .Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Debug)
        .WriteTo.File("third.txt", new JsonFormatter()))
        (...)
    .CreateLogger();

You can also use app settings configs to tell Verilog to do it like this:

  {
  "Serilog": {
    "Using": ["Serilog.Settings.Configuration"],
    "Filter": [
      {
        "Name": "ByExcluding",
        "Args": {
          "expression": "EndsWith(RequestPath, '/SomeEndpoint')"
        }
      }
    ]

You can see here and here for more information

Mohammad
  • 1,197
  • 2
  • 13
  • 30
  • Thanks, is it possible to set a key when calling the log method and then in the config file filter only logs with this key? And then make sure that these logs do not show up in the main log flow? All examples I have seen so far on sub-loggers filter on namespace or likewise. – Banshee Nov 29 '21 at 20:46
  • @Banshee filters use Serilog Expressions [Filter.ByIncludingOnly(expr)] and with the expression, you can do lots of things. have a look here: https://github.com/serilog/serilog-expressions . I think you should have some challenges to do that :) – Mohammad Nov 29 '21 at 21:44
  • Its still not clear how to filter on a specific property from the appsettings.config? All examples I find is by code. – Banshee Dec 01 '21 at 16:02
0

ILogger is a Singleton see here & issue here ILogger<T>

Better option which is simpler IMHO is to create multiple separate sections within the appsettings.json, for e.g. ErrorLog, SecondErrorLog and AuditLog.

  • Step 1 Create sections within the appsettings.json like so

  "ErrorLog": {
    "Using": [ "Serilog.Sinks.File" ],
    "MinimumLevel": "Debug",
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "c:\\temp\\error1-.log",
          "rollingInterval": "Day",
          "restrictedToMinimumLevel": "Error"
        }
      }
    ]
  },
  "SecondErrorLog": {
    "Using": [ "Serilog.Sinks.File" ],
    "MinimumLevel": "Debug",
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          // your Second Error Log or Audit Log etc.
          "path": "c:\\temp\\error2-.log",
          "rollingInterval": "Day",
          "restrictedToMinimumLevel": "Information"
        }
      }
    ]
  }

  • Step 2, and now inside your code, you can access, audit etc.
var myDualErrorLogConfiguration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
            .AddEnvironmentVariables()
            .Build();

        // now get your First Error Log or Audit Log etc.
        var errorSection = myDualErrorLogConfiguration.GetSection("ErrorLog");
       
        // your Second Error Log or Audit Log etc.
        var auditSection = myDualErrorLogConfiguration.GetSection("SecondErrorLog");

        _log = new LoggerConfiguration()
            .ReadFrom
            .ConfigurationSection(errorSection)
            .CreateLogger();

        _secondLog = new LoggerConfiguration()
            .ReadFrom
            .ConfigurationSection(auditSection)
            .CreateLogger();

Update 2:

Lets take step back, for what you're trying to do, i.e. multiple loggers with different configuration, you need an ILoggerFactory to create and configure those, see the example below credit to Hooman. This will help you with creating different loggers as you are seeking.

ILoggerFactory factory = new LoggerFactory().AddConsole();    // add console provider
factory.AddProvider(new LoggerFileProvider("c:\\FirstErrorLog.txt"));   // add file provider
Logger logger = factory.CreateLogger(); // <-- creates a console logger & file logger

Now you can inject it in your constructor

public SomeController(ISomeRepository someRepository, ILoggerFactory logger)
{
    _someRepository = someRepository;
    _logger = logger.CreateLogger("SomeApi.Controllers.TodoController");
}
Transformer
  • 6,963
  • 2
  • 26
  • 52
  • Thanks but Im using injection to get the specific class logger to the class. With your suggestion I suspect that I have to create the diffrent loggers at startup in program.cs and then inject them into the injection build up in some way. Like saying that ILogger – Banshee Dec 08 '21 at 17:17
  • Thanks but even with the Update 2 its not clear how to log to different outputs, to me my post update seems easier? – Banshee Dec 09 '21 at 08:40
0

To solve this I hade to create a separated log entry in the appsettings.json like this :

{
          "Name": "Logger",
          "Args": {
            "configureLogger": {
              "Filter": [
                {
                  "Name": "ByIncludingOnly",
                  "Args": {
                    "expression": "@p['LogToFile']"
                  }
                }
              ],
              "WriteTo": [
                {
                  "Name": "File",
                  "Args": {
                    "path": "Logs/SharedTreatment_logs_Second.txt",
                    "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
                  }
                }
              ]
            }
          }
        },

I den hade to push the specific loggings to this logger like this :

using (LogContext.PushProperty("LogToFile", true))
                { _logger.LogInformation("Incoming Message   MessageGuid : {MessageGuid} , MessageChainGuid : {MessageChainGuid}, SharedTreatment : {SharedTreatment}", incomingMessage.MessageGuid, incomingMessage.MessageChainGuid, incomingMessage.SharedTreatment); }
Banshee
  • 15,376
  • 38
  • 128
  • 219