TuomasK
TuomasK

Reputation: 1899

Serilog multiple files appsettings.json

Im trying to configure serilog to write to multiple files, with no luck whatsoever. With this configuration it just writes to the second file?

{
  "AllowedHosts": "*",
  "Serilog": {
    "Using": [ "Serilog.Sinks.File" ],
    "MinimumLevel": "Debug",
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "c:\\temp\\audit-.log",
          "rollingInterval": "Day",
          "restrictedToMinimumLevel": "Information"
        }
      },
      {
        "Name": "File",
        "Args": {
          "path": "c:\\temp\\error-.log",
          "rollingInterval": "Day",
          "restrictedToMinimumLevel": "Error"
        }
      }
    ]
  }
}

Or is there any way to load many loggers to the software with different configurations from appsettings.json. Something like this?

var errorLogConfiguration = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
    .AddEnvironmentVariables()
    .Build();

_log = new LoggerConfiguration()
    .ReadFrom
    .Configuration(errorLogConfiguration)
    .CreateLogger();

Upvotes: 11

Views: 18020

Answers (6)

Saqif Haque
Saqif Haque

Reputation: 408

I have got a cool solution from a blog i have found. It surely did perform like a charm. if any problem occurs pls let me know. I am also sharing the blog with you guys https://www.techrepository.in/blog/posts/writing-logs-to-different-files-serilog-asp-net-core. Also for filtering the logs you need to use Serilog.Eexpressions nuget package. Hope you get what you are looking for from here

"Serilog": {
"Using": [ "Serilog.Sinks.File", "Serilog.Expressions"  ],
"MinimumLevel": {
  "Default": "Debug"
},
"WriteTo": [
  {
    "Name": "Logger",
    "Args": {
      "configureLogger": {
        "Filter": [
          {
            "Name": "ByIncludingOnly",
            "Args": {
              "expression": "@l = 'Error' or @l = 'Fatal' or @l = 'Warning'"
            }
          }
        ],
        "WriteTo": [
          {
            "Name": "File",
            "Args": {
              "path": "../logs/error_.log",
              "outputTemplate": "{Timestamp:o} [{Level:u3}] ({SourceContext}) {Message}{NewLine}{Exception}",
              "rollingInterval": "Day"
            }
          }
        ]
      }
    }
  },
  {
    "Name": "Logger",
    "Args": {
      "configureLogger": {
        "Filter": [
          {
            "Name": "ByIncludingOnly",
            "Args": {
              "expression": "@l = 'Information' or @l = 'Debug'"
            }
          }
        ],
        "WriteTo": [
          {
            "Name": "File",
            "Args": {
              "path": "../logs/debug_.log",
              "outputTemplate": "{Timestamp:o} [{Level:u3}] ({SourceContext}) {Message}{NewLine}{Exception}",
              "rollingInterval": "Day"
            }
          }
        ]
      }
    }
  }
]},

Step 1: Install Required Packages

Ensure you have the necessary Serilog packages installed. You will need:

Serilog Serilog.Sinks.File Serilog.Expressions

Explanation:

  • Serilog Section: Configures Serilog's sinks and settings.
  • Using: Lists the Serilog packages in use.
  • MinimumLevel: Sets the default log level.
  • WriteTo: Defines where logs should be written and the conditions for writing them.
    • First Configuration Block: Logs Error, Fatal, or Warning messages to error_.log.
    • Second Configuration Block: Logs Information or Debug messages to debug_.log.
  • Filter: Uses Serilog's filtering capabilities to include only specific log levels based on the expression.
  • WriteTo File Sink: Specifies the file path and output format for the logs.

Upvotes: 3

Alex Cr
Alex Cr

Reputation: 441

