sentry-dotnet
sentry-dotnet copied to clipboard
Unhandled exceptions in generic host don't have breadcrumbs
Package
Sentry.Serilog
.NET Flavor
.NET
.NET Version
6.0.7
OS
Linux
SDK Version
3.20.0
Self-Hosted Sentry Version
No response
Steps to Reproduce
I'm integrating Sentry into a dotnet C# console app that uses Serilog and generic host.
To reproduce:
- See snippet below for a simple example app
- Set up your DSN in code
- Install dependencies, run it.
Here's relevant code that shows how app is all hooked up:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Sentry;
using Serilog;
using (var host = CreateHostBuilder().Build())
{
await host.StartAsync();
var lifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();
// Start Martini CLI
var martiniCLI = host.Services.GetRequiredService<MartiniCLI>();
await martiniCLI.RunAsync(lifetime.ApplicationStopping);
// Once completed, automatically stop the application
lifetime.StopApplication();
await host.WaitForShutdownAsync();
}
static IHostBuilder CreateHostBuilder() =>
new HostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseConsoleLifetime()
.ConfigureHostConfiguration(hostConfigBuilder => hostConfigBuilder.AddEnvironmentVariables(prefix: "DOTNET_"))
.ConfigureServices(services => services.AddTransient<MartiniCLI>())
.ConfigureLogging((_, logging) =>
{
logging.AddSimpleConsole().SetMinimumLevel(LogLevel.Debug);
// Configure sentry logger
var sentryLogger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Sentry(debug: true,
minimumBreadcrumbLevel: Serilog.Events.LogEventLevel.Debug,
environment: "Development",
dsn: "")
.CreateLogger();
logging.AddSerilog(sentryLogger);
});
class MartiniCLI
{
readonly ILogger<MartiniCLI> _logger;
public MartiniCLI(ILogger<MartiniCLI> logger)
{
_logger = logger;
}
public async Task RunAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Start");
_logger.LogDebug("Debug log");
SentrySdk.AddBreadcrumb("Manually added breadcrumb", "info", level: BreadcrumbLevel.Debug);
_logger.LogInformation("Delay for 5 seconds");
await Task.Delay(5000, cancellationToken);
_logger.LogInformation("Delay finished");
throw new Exception("Unhandled exception!");
// _logger.LogError("Fake error!");
}
}
Expected Result
I would expect errors (both unhandled exceptions and _logger.LogError) to be logged in Sentry with breadcrumbs.
Actual Result
Errors logged manually using _logger.LogError are logged in Sentry with breadcrumbs. Unhandled exceptions are logged in Sentry but without breadcrumbs.
Thanks for the report and the repro. We'll look into this and get back to you asap. Thanks.
I can reproduce this using the code you provided. I'm still looking into the cause. Thanks for your patience.
Still investigating, but I have eliminated Serilog and can reproduce with just a generic worker host.
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
<PackageReference Include="Sentry.Extensions.Logging" Version="3.20.1" />
</ItemGroup>
</Project>
using MyWorkerService;
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services => { services.AddHostedService<Worker>(); })
.ConfigureLogging(builder =>
{
builder.AddSentry(o =>
{
o.Debug = true;
o.Dsn = "...";
});
})
.Build();
await host.RunAsync();
using Sentry;
namespace MyWorkerService;
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private readonly IHub _hub;
public Worker(ILogger<Worker> logger, IHub hub)
{
_logger = logger;
_hub = hub;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Logger generated breadcrumb");
_hub.AddBreadcrumb("Manually added breadcrumb");
throw new Exception("Unhandled exception in worker");
// _hub.CaptureException(new Exception("Manually captured exception in worker"));
}
}
The unhandled exception comes through, but not the breadcrumbs.
Swap the last lines to capture an exception instead, and the breadcrumbs are present.
The same thing in a plain console app without the generic host works fine.
Just to add a workaround, you can try/catch around the entire worker logic and manually capture the exception. If doing so, you'll want to flag it as "unhandled" like this:
using Sentry.Protocol;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
await DoExecuteAsync(stoppingToken);
}
catch (Exception ex)
{
ex.Data[Mechanism.HandledKey] = false;
ex.Data[Mechanism.MechanismKey] = "Worker.UnhandledException";
_hub.CaptureException(ex);
}
}
private Task DoExecuteAsync(CancellationToken stoppingToken)
{
// ... the actual worker code
}
Or in the originally reported code without changing anything else:
using Sentry.Protocol;
public async Task RunAsync(CancellationToken cancellationToken)
{
try
{
await DoRunAsync(cancellationToken);
}
catch (Exception ex)
{
ex.Data[Mechanism.HandledKey] = false;
ex.Data[Mechanism.MechanismKey] = "Worker.UnhandledException";
SentrySdk.CaptureException(ex);
}
}
private async Task DoRunAsync(CancellationToken cancellationToken)
{
// ... the actual worker code
}
Since there's a viable workaround, I suggest using that for now until we can get a better fix in place.
Thanks.