如何为Entity Framework Core的API控制器实现日志自动化?
Hey there! I totally get how tedious it is to repeat logging code across every controller action—especially when you can’t use a base controller since you’re inheriting from ControllerBase. Let’s look at some clean, automated solutions that will eliminate that repetitive work:
1. Use an Action Filter (Best for Controller-Specific Logging)
Action filters let you intercept the execution of controller actions, so you can add logging logic once and apply it to all (or selected) controllers/actions. This is perfect for your API controller workflow.
Step 1: Create a Custom Action Filter
This filter will log when an action starts, completes successfully, or fails, and automatically capture controller/action names, parameters, execution time, and status codes:
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; using System.Diagnostics; public class ActionLoggingFilter : IActionFilter { private readonly ILogger<ActionLoggingFilter> _logger; private readonly Stopwatch _stopwatch = new(); public ActionLoggingFilter(ILogger<ActionLoggingFilter> logger) { _logger = logger; } public void OnActionExecuting(ActionExecutingContext context) { // Capture request details before the action runs var controllerName = context.RouteData.Values["controller"]?.ToString(); var actionName = context.RouteData.Values["action"]?.ToString(); var parameters = string.Join(", ", context.ActionArguments.Select(kv => $"{kv.Key}={kv.Value}")); _logger.LogInformation("Starting action: {Controller}.{Action} | Parameters: {Parameters}", controllerName, actionName, parameters); _stopwatch.Start(); } public void OnActionExecuted(ActionExecutedContext context) { _stopwatch.Stop(); var controllerName = context.RouteData.Values["controller"]?.ToString(); var actionName = context.RouteData.Values["action"]?.ToString(); var statusCode = context.HttpContext.Response.StatusCode; var elapsedMs = _stopwatch.ElapsedMilliseconds; // Handle success vs failure cases if (context.Exception != null) { _logger.LogError(context.Exception, "Action {Controller}.{Action} failed | Elapsed: {ElapsedMs}ms | Status Code: {StatusCode}", controllerName, actionName, elapsedMs, statusCode); } else { // Check for custom business logs (like your "station not found" message) if (context.HttpContext.Items.TryGetValue("BusinessLog", out var businessMessage)) { _logger.LogInformation("Action {Controller}.{Action} completed | Elapsed: {ElapsedMs}ms | Status Code: {StatusCode} | Details: {BusinessMessage}", controllerName, actionName, elapsedMs, statusCode, businessMessage); } else { _logger.LogInformation("Action {Controller}.{Action} completed successfully | Elapsed: {ElapsedMs}ms | Status Code: {StatusCode}", controllerName, actionName, elapsedMs, statusCode); } } } }
Step 2: Register the Filter Globally
Add this to your Program.cs to apply the filter to all controller actions:
builder.Services.AddControllers(options => { options.Filters.Add<ActionLoggingFilter>(); });
Or, if you want to apply it selectively, turn it into an attribute:
public class LogActionAttribute : TypeFilterAttribute { public LogActionAttribute() : base(typeof(ActionLoggingFilter)) { } }
Then use it on controllers or individual actions:
[Route("api/[controller]")] [ApiController] [LogAction] // Applies to all actions in this controller public class PetrolStationController : ControllerBase { // ... your existing controller code }
Step 3: Add Custom Business Logs (If Needed)
If you still need to log specific business details (like "stationID does not exist"), you can pass that info to the filter using HttpContext.Items:
[HttpGet("GetStationNodeObject/{stationId}")] public async Task<ActionResult<StationList>> GetStationLocation(Guid stationID) { var stationList = await _context.StationList.FindAsync(stationID); if (stationList == null) { // Pass custom message to the filter HttpContext.Items["BusinessLog"] = $"Station ID {stationID} does not exist"; return NotFound(); } var petrolStation = await _stationService.GetStationLocation(stationID); return petrolStation; }
2. Use Middleware (For Global Request Logging)
If you want to log all incoming HTTP requests (not just controller actions), middleware is a great option. It sits in the request pipeline and can log every request/response pair.
Create the Middleware
using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using System.Diagnostics; public class RequestLoggingMiddleware { private readonly RequestDelegate _next; private readonly ILogger<RequestLoggingMiddleware> _logger; public RequestLoggingMiddleware(RequestDelegate next, ILogger<RequestLoggingMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { var stopwatch = Stopwatch.StartNew(); var controllerName = context.Request.RouteValues["controller"]?.ToString(); var actionName = context.Request.RouteValues["action"]?.ToString(); var requestPath = context.Request.Path; var requestMethod = context.Request.Method; try { _logger.LogInformation("Received request: {Method} {Path} | Controller: {Controller}, Action: {Action}", requestMethod, requestPath, controllerName, actionName); // Pass the request to the next middleware/controller await _next(context); stopwatch.Stop(); _logger.LogInformation("Request completed: {Method} {Path} | Status Code: {StatusCode} | Elapsed: {ElapsedMs}ms", requestMethod, requestPath, context.Response.StatusCode, stopwatch.ElapsedMilliseconds); } catch (Exception ex) { stopwatch.Stop(); _logger.LogError(ex, "Request failed: {Method} {Path} | Controller: {Controller}, Action: {Action} | Elapsed: {ElapsedMs}ms", requestMethod, requestPath, controllerName, actionName, stopwatch.ElapsedMilliseconds); throw; } } }
Register the Middleware
Add this to Program.cs (make sure it’s between UseRouting and UseEndpoints):
app.UseRouting(); // Add the logging middleware here app.UseMiddleware<RequestLoggingMiddleware>(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
Key Tips for Better Logging
- Use Structured Logging: Instead of concatenating strings (like your original
Message), use the placeholder syntax ({Controller},{Action}) so log systems (like Serilog, Application Insights) can parse and filter logs easily. - Avoid Redundant Data: The filter/middleware automatically captures controller/action names, so you don’t need to set a
Controllerproperty manually. - Centralize Log Configuration: Configure your logger (e.g., in
Program.cs) to output to the right destinations (console, files, cloud services) once, instead of per controller.
内容的提问来源于stack exchange,提问作者SkyeBoniwell

