Log just this information to a other file with Serilog

Viewed 1600

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.

3 Answers

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

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");
}

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); }
Related