Overview
This guide covers integrating log4net logging into .NET Core applications, from basic setup to advanced implementation patterns.
Getting Started
Prerequisites
Install the required NuGet packages for logging functionality:
Microsoft.Extensions.Logging.Log4Net.AspNetCore
log4net
Configuring the Application
Enable log4net in the aplication startup file:
builder.Logging.AddLog4Net();
Log Configuration File
Create a log4net.config file in your project root:
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<appender name="ConsoleOutput" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
</layout>
</appender>
<appender name="FileOutput" type="log4net.Appender.RollingFileAppender">
<file value="logs/" />
<appendToFile value="true" />
<param name="MaxSizeRollBackups" value="10"/>
<param name="maximumFileSize" value="3MB" />
<param name="RollingStyle" value="Date" />
<param name="DatePattern" value=""app_"yyyyMMdd".log"" />
<param name="StaticLogFileName" value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %5level %logger.%method [%line] - MESSAGE: %message%newline %exception" />
</layout>
</appender>
<root>
<level value="ALL"/>
<appender-ref ref="ConsoleOutput" />
<appender-ref ref="FileOutput" />
</root>
</log4net>
Configuraton Details
- ConsoleOutput: Writes logs to the debug console
- FileOutput: Writes logs to rotating files in the logs directory
- MaxSizeRollBackups: Retains up to 10 backup files
- maximumFileSize: Each log file limited to 3MB
- DatePattern: Log files named by date format (app_20230431.log)
Global Exception Handling
Create a custom exception filter for capturing unhandled exceptions:
public class GlobalExceptionFilter : ExceptionFilterAttribute
{
private readonly ILogger<GlobalExceptionFilter> _log;
public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> log)
{
_log = log;
}
public override void OnException(ExceptionContext context)
{
var exception = context.Exception;
_log.LogError(exception, "Unhandled exception occurred: {Message}", exception.Message);
context.ExceptionHandled = true;
}
}
Advanced Logging Implementation
Domain Models
public class AuditLog
{
public int Id { get; set; }
public string ActionName { get; set; }
public string ControllerName { get; set; }
public string UserId { get; set; }
public DateTime ExecutedAt { get; set; }
public string RequestMethod { get; set; }
public string RequestPath { get; set; }
public int StatusCode { get; set; }
public string Details { get; set; }
}
Database Integration with EF Core
public class AppDbContext : DbContext
{
public DbSet<AuditLog> AuditLogs { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<AuditLog>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.ExecutedAt).HasDefaultValueSql("GETUTCDATE()");
});
}
}
Audit Service Implementation
public interface IAuditService
{
Task RecordActionAsync(string action, string controller, string userId, string method, string path, int statusCode, string details);
}
public class AuditService : IAuditService
{
private readonly AppDbContext _db;
private readonly ILogger<AuditService> _logger;
public AuditService(AppDbContext db, ILogger<AuditService> logger)
{
_db = db;
_logger = logger;
}
public async Task RecordActionAsync(string action, string controller, string userId, string method, string path, int statusCode, string details)
{
var entry = new AuditLog
{
ActionName = action,
ControllerName = controller,
UserId = userId,
RequestMethod = method,
RequestPath = path,
StatusCode = statusCode,
Details = details,
ExecutedAt = DateTime.UtcNow
};
try
{
_db.AuditLogs.Add(entry);
await _db.SaveChangesAsync();
_logger.LogInformation("Audit entry recorded for {User}", userId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to record audit entry");
}
}
}
Action Filter for Request Tracking
public class RequestAuditFilter : IActionFilter
{
private readonly IAuditService _auditService;
private readonly ILogger<RequestAuditFilter> _logger;
public RequestAuditFilter(IAuditService auditService, ILogger<RequestAuditFilter> logger)
{
_auditService = auditService;
_logger = logger;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogDebug("Executing {Controller}.{Action}",
context.RouteData.Values["controller"],
context.RouteData.Values["action"]);
}
public void OnActionExecuted(ActionExecutedContext context)
{
var userId = context.HttpContext.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "anonymous";
_auditService.RecordActionAsync(
context.RouteData.Values["action"]?.ToString(),
context.RouteData.Values["controller"]?.ToString(),
userId,
context.HttpContext.Request.Method,
context.HttpContext.Request.Path,
context.Result is ObjectResult result ? result.StatusCode ?? 200 : 200,
context.Exception?.Message
).Wait();
}
}
Registering Filters Globally
builder.Services.AddControllers(options =>
{
options.Filters.Add<RequestAuditFilter>();
options.Filters.Add<GlobalExceptionFilter>();
});