Ведение журнала HTTP в ASP.NET Core

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.

Внимание

Эта информация относится к предварительному выпуску продукта, который может быть существенно изменен до его коммерческого выпуска. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В текущем выпуске см . версию .NET 8 этой статьи.

Ведение журнала HTTP — это ПО промежуточного слоя, которое регистрирует сведения о входящих HTTP-запросах и ответах HTTP. Ведение журнала HTTP предоставляет журналы со следующими сведениями:

  • HTTP-запросы;
  • Общие свойства
  • Заголовки
  • Текст
  • HTTP-ответы.

Ведение журнала HTTP может:

  • Регистрируют все запросы и ответы или только запросы и ответы, соответствующие определенным критериям.
  • Выберите, какие части запроса и ответа регистрируются.
  • Разрешите отредактировать конфиденциальную информацию из журналов.

Ведение журнала HTTP может снизить производительность приложения, особенно при ведении журналов запросов и ответов. При выборе регистрируемых полей учитывайте возможное влияние на производительность. Проверьте влияние выбранных свойств ведения журнала на производительность.

Предупреждение

Ведение журнала HTTP может потенциально ведения журнала личных сведений (PII). Примите во внимание возможные риски и не записывайте конфиденциальную информацию.

Включение ведения журнала HTTP

Ведение журнала HTTP включено путем вызова AddHttpLogging и UseHttpLogging, как показано в следующем примере:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(o => { });

var app = builder.Build();

app.UseHttpLogging();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();

app.MapGet("/", () => "Hello World!");

app.Run();

Пустая лямбда в предыдущем примере вызова AddHttpLogging добавляет ПО промежуточного слоя с конфигурацией по умолчанию. По умолчанию протоколирование HTTP регистрирует общие свойства, такие как путь, код состояния и заголовки для запросов и ответов.

Добавьте следующую строку в файл appsettings.Development.json на уровне "LogLevel": {, чтобы отображались журналы HTTP:

 "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"

При настройке по умолчанию запрос и ответ регистрируются как пара сообщений, аналогичных следующему примеру:

info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
      Request:
      Protocol: HTTP/2
      Method: GET
      Scheme: https
      PathBase:
      Path: /
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
      Host: localhost:52941
      User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.61
      Accept-Encoding: gzip, deflate, br
      Accept-Language: en-US,en;q=0.9
      Upgrade-Insecure-Requests: [Redacted]
      sec-ch-ua: [Redacted]
      sec-ch-ua-mobile: [Redacted]
      sec-ch-ua-platform: [Redacted]
      sec-fetch-site: [Redacted]
      sec-fetch-mode: [Redacted]
      sec-fetch-user: [Redacted]
      sec-fetch-dest: [Redacted]
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
      Response:
      StatusCode: 200
      Content-Type: text/plain; charset=utf-8
      Date: Tue, 24 Oct 2023 02:03:53 GMT
      Server: Kestrel

Параметры ведения журнала HTTP

Чтобы настроить глобальные параметры ПО промежуточного слоя ведения журнала HTTP, вызовите AddHttpLogging его Program.cs, используя лямбда-файл для настройки HttpLoggingOptions.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Примечание.

В предыдущем примере и следующих примерах UseHttpLogging вызывается после UseStaticFilesэтого, поэтому ведение журнала HTTP не включено для статических файлов. Чтобы включить ведение журнала HTTP статического файла, вызовите перед UseStaticFilesвызовомUseHttpLogging.

LoggingFields

HttpLoggingOptions.LoggingFields — это флаг перечисления, который позволяет настроить определенные регистрируемые части запроса и ответа. HttpLoggingOptions.LoggingFields по умолчанию использует RequestPropertiesAndHeaders | ResponsePropertiesAndHeaders.

RequestHeaders и ResponseHeaders.

RequestHeaders и ResponseHeaders представляют собой наборы заголовков HTTP, которые регистрируются. Значения заголовков регистрируются только для имен заголовков, которые находятся в этих коллекциях. Следующий код добавляется sec-ch-ua в RequestHeadersжурнал, поэтому значение заголовка sec-ch-ua регистрируется. И он добавляется MyResponseHeader в ResponseHeadersжурнал, поэтому значение заголовка MyResponseHeader регистрируется. Если эти строки удалены, значения этих заголовков являются [Redacted].

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

MediaTypeOptions

MediaTypeOptions предоставляет конфигурацию для выбора кодировки, используемой для конкретного типа носителя.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Этот подход также можно использовать для включения ведения журнала для данных, которые не регистрируются по умолчанию (например, данные формы, которые могут иметь тип носителя, application/x-www-form-urlencoded например или multipart/form-data).

Методы MediaTypeOptions

RequestBodyLogLimit и ResponseBodyLogLimit.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

CombineLogs

Параметр CombineLogs настройки true ПО промежуточного слоя для консолидации всех включенных журналов запроса и ответа в один журнал в конце. К ним относятся запрос, текст запроса, ответ, текст ответа и длительность.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Конфигурация для конкретной конечной точки

Для конфигурации для конкретной конечной точки в минимальных приложениях WithHttpLogging API доступен метод расширения. В следующем примере показано, как настроить ведение журнала HTTP для одной конечной точки:

app.MapGet("/response", () => "Hello World! (logging response)")
    .WithHttpLogging(HttpLoggingFields.ResponsePropertiesAndHeaders);

Для конфигурации для конкретной конечной точки в приложениях, использующих контроллеры, [HttpLogging] атрибут доступен. Атрибут также можно использовать в минимальных приложениях API, как показано в следующем примере:

app.MapGet("/duration", [HttpLogging(loggingFields: HttpLoggingFields.Duration)]
    () => "Hello World! (logging duration)");

IHttpLoggingInterceptor

IHttpLoggingInterceptor — это интерфейс для службы, которая может быть реализована для обработки запросов и обратных вызовов для каждого ответа для настройки сведений, которые регистрируются в журнале. Все параметры журнала, относящиеся к конечной точке, применяются сначала и затем можно переопределить в этих обратных вызовах. Реализация может:

  • Проверьте запрос или ответ.
  • Включите или отключите любой HttpLoggingFields.
  • Измените объем регистра запроса или ответа.
  • Добавьте настраиваемые поля в журналы.

IHttpLoggingInterceptor Регистрация реализации путем вызова AddHttpLoggingInterceptor<T>Program.cs. Если зарегистрировано несколько IHttpLoggingInterceptor экземпляров, они выполняются в порядке, зарегистрированном.

В следующем примере показано, как зарегистрировать реализацию IHttpLoggingInterceptor :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.Duration;
});
builder.Services.AddHttpLoggingInterceptor<SampleHttpLoggingInterceptor>();

