Skip to content

Instantly share code, notes, and snippets.

@otto-gebb
Created February 12, 2025 08:59
Show Gist options
  • Save otto-gebb/c6c963c7d77ea02aed3f442ae4fddb0a to your computer and use it in GitHub Desktop.
Save otto-gebb/c6c963c7d77ea02aed3f442ae4fddb0a to your computer and use it in GitHub Desktop.
/*
When `LogOperation1` logs the exception, the log record does not contain the properties
of the logging scope that were active at the moment of throwing.
`LogOperation2` has no such issue.
Demo:
❯ dotnet run | grep '^{' | jq 'del(.["@t"], .["@i"], .["@l"], .["@x"], .SourceContext)'
{
"@m": "Starting operation: \"Fail1\"",
"OperationName": "Fail1"
}
{
"@m": "Operation failed: \"Fail1\"",
"OperationName": "Fail1"
}
{
"@m": "Starting operation: \"Fail2\"",
"OperationName": "Fail2"
}
{
"@m": "Operation failed: \"Fail2\"",
"OperationName": "Fail2",
"inner": 42
}
The trick with logging in an exception filter (`when` clause) is used by Serilog, see
https://github.com/serilog/serilog-aspnetcore/blob/dc1181cea495194d86f7ea54c42298d1f805b529/src/Serilog.AspNetCore/AspNetCore/RequestLoggingMiddleware.cs#L69
... but somehow not by Microsoft
https://github.com/dotnet/aspnetcore/blob/81b289460a9ce5fcbd0d32d80a645f139dffc422/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddlewareImpl.cs#L103
*/
using Serilog;
using Serilog.Formatting.Compact;
using ILogger = Microsoft.Extensions.Logging.ILogger;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<MyService>();
builder.Host.UseSerilog((services, lc) => lc
.Enrich.FromLogContext()
.WriteTo.Console(new RenderedCompactJsonFormatter()));
var app = builder.Build();
var myService = app.Services.GetRequiredService<MyService>();
try { myService.Fail1(); } catch{}
try { myService.Fail2(); } catch{}
public class MyService {
private readonly ILogger<MyService> logger;
public MyService(ILogger<MyService> logger)
{
this.logger = logger;
}
public int Fail1() => LogHelper.LogOperation1(
logger,
() => {
using var _ = logger.BeginScope(new Dictionary<string, object> {["inner"] = 42});
throw new Exception("Fail 1");
return 1;
}
);
public int Fail2() => LogHelper.LogOperation2(
logger,
() => {
using var _ = logger.BeginScope(new Dictionary<string, object> {["inner"] = 42});
throw new Exception("Fail 2");
return 1;
}
);
}
public static class LogHelper {
public static T LogOperation1<T>(
ILogger logger,
Func<T> action,
[System.Runtime.CompilerServices.CallerMemberName] string operationName = "")
{
try
{
logger.LogInformation("Starting operation: {OperationName}", operationName);
var result = action();
logger.LogInformation("Operation completed: {OperationName}", operationName);
return result;
}
catch (Exception ex)
{
logger.LogError(ex, "Operation failed: {OperationName}", operationName);
throw;
}
}
public static T LogOperation2<T>(
ILogger logger,
Func<T> action,
[System.Runtime.CompilerServices.CallerMemberName] string operationName = "")
{
bool LogException(Exception ex)
{
logger.LogError(ex, "Operation failed: {OperationName}", operationName);
return false;
}
try
{
logger.LogInformation("Starting operation: {OperationName}", operationName);
var result = action();
logger.LogInformation("Operation completed: {OperationName}", operationName);
return result;
}
catch (Exception ex) when (LogException(ex))
{
throw;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment