6

I am using Serilog for logging in my .net 6 web api and would like to expose an endpoint to enable me to change the global logging level. This will enable me to switch the level via a front end toggle switch. This is what I have implemented so far but it does not seem to have any effect at all. Even just setting the logging level in the startup is not moving the default level off of "Information". I am using the .net DI to configure everything.

This is what I have so far:

Program.cs

var levelSwitcher = new LoggingLevelSwitch(LogEventLevel.Verbose);

builder.Services.AddSingleton<LoggingLevelSwitch>(levelSwitcher);

var seriLogger = new LoggerConfiguration()
            .MinimumLevel.ControlledBy(levelSwitcher)
            .ReadFrom.Configuration(builder.Configuration)
            .Enrich.FromLogContext()
            .CreateLogger();

Then in my endpoint controller

    private readonly LoggingLevelSwitch _levelSwitch;
    private readonly ILogger<DiagnosticsController> _logger;

    public DiagnosticsController(ILogger<DiagnosticsController> logger, LoggingLevelSwitch levelSwitch)
    {
        _logger = logger;
        _levelSwitch = levelSwitch;
    }

    [HttpGet(Name = "SwitchLoggingLevel")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public IActionResult SwitchLoggingLevel(string level)
    {
        if (Enum.TryParse(level, out Serilog.Events.LogEventLevel newLoggingLevel))
        {
            _levelSwitch.MinimumLevel = newLoggingLevel;
            return Ok();
        }

        return BadRequest();
    }

From what I can see in the debugger is that the logger is still on "Information" minimum level and even when call the endpoint and passing in a more or less verbose level nothing is having an effect.

My initial thoughts are that the way I am initializing the switcher in the DI environment is incorrect, but I am not sure on how to do it correctly (ie creating a service and then immediately using it in the subsequent config of the logger). But even if that is incorrect, the actual minimum level is not even being set to "Error" as configured.

halfer
  • 19,824
  • 17
  • 99
  • 186
Mark Redfern
  • 357
  • 1
  • 3
  • 24

2 Answers2

1

Check that you have configured Serilog correctly because the code works as expected.

Assign your variable seriLogger to Log.Logger or

Log.Logger = new LoggerConfiguration()
   .MinimumLevel.ControlledBy(levelSwitcher)
   .ReadFrom.Configuration(builder.Configuration)
   .Enrich.FromLogContext()
   .WriteTo.Console()
   .CreateLogger();

builder.Host.UseSerilog();

Note that you need to add WriteTo.Console() or any other sink.

Roman Marusyk
  • 23,328
  • 24
  • 73
  • 116
1

(Posted on behalf of the question author to move it to the answer space).

Whilst @Roman Marusyk didn't exactly solve the issue, he gave enough information to get me across the line. This is what I ended up with in my DI configuration:

public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        var levelSwitcher = new LoggingLevelSwitch(LogEventLevel.Warning);

        builder.Host.UseSerilog((ctx, lc) => lc
            .MinimumLevel.ControlledBy(levelSwitcher)
            .ReadFrom.Configuration(ctx.Configuration));

        // Add services to the container.
        builder.Services.TryAddSingleton(levelSwitcher);

        builder.Services.AddControllers();
        // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen();

        var app = builder.Build();


        // Configure the HTTP request pipeline.
        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }

        app.UseHttpsRedirection();

        app.UseAuthorization();

        app.UseSerilogRequestLogging();

        app.MapControllers();

        app.Run();
    }

This is still not production ready, but it works as expected now. I have an API endpoint that I can pass through a LogEventLevel as a string and it then gets applied to the Logger System. Here is my appSettings.json just for completeness:

{
"Logging": {
"LogLevel": {
  "Default": "Information",
  "Microsoft": "Warning",
  "Microsoft.Hosting.Lifetime": "Information"
}
  },
  "AllowedHosts": "*",
  "Serilog": {
"using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
"Override": {
  "Microsoft.AspNetCore": "Warning"
},
"WriteTo": [
  {
    "name": "Console"
  },
  {
    "Name": "File",
    "Args": {
      "buffered": false,
      "path": "./logs/data-logs.txt",
      "rollingInterval": "Day",
      "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
    }
  }
],
"Enrich": [
  "WithMachineName",
  "FromLogContext",
  "WithThreadId",
  "WithProcessId",
  "WithClientAgent",
  "WithClientIp"
]
}
}

And finally my controller that does the switch

public class DiagnosticsController : ControllerBase
{
    private readonly LoggingLevelSwitch _levelSwitch;
    private readonly ILogger<DiagnosticsController> _logger;

    public DiagnosticsController(ILogger<DiagnosticsController> logger, LoggingLevelSwitch levelSwitch)
    {
        _logger = logger;
        _levelSwitch = levelSwitch;
    }

    [HttpGet(Name = "SwitchLoggingLevel")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public IActionResult SwitchLoggingLevel(string level)
    {
        if (Enum.TryParse(level, out Serilog.Events.LogEventLevel newLoggingLevel))
        {
            _levelSwitch.MinimumLevel = newLoggingLevel;
            return Ok();
        }

        return BadRequest();
    }

    [HttpGet(Name = "GetCurrentLoggingLevel")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public IActionResult CurrentLoggingLevel()
    {
        return Ok(_levelSwitch.MinimumLevel.ToString());
    }
}
halfer
  • 19,824
  • 17
  • 99
  • 186