В следующем примере показана IHttpLoggingInterceptor реализация:

  • Проверяет метод запроса и отключает ведение журнала для запросов POST.
  • Для запросов, отличных от POST:
    • Редактирует путь запроса, заголовки запросов и заголовки ответа.
    • Добавляет настраиваемые поля и значения полей в журналы запросов и ответов.
using Microsoft.AspNetCore.HttpLogging;

namespace HttpLoggingSample;

internal sealed class SampleHttpLoggingInterceptor : IHttpLoggingInterceptor
{
    public ValueTask OnRequestAsync(HttpLoggingInterceptorContext logContext)
    {
        if (logContext.HttpContext.Request.Method == "POST")
        {
            // Don't log anything if the request is a POST.
            logContext.LoggingFields = HttpLoggingFields.None;
        }

        // Don't enrich if we're not going to log any part of the request.
        if (!logContext.IsAnyEnabled(HttpLoggingFields.Request))
        {
            return default;
        }

        if (logContext.TryDisable(HttpLoggingFields.RequestPath))
        {
            RedactPath(logContext);
        }

        if (logContext.TryDisable(HttpLoggingFields.RequestHeaders))
        {
            RedactRequestHeaders(logContext);
        }

        EnrichRequest(logContext);

        return default;
    }

    public ValueTask OnResponseAsync(HttpLoggingInterceptorContext logContext)
    {
        // Don't enrich if we're not going to log any part of the response
        if (!logContext.IsAnyEnabled(HttpLoggingFields.Response))
        {
            return default;
        }

        if (logContext.TryDisable(HttpLoggingFields.ResponseHeaders))
        {
            RedactResponseHeaders(logContext);
        }

        EnrichResponse(logContext);

        return default;
    }

    private void RedactPath(HttpLoggingInterceptorContext logContext)
    {
        logContext.AddParameter(nameof(logContext.HttpContext.Request.Path), "RedactedPath");
    }

    private void RedactRequestHeaders(HttpLoggingInterceptorContext logContext)
    {
        foreach (var header in logContext.HttpContext.Request.Headers)
        {
            logContext.AddParameter(header.Key, "RedactedHeader");
        }
    }

    private void EnrichRequest(HttpLoggingInterceptorContext logContext)
    {
        logContext.AddParameter("RequestEnrichment", "Stuff");
    }

    private void RedactResponseHeaders(HttpLoggingInterceptorContext logContext)
    {
        foreach (var header in logContext.HttpContext.Response.Headers)
        {
            logContext.AddParameter(header.Key, "RedactedHeader");
        }
    }

    private void EnrichResponse(HttpLoggingInterceptorContext logContext)
    {
        logContext.AddParameter("ResponseEnrichment", "Stuff");
    }
}

При использовании этого перехватчика запрос POST не создает журналы, даже если для журнала HTTP настроено ведение журнала HttpLoggingFields.All. Запрос GET создает журналы, аналогичные следующему примеру:

