You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 8中自定义HTTP日志中间件格式及文件日志存储方法

自定义ASP.NET Core HttpLogging格式与无第三方库文件日志实现

一、自定义HttpLogging中间件日志格式

默认的HttpLogging中间件依赖HttpLoggingFormatter处理日志输出,要自定义格式,只需实现专属格式化类并替换默认服务:

  1. 创建自定义格式化类,继承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;
    }
}
  1. 在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实现文件日志,步骤如下:

  1. 创建自定义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);
        }
    }
}
  1. 创建自定义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();
    }
}
  1. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 18:55:03