static void

ASP.Net Core Logging

See docs.asp.net

Nuget packages

Startup

Use

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.");

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();

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));
        }
    }
}