info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
      Request:
      Path: RedactedPath
      Accept: RedactedHeader
      Host: RedactedHeader
      User-Agent: RedactedHeader
      Accept-Encoding: RedactedHeader
      Accept-Language: RedactedHeader
      Upgrade-Insecure-Requests: RedactedHeader
      sec-ch-ua: RedactedHeader
      sec-ch-ua-mobile: RedactedHeader
      sec-ch-ua-platform: RedactedHeader
      sec-fetch-site: RedactedHeader
      sec-fetch-mode: RedactedHeader
      sec-fetch-user: RedactedHeader
      sec-fetch-dest: RedactedHeader
      RequestEnrichment: Stuff
      Protocol: HTTP/2
      Method: GET
      Scheme: https
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
      Response:
      Content-Type: RedactedHeader
      MyResponseHeader: RedactedHeader
      ResponseEnrichment: Stuff
      StatusCode: 200
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[4]
      ResponseBody: Hello World!
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[8]
      Duration: 2.2778ms

Порядок настройки ведения журнала приоритета

В следующем списке показано порядок приоритета для конфигурации ведения журнала:

  1. Глобальная конфигурация из HttpLoggingOptions, заданная путем вызова AddHttpLogging.
  2. Конфигурация для конкретной конечной точки из атрибута [HttpLogging] или WithHttpLogging метода расширения переопределяет глобальную конфигурацию.
  3. IHttpLoggingInterceptor вызывается с результатами и может дополнительно изменять конфигурацию для каждого запроса.

Ведение журнала HTTP реализуется с помощью ПО промежуточного слоя, которое регистрирует сведения о входящих HTTP-запросах и ответах. Ведение журнала HTTP предоставляет журналы со следующими сведениями:

  • HTTP-запросы;
  • Общие свойства
  • Заголовки
  • Текст
  • HTTP-ответы.

Ведение журнала HTTP можно использовать в нескольких сценариях:

  • запись сведений о входящих запросах и ответах;
  • фильтрация регистрируемых частей запросов и ответов;
  • фильтрация регистрируемых заголовков.

Ведение журнала HTTP может снизить производительность приложения, особенно при записи текста запроса и ответа. При выборе регистрируемых полей учитывайте возможное влияние на производительность. Проверьте влияние выбранных свойств ведения журнала на производительность.

Предупреждение

В ходе ведения журнала HTTP могут записываться личные сведения. Примите во внимание возможные риски и не записывайте конфиденциальную информацию.

Включение ведения журнала HTTP

Ведение журнала HTTP включается с использованием UseHttpLogging для добавления соответствующего ПО промежуточного слоя.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.UseHttpLogging();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();

app.MapGet("/", () => "Hello World!");

app.Run();

По умолчанию в ходе ведения журнала HTTP в журнал записываются общие свойства, такие как путь, код состояния и заголовки для запросов и ответов. Добавьте следующую строку в файл appsettings.Development.json на уровне "LogLevel": {, чтобы отображались журналы HTTP:

 "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"

Выходные данные записываются в журнал как одно сообщение в LogLevel.Information.

Пример выходных данных запроса

Параметры ведения журнала HTTP

Чтобы настроить ПО промежуточного слоя для ведения журнала HTTP, вызовите AddHttpLogging в Program.cs.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Примечание.

В предыдущем примере и следующих примерах UseHttpLogging вызывается после UseStaticFilesэтого, поэтому ведение журнала HTTP не включено для статического файла. Чтобы включить ведение журнала HTTP статического файла, вызовите перед UseStaticFilesвызовомUseHttpLogging.

LoggingFields

HttpLoggingOptions.LoggingFields — это флаг перечисления, который позволяет настроить определенные регистрируемые части запроса и ответа. HttpLoggingOptions.LoggingFields по умолчанию использует RequestPropertiesAndHeaders | ResponsePropertiesAndHeaders.

RequestHeaders

Headers — это набор заголовков HTTP-запросов, которые разрешено регистрировать. Значения заголовков регистрируются только для имен заголовков в этой коллекции. Следующий код регистрирует заголовок "sec-ch-ua" запроса. Если удалить logging.RequestHeaders.Add("sec-ch-ua");, значение заголовка запроса "sec-ch-ua" исключается из записи. Следующий выделенный код вызывает HttpLoggingOptions.RequestHeaders и HttpLoggingOptions.ResponseHeaders:

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

MediaTypeOptions

MediaTypeOptions предоставляет конфигурацию для выбора кодировки, используемой для конкретного типа носителя.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Этот подход также можно использовать для включения ведения журнала для данных, которые не регистрируются по умолчанию (например, данные формы, которые могут иметь тип носителя, например application/x-www-form-urlencoded или multipart/form-data).

Методы MediaTypeOptions

RequestBodyLogLimit и ResponseBodyLogLimit.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();