.NET applications throw exceptions all the time — a database timeout in background work, a validation error halfway through a request, a third-party API that fails at 3 a.m. The difference between chaos and control is whether you hear about these crashes from a dashboard or from a support ticket. Dotnet error tracking ensures that every unhandled exception lands in a grouped, actionable issue feed instead of lost in your logs.
This guide shows you how to set up production-grade error tracking in an ASP.NET Core application using the Sentry SDK, pointed at LightTrace. By the end, you'll capture controller errors, middleware exceptions, and background tasks with full stack traces, request context, and the exact release that introduced the regression.
Install and initialize the SDK
Start by adding the Sentry SDK to your project:
dotnet add package Sentry.AspNetCore
Then initialize it early in your Program.cs (or Startup.cs if you're on an older ASP.NET Core version). The key is to call UseSentry() before you build the host:
using Sentry.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
// Initialize Sentry first, before adding services
builder.WebHost.UseSentry(options =>
{
options.Dsn = "https://<key>@your-lighttrace-host/1";
options.Environment = builder.Environment.EnvironmentName;
options.Release = "api@1.0.0";
options.TracesSampleRate = 1.0;
});
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();
That single initialization installs global handlers for uncaught exceptions and integrates with ASP.NET Core's middleware pipeline. From this point forward, any unhandled exception automatically reports to LightTrace.
Because LightTrace speaks the Sentry protocol, you use the standard Sentry.AspNetCore package and change only the Dsn. No compatibility layer, no vendor lock-in — just point it at LightTrace instead of Sentry's servers.
Capture controller and middleware exceptions
ASP.NET Core's dependency injection and middleware model makes exception handling automatic once the SDK is initialized. Here's a typical controller:
using Sentry;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly OrderService _orderService;
private readonly IHub _sentryHub;
public OrdersController(OrderService orderService, IHub sentryHub)
{
_orderService = orderService;
_sentryHub = sentryHub;
}
[HttpPost]
public async Task<ActionResult<OrderDto>> CreateOrder([FromBody] CreateOrderRequest request)
{
try
{
var order = await _orderService.CreateAsync(request);
return Ok(order);
}
catch (ValidationException ex)
{
// Log and re-throw; the exception middleware will capture it
_sentryHub.CaptureException(ex);
return BadRequest(ex.Message);
}
}
}
When an exception escapes a controller action, ASP.NET Core's middleware catches it and reports it to LightTrace automatically. You don't need a global exception handler — the SDK hooks into the request pipeline.
However, for fine-grained control (like adding custom context for certain exceptions), you can use the injected IHub to capture explicitly:
try
{
await _paymentService.ChargeAsync(order.Total, request.PaymentMethod);
}
catch (PaymentException ex)
{
_sentryHub.CaptureException(ex, scope =>
{
scope.SetTag("payment_provider", ex.Provider);
scope.SetTag("transaction_id", ex.TransactionId);
scope.Level = SentryLevel.Error;
});
throw;
}
Inject IHub into your services and call CaptureException() for errors you want to log but still handle gracefully. This is cleaner than using Sentry.SentrySdk.CaptureException() globally.
Add request context and breadcrumbs
Raw stack traces are a start. The debugging speed comes from context — who hit the error, which release, what were they doing?
Set the user in a middleware so every request captures user info:
app.Use(async (context, next) =>
{
var hub = context.RequestServices.GetRequiredService<IHub>();
if (context.User?.Identity?.IsAuthenticated == true)
{
hub.ConfigureScope(scope =>
{
scope.User = new SentryUser
{
Id = context.User.FindFirst("sub")?.Value,
Email = context.User.FindFirst("email")?.Value,
Username = context.User.FindFirst("name")?.Value,
};
});
}
await next.Invoke();
});
Now every error knows which user hit it. Add breadcrumbs at key moments to trace the path:
public async Task<OrderDto> CreateAsync(CreateOrderRequest request)
{
var hub = SentrySdk.CurrentHub;
hub.AddBreadcrumb(message: "Order creation started", category: "checkout");
var customer = await _db.Customers.FirstAsync(c => c.Id == request.CustomerId);
hub.AddBreadcrumb(message: "Customer loaded", category: "checkout", level: BreadcrumbLevel.Debug);
var inventory = await _inventoryService.CheckAvailabilityAsync(request.Items);
hub.AddBreadcrumb(message: "Inventory checked", category: "checkout", level: BreadcrumbLevel.Debug);
var order = new Order
{
CustomerId = request.CustomerId,
Items = request.Items,
Total = inventory.CalculateTotal(),
CreatedAt = DateTime.UtcNow,
};
await _db.Orders.AddAsync(order);
await _db.SaveChangesAsync();
hub.AddBreadcrumb(message: "Order saved to database", category: "checkout");
return MapToDto(order);
}
When an error occurs, you see the exact sequence of events that led to it — customer loaded, inventory checked, database saved — instead of just a stack trace and a prayer.
Handle background jobs and services
Background tasks don't run in an HTTP request context, so they need explicit setup. If you're using a hosted service or a queue library like Hangfire:
public class EmailNotificationService : BackgroundService
{
private readonly IHub _sentryHub;
private readonly EmailQueue _queue;
public EmailNotificationService(IHub sentryHub, EmailQueue queue)
{
_sentryHub = sentryHub;
_queue = queue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
var email = await _queue.DequeueAsync();
if (email != null)
{
_sentryHub.ConfigureScope(scope =>
{
scope.SetTag("job_type", "email");
scope.SetTag("recipient", email.To);
});
await SendEmailAsync(email);
}
}
catch (Exception ex)
{
_sentryHub.CaptureException(ex);
// Log and retry
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
}
}
private async Task SendEmailAsync(Email email)
{
try
{
await _smtpClient.SendMailAsync(email.ToMailMessage());
_sentryHub.AddBreadcrumb(message: $"Email sent to {email.To}", category: "email");
}
catch (SmtpException ex)
{
_sentryHub.CaptureException(ex, scope =>
{
scope.SetTag("email_recipient", email.To);
});
throw;
}
}
}
Tag background jobs with their type and relevant IDs so you can later group errors by which job is flaky.
Tag errors by release and environment
One of the fastest ways to debug regressions is to know which release introduced them. Always tag events with a release version:
builder.WebHost.UseSentry(options =>
{
options.Dsn = "https://<key>@your-lighttrace-host/1";
options.Environment = builder.Environment.EnvironmentName;
// Read from environment variable set during deployment
options.Release = Environment.GetEnvironmentVariable("APP_VERSION") ?? "development";
options.TracesSampleRate = 1.0;
});
In your CI/CD pipeline, set APP_VERSION to your actual build number:
docker build --build-arg VERSION=2.1.0 -t myapp:2.1.0 .
docker run -e APP_VERSION=2.1.0 myapp:2.1.0
Now when an error appears in LightTrace, you can immediately see which releases were affected. If a regression hits after deploying version 2.1.0, release health monitoring shows you the exact moment it started and helps you roll back with confidence.
Test your setup
To verify the SDK is working, add a test endpoint:
[HttpGet("test-error")]
public ActionResult TestError()
{
throw new Exception("Test error to verify Sentry integration is working");
}
Call GET /api/orders/test-error, and within seconds you should see the error grouped in LightTrace. If you don't:
- Verify your DSN is correct (copy it from your LightTrace project settings)
- Check that
UseSentry()was called inProgram.csbefore building the app - Ensure your ASP.NET Core environment is set (e.g.,
ASPNETCORE_ENVIRONMENT=Production)
Common exceptions like NullReferenceException (the .NET equivalent of the notorious JavaScript TypeError) will now surface immediately with a stack trace pointing to the exact line.
What's next
Error tracking is the first step. Error tracking best practices show you how to triage, prioritize, and close the loop fast. Set up alert rules to page your team on new errors or spikes, and use distributed tracing to connect errors across your microservices if you're running multiple .NET services.
For a framework-agnostic overview of the setup pattern, see how to add error tracking to any app.
Start tracking errors in minutes
Point the Sentry SDK at LightTrace and see your first .NET errors grouped in minutes — free up to 5,000 events a month.
Error tracking in .NET is table stakes for production. Silent failures are worse than crashes because your team never knows they're happening. With the SDK initialized and middleware in place, every unhandled exception becomes a visible issue in your dashboard, and your team can fix it before a customer complains.