如何在ASP.NET Core中创建供BenchmarkFilter使用的CSV文件日志器?
实现自定义CSV日志器并注入到BenchmarkFilter
咱们一步一步来搞定这个需求,核心是要给BenchmarkFilter单独配置一个能输出CSV格式的日志实例,具体步骤如下:
1. 实现CSV格式的自定义日志提供程序
默认的日志组件没有直接支持CSV的选项,所以咱们得自己写一个专门生成CSV日志的提供程序和日志类。
首先创建CsvFileLoggerProvider,它负责创建日志实例并初始化CSV文件表头:
using Microsoft.Extensions.Logging; using System.IO; using System.Threading; public class CsvFileLoggerProvider : ILoggerProvider { private readonly string _filePath; private readonly object _lock = new object(); public CsvFileLoggerProvider(string filePath) { _filePath = filePath; // 首次创建时写入CSV表头 if (!File.Exists(filePath)) { File.WriteAllText(filePath, "Timestamp,ActionFullName,ExecutionDurationMs\n"); } } public ILogger CreateLogger(string categoryName) { return new CsvFileLogger(_filePath, _lock); } public void Dispose() { // 可添加文件流清理等逻辑 } }
然后实现CsvFileLogger,负责实际写入CSV格式的日志内容:
using Microsoft.Extensions.Logging; using System; using System.IO; public class CsvFileLogger : ILogger { private readonly string _filePath; private readonly object _lock; public CsvFileLogger(string filePath, object @lock) { _filePath = filePath; _lock = @lock; } public IDisposable BeginScope<TState>(TState state) => null; public bool IsEnabled(LogLevel logLevel) { // 只记录基准测试相关的信息级日志 return logLevel == LogLevel.Information; } public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { if (!IsEnabled(logLevel)) return; var logContent = formatter(state, exception); var timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff"); // 处理字段中的逗号转义,避免破坏CSV格式 var escapedContent = logContent.Replace(",", "\\,"); var csvLine = $"{timestamp},{escapedContent}\n"; // 线程安全写入文件,防止多请求并发写入冲突 lock (_lock) { File.AppendAllText(_filePath, csvLine); } } }
2. 注册自定义日志提供程序
根据你的.NET版本,在服务注册环节添加这个CSV日志提供程序:
如果你用的是.NET 6+(Program.cs):
var builder = WebApplication.CreateBuilder(args); // 注册CSV日志提供程序,指定日志文件路径(比如存到wwwroot下的logs文件夹) var logFilePath = Path.Combine(builder.Environment.WebRootPath, "logs", "action_benchmark.csv"); builder.Logging.AddProvider(new CsvFileLoggerProvider(logFilePath)); // 注册BenchmarkFilter为全局过滤器 builder.Services.AddControllersWithViews(options => { options.Filters.Add<BenchmarkFilter>(); }); // 其他服务配置... var app = builder.Build(); // 中间件配置... app.Run();
如果你用的是.NET 5及以前(Startup.cs):
public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(options => { options.Filters.Add<BenchmarkFilter>(); }); // 注册CSV日志提供程序 services.AddLogging(loggingBuilder => { var logFilePath = Path.Combine(Environment.WebRootPath, "logs", "action_benchmark.csv"); loggingBuilder.AddProvider(new CsvFileLoggerProvider(logFilePath)); }); }
3. 调整BenchmarkFilter注入日志并输出CSV字段
修改你的BenchmarkFilter,注入日志实例后,把Action名称、执行时长等数据按CSV格式输出:
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; using System.Diagnostics; public class BenchmarkFilter : IActionFilter { private readonly ILogger<BenchmarkFilter> _logger; private Stopwatch _stopwatch; public BenchmarkFilter(ILogger<BenchmarkFilter> logger) { _logger = logger; } public void OnActionExecuting(ActionExecutingContext context) { // 开始计时 _stopwatch = Stopwatch.StartNew(); } public void OnActionExecuted(ActionExecutedContext context) { _stopwatch.Stop(); // 获取Action的完整名称(控制器+Action) var actionFullName = $"{context.Controller.GetType().Name}.{context.ActionDescriptor.ActionName}"; var durationMs = _stopwatch.ElapsedMilliseconds; // 直接输出CSV格式的日志内容,让日志器直接写入文件 _logger.LogInformation("{ActionFullName},{DurationMs}", actionFullName, durationMs); } }
4. 可选优化点
- 日志滚动:如果日志文件过大,可以在
CsvFileLoggerProvider里添加按天/按文件大小滚动日志的逻辑; - 异步写入:高并发场景下,把
File.AppendAllText改成异步的await File.AppendAllTextAsync,配合SemaphoreSlim替代lock,性能会更好; - 字段标准化:如果Action名称可能包含特殊字符,可以进一步完善转义逻辑(比如用双引号包裹字段)。
这样配置完成后,BenchmarkFilter的基准测试数据就会自动写入你指定路径的CSV文件,格式清晰方便后续分析。
内容的提问来源于stack exchange,提问作者WhatTheDeuce
相关产品推荐
相关产品推荐

