.NET 8中自定义HTTP日志中间件格式及文件日志存储方法
自定义ASP.NET Core HttpLogging格式与无第三方库文件日志实现
一、自定义HttpLogging中间件日志格式
默认的HttpLogging中间件依赖HttpLoggingFormatter处理日志输出,要自定义格式,只需实现专属格式化类并替换默认服务:
- 创建自定义格式化类,继承
HttpLoggingFormatter
public class CustomHttpLoggingFormatter : HttpLoggingFormatter { public CustomHttpLoggingFormatter(IOptions<HttpLoggingOptions> options) : base(options) { } public override async Task WriteRequestAsync(HttpLoggingContext context) { var request = context.HttpContext.Request; var logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 请求: {request.Method} {request.Path}{request.QueryString} " + $"来源IP: {context.HttpContext.Connection.RemoteIpAddress} " + $"UserAgent: {request.Headers.UserAgent}"; // 若开启请求体日志,追加内容 if (Options.LoggingFields.HasFlag(HttpLoggingFields.RequestBody)) { var bodyContent = await ReadRequestBodyAsync(context); if (!string.IsNullOrEmpty(bodyContent)) { logMessage += $" 请求体: {bodyContent}"; } } context.Logger.LogInformation(logMessage); } public override async Task WriteResponseAsync(HttpLoggingContext context) { var response = context.HttpContext.Response; var logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 响应: {response.StatusCode} " + $"请求路径: {context.HttpContext.Request.Path}{context.HttpContext.Request.QueryString} " + $"响应时长: {context.ElapsedMilliseconds}ms"; // 若开启响应体日志,追加内容 if (Options.LoggingFields.HasFlag(HttpLoggingFields.ResponseBody)) { var bodyContent = await ReadResponseBodyAsync(context); if (!string.IsNullOrEmpty(bodyContent)) { logMessage += $" 响应体: {bodyContent}"; } } context.Logger.LogInformation(logMessage); } // 辅助方法:读取请求体并重置流位置 private async Task<string> ReadRequestBodyAsync(HttpLoggingContext context) { context.HttpContext.Request.Body.Position = 0; using var reader = new StreamReader(context.HttpContext.Request.Body, leaveOpen: true); var body = await reader.ReadToEndAsync(); context.HttpContext.Request.Body.Position = 0; return body; } // 辅助方法:读取响应体并恢复原始流 private async Task<string> ReadResponseBodyAsync(HttpLoggingContext context) { var originalStream = context.HttpContext.Response.Body; using var memoryStream = new MemoryStream(); context.HttpContext.Response.Body = memoryStream; await context.Next(context.HttpContext); memoryStream.Position = 0; using var reader = new StreamReader(memoryStream); var body = await reader.ReadToEndAsync(); memoryStream.Position = 0; await memoryStream.CopyToAsync(originalStream); context.HttpContext.Response.Body = originalStream; return body; } }
- 在Program.cs中替换默认格式化服务
var builder = WebApplication.CreateBuilder(args); // 配置HttpLogging并替换自定义格式化器 builder.Services.AddHttpLogging(options => { options.LoggingFields = HttpLoggingFields.All; // 根据需求调整要记录的字段 }) .AddSingleton<HttpLoggingFormatter, CustomHttpLoggingFormatter>(); var app = builder.Build(); // 启用HttpLogging中间件(注意顺序:需放在业务中间件之前) app.UseHttpLogging(); app.MapGet("/", () => "Hello World!"); app.Run();
二、不依赖第三方库实现文件日志存储
通过自定义ILogger和ILoggerProvider实现文件日志,步骤如下:
- 创建自定义Logger类
public class FileLogger : ILogger { private readonly string _categoryName; private readonly string _logFilePath; private readonly object _lock = new object(); public FileLogger(string categoryName, string logFilePath) { _categoryName = categoryName; _logFilePath = logFilePath; // 确保日志目录存在 var directory = Path.GetDirectoryName(logFilePath); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } } public IDisposable BeginScope<TState>(TState state) => null; public bool IsEnabled(LogLevel logLevel) { // 按需配置日志级别过滤,此处默认开启所有非None级别 return logLevel != LogLevel.None; } public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { if (!IsEnabled(logLevel)) return; var logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] [{logLevel}] [{_categoryName}] {formatter(state, exception)}"; if (exception != null) { logMessage += $"\n异常详情: {exception.ToString()}"; } logMessage += "\n----------------------------------------\n"; // 线程安全写入文件 lock (_lock) { File.AppendAllText(_logFilePath, logMessage); } } }
- 创建自定义LoggerProvider类
public class FileLoggerProvider : ILoggerProvider { private readonly string _logFilePath; private readonly Dictionary<string, FileLogger> _loggers = new Dictionary<string, FileLogger>(); private readonly object _lock = new object(); public FileLoggerProvider(string logFilePath) { _logFilePath = logFilePath; } public ILogger CreateLogger(string categoryName) { lock (_lock) { if (!_loggers.TryGetValue(categoryName, out var logger)) { logger = new FileLogger(categoryName, _logFilePath); _loggers[categoryName] = logger; } return logger; } } public void Dispose() { _loggers.Clear(); } }
- 在Program.cs中注册自定义日志提供器
var builder = WebApplication.CreateBuilder(args); // 添加自定义文件日志提供器 builder.Logging.ClearProviders() // 可选:清除默认控制台等日志提供器,需保留则删除此行 .AddProvider(new FileLoggerProvider(Path.Combine(AppContext.BaseDirectory, "Logs", "app.log"))); // 其他服务配置 builder.Services.AddHttpLogging(options => { options.LoggingFields = HttpLoggingFields.All; }) .AddSingleton<HttpLoggingFormatter, CustomHttpLoggingFormatter>(); var app = builder.Build(); app.UseHttpLogging(); app.MapGet("/", () => "Hello World!"); app.Run();
注意事项
- 自定义HttpLoggingFormatter时,必须重置请求/响应流的位置,避免影响后续中间件读取
- 文件日志写入需保证线程安全,高并发场景可替换为异步追加方式优化性能
- 可按需扩展文件日志功能,比如按日期分割日志、限制单文件大小自动归档等
内容的提问来源于stack exchange,提问作者JOYBOY
相关产品推荐
相关产品推荐