My findings after a while of errors, retry and nearly giving up based on lack of documentation about Serilog. They have a tread on GitHub: https://github.com/serilog/serilog-filters-expressions/issues/27. Nearly every thread goes to the same conclusion that you have to create a SUBLOGGER. This is my implementation. For this implementation you need the following plugins:

  • Serilog Filter
  • Serilog Sink
  • Serilog Async

    "Serilog": {
    "Using": [ "Serilog.Sinks.File" ],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "WriteTo:Information": { //this name here can be changed
      "Name": "Logger", //this name here is essential
      "Args": {
        "configureLogger": {
          "Filter": [
            {
              "Name": "ByIncludingOnly",
              "Args": {
                "expression": "@Level = 'Information'"
              }
            }
          ],
          "WriteTo": [
            {
              "Name": "Async", //i use async plugin from serilog
              "Args": {
                "configure": [
                  {
                    "Name": "File",
                    "Args": {
                      "path": "Logs/Log_.txt",
                      "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog",
                      "rollingInterval": "Day",
                      "retainedFileCountLimit": 7
                    }
                  }
                ]
              }
            }
          ]
        }
      }
    },
    

Upvotes: 5

CYB3R055
CYB3R055

Reputation: 81

After struggling for days due to the simple documentation for Serilog and any of the sinks, this is what finally worked for me without creating any separate logger:

"Serilog": {
    "Using": [ "Serilog.Sinks.Async" ],
    "MinimumLevel": "Verbose",
    "Enrich": [ "FromLogContext", "WithDemystifiedStackTraces" ],
    "WriteTo:Information": {
        "Name": "Async",
        "Args": {
            "Configure": [
                {
                    "Name": "RollingFile",
                    "Args": {
                        "RestrictedToMinimumLevel": "Information",
                        "Formatter": "FOOINC.API.Configuration.Logging.CustomRenderedCompactJsonFormatter, FOOINC.API.Configuration",
                        "PathFormat": "_logs\\info\\info-log.json"
                    }
                }
            ]
        }
    },
    "WriteTo:Error": {
        "Name": "Async",
        "Args": {
            "Configure": [
                {
                    "Name": "RollingFile",
                    "Args": {
                        "RestrictedToMinimumLevel": "Error",
                        "Formatter": "FOOINC.API.Configuration.Logging.CustomRenderedCompactJsonFormatter, FOOINC.API.Configuration",
                        "PathFormat": "_logs\\errors\\error-log.json"
                    }
                }
            ]
        }
    }
}

Hope this helps anyone out there!!

Upvotes: 7

Alexander Brattsev
Alexander Brattsev

Reputation: 984

There is little documentation on logger settings through the configuration file. Can anyone help - just an example of using the settings of the logger. In the example - all logs are written in one sample.txt. Logs on a call of a certain API / api/health - in a separate file and are not included in sample.txt. And an example ad - IMyLogger-writes to a separate SampleMy.txt. You can add many sections, and divide the logs by different criteria. It is better to set local logging levels as minimal, they will be overwritten by the global level. The global filter will exclude logs from all sub-loggers (I don't use it). PS sorry for the bad English)

"Serilog": {
    "MinimumLevel": "Information", //<- global error level. Ovveride all local error level
    "WriteTo": [
      {
        "Name": "Console"
      },
      {
        "Name": "Logger",
        "Args": {
          "configureLogger": {
            "MinimumLevel": "Debug", // <- local error level. 
            //Only records with Information logging level will be written to the log file
           //but if ovveride global level to Debug, and dont override local error level -> it will still be global
            "WriteTo": [
              {
                "Name": "File",
                "Args": {
                  "path": "log\\SampleHealthCheck-.txt", //write health-check log in different file
                  "rollingInterval": "Day",
                  "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [CorrId:{CorrelationId}] [Op:{OperationId}] [U:{UserName}] {Message:lj}{NewLine}{Exception}"
                }
              }
            ],
            "Filter": [
              {
                "Name": "ByIncludingOnly",
                "Args": {
                  "expression": "RequestPath like '%/api/health'"
                }
              }
            ]
          }
        }
      },
      {
        "Name": "Logger",
        "Args": {
          "configureLogger": {
            "MinimumLevel": "Debug",
            "WriteTo": [
              {
                "Name": "File",
                "Args": {
                  "path": "log\\SampleMy-.txt", //Write some log in different file. Control through code 
                  "rollingInterval": "Day",
                  "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [CorrId:{CorrelationId}] [Op:{OperationId}] [U:{UserName}] {Message:lj}{NewLine}{Exception}"
                }
              }
            ],
            "Filter": [
              {
                "Name": "ByIncludingOnly",
                "Args": {
                  "expression": "SourceContext = 'MyProject.IMyLogger'"
                }
              }
            ]
          }
        }
      },
      {
        "Name": "Logger",
        "Args": {
          "configureLogger": {
            "MinimumLevel": "Information",
            "WriteTo": [
              {
                "Name": "File",
                "Args": {
                  "path": "log\\Sample-.txt", //all logs, without health-check
                  "rollingInterval": "Day",
                  "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [CorrId:{CorrelationId}] [Op:{OperationId}] [U:{UserName}] {Message:lj}{NewLine}{Exception}"
                }
              }
            ],
            "Filter": [
              {
                "Name": "ByExcluding",
                "Args": {
                  "expression": "RequestPath like '%/api/health'"
                }
              }
            ]
          }
        }
      }
    ],
    "Enrich": [
      "WithProcessName"
    ],
    "Properties": {
      "Application": "Sample",
      "Environment": "Test"
    }
  }

public class MyCommandHandler : IRequestHandler<MyCommand, Unit>
{
    private readonly ILogger _myLogger;
    private static int _count;

    public MyCommandHandler()
    {
        _myLogger = Log.ForContext<IMyLogger>();
    }

    public async Task<Unit> Handle(MyCommand request, CancellationToken cancellationToken)
    {
        _count++;

        Log.Debug("MyCommandHandler Count call = {count}",_count ); //write sample.txt
        Log.Information("MyCommandHandler Count call = {count}",_count ); //write in sample.txt
        Log.Error("MyCommandHandler Count call = {count}",_count); //write in sample.txt

        _myLogger.Information("Log from IMyLogger", _count); //write in sample.txt and in sampleMy.txt

        return Unit.Value;
    }
}

Upvotes: 3

TuomasK
TuomasK

Reputation: 1899

I found the solution. Created separate sections to appsettings.json, ErrorLog and AuditLog.

  "ErrorLog": {
    "Using": [ "Serilog.Sinks.File" ],
    "MinimumLevel": "Debug",
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "c:\\temp\\error-.log",
          "rollingInterval": "Day",
          "restrictedToMinimumLevel": "Error"
        }
      }
    ]
  },
  "AuditLog": {
    "Using": [ "Serilog.Sinks.File" ],
    "MinimumLevel": "Debug",
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "c:\\temp\\audit-.log",
          "rollingInterval": "Day",
          "restrictedToMinimumLevel": "Information"
        }
      }
    ]
  }

Now I can create 2 separate loggers:

            var errorLogConfiguration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
                .AddEnvironmentVariables()
                .Build();

            var errorSection = errorLogConfiguration.GetSection("ErrorLog");
            var auditSection = errorLogConfiguration.GetSection("AuditLog");

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

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

Which suits my need better.

Upvotes: 8

Pretasoc
Pretasoc

Reputation: 1126

I made some tests on this issue. The good news is your configuration works. Your error files probably isn't created, because no error is logged.

Serilog will create the log file, when the first message is logged into that file. You can confirm this if you run this simple program and (un)comment the logging of an error.

class Program
{
    static void Main(string[] args)
    {
        var errorLogConfiguration = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("settings.json", optional: false, reloadOnChange: true)
            .Build();

        var log = new LoggerConfiguration()
            .ReadFrom
            .Configuration(errorLogConfiguration)
            .CreateLogger();
        log.Warning("Warning");
        log.Error("Error");
    }
}

Upvotes: -1

Related Questions