Created
February 12, 2025 08:59
-
-
Save otto-gebb/c6c963c7d77ea02aed3f442ae4fddb0a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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