In a production ASP.NET Core Web API, two categories of logs are especially important: HTTP request logs (which endpoint was called, how long it took, what status code was returned) and performance logs (slow database queries, slow service calls). Without these, troubleshooting performance regressions and error spikes requires guesswork. Serilog’s UseSerilogRequestLogging middleware provides concise, structured request logs, and standard Stopwatch-based logging patterns cover performance measurement.
Serilog Request Logging
// โโ Replace verbose ASP.NET Core request logging with Serilog's concise version โโ
// Without UseSerilogRequestLogging, ASP.NET Core logs 4+ lines per request.
// With it: one structured line per request with all relevant properties.
// Program.cs โ add BEFORE MapControllers
app.UseSerilogRequestLogging(opts =>
{
// Include response body size, endpoint name, user
opts.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme);
diagnosticContext.Set("UserAgent", httpContext.Request.Headers.UserAgent.ToString());
diagnosticContext.Set("UserId", httpContext.User.Identity?.Name ?? "anonymous");
};
// Only log requests that take more than 200ms as Warning
opts.GetLevel = (httpContext, elapsed, ex) =>
ex != null ? LogEventLevel.Error
: elapsed > 500 ? LogEventLevel.Warning
: LogEventLevel.Information;
});
// Output: "HTTP GET /api/posts/42 responded 200 in 45.2 ms" + all enriched properties
// Each entry includes: Method, Path, StatusCode, Elapsed, RequestHost, UserId, etc.
UseSerilogRequestLogging suppresses the verbose ASP.NET Core framework request logs by default (the multiple per-request Microsoft.AspNetCore.Hosting.HttpRequestIn and HttpRequestOut entries). This is usually desired โ Serilog’s single structured line is more useful than the framework’s multiple lines. To keep both, set opts.MessageTemplate and do not suppress the Microsoft log categories in your minimum level overrides.GetLevel delegate to log slow requests at a higher level. Requests completing in under 200ms are routine โ Information level is appropriate. Requests taking 500msโ2s may indicate a performance issue โ Warning level gets attention without alarm. Requests over 5 seconds indicate a definite problem โ Error level. This tiered approach means fast requests do not flood your logs, while slow requests are immediately visible in dashboards that monitor Warning and Error counts.Performance Logging with Stopwatch
// โโ Measure and log operation duration โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
public async Task<IReadOnlyList<Post>> GetPublishedAsync(int page, int size, CancellationToken ct)
{
var sw = Stopwatch.StartNew();
try
{
var posts = await _repo.GetPublishedAsync(page, size, ct);
sw.Stop();
if (sw.ElapsedMilliseconds > 500)
_logger.LogWarning(
"Slow query: GetPublishedAsync page={Page} size={Size} took {ElapsedMs}ms",
page, size, sw.ElapsedMilliseconds);
else
_logger.LogDebug(
"GetPublishedAsync page={Page} size={Size} completed in {ElapsedMs}ms",
page, size, sw.ElapsedMilliseconds);
return posts;
}
catch (Exception ex)
{
sw.Stop();
_logger.LogError(ex,
"GetPublishedAsync failed after {ElapsedMs}ms", sw.ElapsedMilliseconds);
throw;
}
}
// โโ High-performance LoggerMessage (eliminates allocation per call) โโโโโโโโ
public static partial class LogMessages
{
[LoggerMessage(Level = LogLevel.Warning,
Message = "Slow query: {Operation} took {ElapsedMs}ms")]
public static partial void SlowQuery(ILogger logger, string operation, long elapsedMs);
[LoggerMessage(Level = LogLevel.Information,
Message = "User {UserId} authenticated from {IpAddress}")]
public static partial void UserAuthenticated(ILogger logger, string userId, string ipAddress);
}
Common Mistakes
Mistake 1 โ Logging request bodies containing sensitive data
โ Wrong โ logging the full request body exposes passwords and personal data in logs.
โ Correct โ never log request/response bodies by default; add controlled debug logging if needed.
Mistake 2 โ Using string interpolation in LoggerMessage attributes
โ Wrong โ compile error: LoggerMessage messages must be compile-time constants.
โ Correct โ use named placeholders in the Message attribute; values passed as method parameters.