ASP.Net Core Logging
See docs.asp.net
Nuget packages
- "Microsoft.Extensions.Logging"
- One or more loggers:
- "Microsoft.Extensions.Logging.Console" - console logging.
- "Microsoft.Extensions.Logging.EventLog" - windows events.
- Another CoreFx-compatible logger (Serilog is one: Serilog.Framework.Logging)
Startup
- Startup.Configure: loggerFactory.AddConsole(); or loggerFactory.AddSerilog()
Or configure log filtering eg loggerFactory.AddConsole(LogLevel.Debug) (default is Verbose)
For file logging, take IHostingEnvironment and use the hostEnv.ContentRootPath - You can log in Configure - take argument ILoggerFactory. Use it after AddConsole.
Use
- Either- take argument ILoggerFactory and created a named logger
var logger = loggerFactory.CreateLogger("Catchall Endpoint");
//Debug-Verbose (default)-Information-Warning-Error-Critical
logger.LogWarning("Something happened {path}", context.Request.Path); - Or = take argument ILogger<T> where T is probably your classname.
Unit Tests
In a test you will need to provide an ILogger<X>. Provide it with NullLogger<X>.Instance
var validator = new X(NullLogger<X>.Instance);
NLog
Sample NLog setup in .net 10 Program.cs
using System.Diagnostics;
using NLog;
using NLog.Web;
// dotnet add package NLog.Web.AspNetCore
// dotnet add package NLog.Targets.Trace
// load the NLog configuration from the appsettings.json file
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Logging.ClearProviders();
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
builder.Host.UseNLog();
// Add NLog as a trace listener
Trace.Listeners.Add(new NLogTraceListener());
builder.Services.AddControllers();
builder.Services.AddOpenApi();
var app = builder.Build();
logger.Info("Application started.");
using NLog;
using NLog.Web;
// dotnet add package NLog.Web.AspNetCore
// dotnet add package NLog.Targets.Trace
// load the NLog configuration from the appsettings.json file
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Logging.ClearProviders();
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
builder.Host.UseNLog();
// Add NLog as a trace listener
Trace.Listeners.Add(new NLogTraceListener());
builder.Services.AddControllers();
builder.Services.AddOpenApi();
var app = builder.Build();
logger.Info("Application started.");
Global exception handling
Use middleware. In Program.cs (before authorisation here, as we want to capture 403 auth errors):
app.UseMiddleware<RequestLoggingMiddleware>();
app.UseAuthorization();
app.MapControllers();
app.Run();
app.UseAuthorization();
app.MapControllers();
app.Run();
The middleware class:
using System.Text.Json;
namespace WebApi1;
public class RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
public async Task InvokeAsync(HttpContext context)
{
var request = context.Request;
//log all calls to the API with method and path
logger.LogInformation("Request {Method} {Path}", request.Method, request.Path);
try
{
await next(context);
//if an authentication error occurs, log it as a warning
if (context.Response.StatusCode >= StatusCodes.Status400BadRequest &&
context.Response.StatusCode < StatusCodes.Status500InternalServerError)
{
//401+403 could be pen testing attempts, 404 probing, so log them as warnings
logger.LogWarning(
"HTTP {StatusCode} for {Method} {Path}",
context.Response.StatusCode,
request.Method,
request.Path);
}
}
catch (Exception ex)
{
//if an unhandled exception occurs
var clientId = context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
var username = context.User?.Identity?.IsAuthenticated == true ? context.User?.Identity?.Name : "anonymous";
logger.LogError(ex,
$"Exception {request.Method} {request.Path} client={clientId} user={username}");
//throw; //preserve original exception stack trace
//optional - here we return a generic error response
context.Response.Clear();
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/json";
var payload = new
{
error = "An unexpected error occurred.",
statusCode = StatusCodes.Status500InternalServerError
};
await context.Response.WriteAsync(JsonSerializer.Serialize(payload));
}
}
}
namespace WebApi1;
public class RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger)
{
public async Task InvokeAsync(HttpContext context)
{
var request = context.Request;
//log all calls to the API with method and path
logger.LogInformation("Request {Method} {Path}", request.Method, request.Path);
try
{
await next(context);
//if an authentication error occurs, log it as a warning
if (context.Response.StatusCode >= StatusCodes.Status400BadRequest &&
context.Response.StatusCode < StatusCodes.Status500InternalServerError)
{
//401+403 could be pen testing attempts, 404 probing, so log them as warnings
logger.LogWarning(
"HTTP {StatusCode} for {Method} {Path}",
context.Response.StatusCode,
request.Method,
request.Path);
}
}
catch (Exception ex)
{
//if an unhandled exception occurs
var clientId = context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
var username = context.User?.Identity?.IsAuthenticated == true ? context.User?.Identity?.Name : "anonymous";
logger.LogError(ex,
$"Exception {request.Method} {request.Path} client={clientId} user={username}");
//throw; //preserve original exception stack trace
//optional - here we return a generic error response
context.Response.Clear();
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/json";
var payload = new
{
error = "An unexpected error occurred.",
statusCode = StatusCodes.Status500InternalServerError
};
await context.Response.WriteAsync(JsonSerializer.Serialize(payload));
}
}
}