1

Serilog with ASP NET 5 Razor Pages.

Reducing log verbosity is very useful for Informational logs.

However for debug logs, how to get a MinimumLevel.Override("Microsoft.AspNetCore") to be specific to a debug file sink?

Creating 2 configurations could be a solution, but feels like something more elegant may be possible?

Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.Enrich.FromLogContext()

// for debug file sink I want the override to be Debug
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Debug)

.WriteTo.File("debug.txt", restrictedToMinimumLevel: LogEventLevel.Debug)

// for info and warning file sinks I want the override to be Warning
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)

.WriteTo.File("info.txt", restrictedToMinimumLevel: LogEventLevel.Information)
.WriteTo.File("warning.txt", restrictedToMinimumLevel: LogEventLevel.Warning)
.CreateLogger();

Everything works as expected using just one override. But not together.

In the example above the Warning override takes precedence and no AspNetCore Debug event logs are written to debug.txt

Edit

In summary, I'd like my debug log to include Information event level from Microsoft.AspNetCore and my info log file to include Warning event level from Microsoft.AspNetCore

enter image description here

I got the 2 logs files how I wanted by commenting out and in 1. and 2. below

// 1. for debug file sink I want AspNetCore.Information or Debug level override
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Information)
.WriteTo.File($@"{logFilePath}debugx.txt", restrictedToMinimumLevel: LogEventLevel.Debug, rollingInterval: RollingInterval.Day)

// 2. for info and warning file sinks below I want only AspNetCore warnings
//.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)

It's an interesting one

Dave Mateer
  • 6,588
  • 15
  • 76
  • 125

3 Answers3

1

You want to filter log data and want to populate into different file sinks.

For Example /Logs/Error/Errlog.txt and /Logs/Info/InfoLog.txt

You can achieve this by using Serilog.Expressions nuget package. If time permits, I will paste a working example here.

Serilog.Expressions sample from Serilog

https://github.com/serilog/serilog-expressions/blob/dev/example/Sample/Program.cs

In below example it will exclude Name=User line and only print second line on console

using var log = new LoggerConfiguration()
                                .Filter.ByExcluding("@m like 'Welcome!%' and Name = 'User'")
                                .WriteTo.Console()
                                .CreateLogger();
    // Logged normally
   log.Information("Welcome!, {Name}", "User");

   // Excluded by the filter
   log.Information("Welcome!, {Name}", "Domain\\UserName");

Here is the filtering example for \Logs\Info\Info-20210720.txt which filters Error, Fatal or Warning levels. More information here

 var exprInfo = "@l='Error' or @l='Fatal' or @l='Warning'";

            var loggerInfo = new LoggerConfiguration()                            
                             .WriteTo.File(
                                 @"C:\Temp\Logs\Info\Info-.txt",
                                 fileSizeLimitBytes: 1_000_000,
                                 outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level}] [{SourceContext}] [{EventId}] {Message}{NewLine}{Exception}",
                                 rollingInterval: RollingInterval.Day,
                                 rollOnFileSizeLimit: true,
                                 shared: true,
                                 flushToDiskInterval: TimeSpan.FromSeconds(1))
                            .MinimumLevel.Override("Microsoft", LogEventLevel.Debug)
                            .Filter.ByExcluding(exprInfo)
                            .CreateLogger();

  try
            {
                loggerInfo.Debug("TEST");

                SelfLog.Enable(Console.Out);

                var sw = System.Diagnostics.Stopwatch.StartNew();
              

                for (var i = 0; i < 100; ++i)
                {
                    loggerInfo.Information("Hello, file logger!>>>>>>{Count}", i);
                    loggerInfo.Information("Writing to log file with INFORMATION severity level.");

                    loggerInfo.Debug("Writing to log file with DEBUG severity level.");

                    loggerInfo.Warning("Writing to log file with WARNING severity level.");

                    loggerInfo.Error("Writing to log file with ERROR severity level.");

                    loggerInfo.Fatal("Writing to log file with CRITICAL severity level.");

                }



                sw.Stop();

                Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms");
                Console.WriteLine($"Size: {new FileInfo("log.txt").Length}");

                Console.WriteLine("Press any key to delete the temporary log file...");
                Console.ReadKey(true);
                
            }
            catch (Exception ex)
            {
                loggerInfo.Fatal(ex, "Application Start-up for Serilog failed");
                throw;
            }
            finally
            {
                Log.CloseAndFlush();
            }

enter image description here enter image description here

Chinmay T
  • 745
  • 1
  • 9
  • 17
  • Many thanks @ChinmayT! Although this didn't quite do it for me, you pointed me in the right direction and an interesting journey. I've posted a separate answer. – Dave Mateer Jul 22 '21 at 14:32
1

I solved it by using sub loggers and filters as described in here: How can I override Serilog levels differently for different sinks?

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()

    .Enrich.FromLogContext()

    // Includes Debug from Microsoft.AspNetCore (noisy)
    // useful for deep debugging
    .WriteTo.File($@"logs/debug.txt", rollingInterval: RollingInterval.Day)

    // Info-with-framework (useful for debugging)
    .WriteTo.Logger(lc => lc
        .MinimumLevel.Information()
        .Filter.ByExcluding("RequestPath in ['/health-check', '/health-check-db']")
        .WriteTo.File("logs/info-with-framework.txt", rollingInterval: RollingInterval.Day)
        .WriteTo.Console()
    )

    // Info
    // framework minimum level is Warning (normal everyday looking at logs)
    .WriteTo.Logger(lc => lc
        .MinimumLevel.Information()
        .Filter.ByExcluding("RequestPath in ['/health-check', '/health-check-db']")
        .Filter.ByExcluding("SourceContext = 'Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware'")
        .Filter.ByExcluding(logEvent =>
            logEvent.Level < LogEventLevel.Warning &&
            Matching.FromSource("Microsoft.AspNetCore").Invoke(logEvent))
        .WriteTo.File("logs/info.txt", rollingInterval: RollingInterval.Day))

    // Warning (bad things - Warnings, Error and Fatal)
    .WriteTo.Logger(lc => lc
        .MinimumLevel.Warning()
        // stopping duplicate stacktraces, see blog 2021/03/10/a11-serilog-logging-in-razor-pages
        .Filter.ByExcluding("SourceContext = 'Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware'")
        .WriteTo.File("logs/warning.txt", rollingInterval: RollingInterval.Day))


   // SignalR - tweak levels by filtering on these namespaces
   // Microsoft.AspNetCore.SignalR
   // Microsoft.AspNetCore.Http.Connections

   .CreateLogger();

Although this works, there may be a better way https://nblumhardt.com/2016/07/serilog-2-write-to-logger/

Dave Mateer
  • 6,588
  • 15
  • 76
  • 125
0

I feel like you don't need those minium level override calls. The restricted to minimum level parameter in the sinks will take are of filtering.

You do need to set the minimum level to info so the info sink can work.

LarryX
  • 591
  • 2
  • 7