Как настроить Serilog для совместной регистрации тел запросов и ответов?C#

Место общения программистов C#
Anonymous
Как настроить Serilog для совместной регистрации тел запросов и ответов?

Сообщение Anonymous »

В веб-API ASP.NET Core очень легко добавить Microsoft.AspNetCore.HttpLogging, чтобы включить как запрос, так и ответ.
В моем запуске, раньше создавая приложение с помощью builder.Build(), я добавляю этот код:

Код: Выделить всё

builder.Services.AddHttpLogging(logging =>
{
logging.LoggingFields =
HttpLoggingFields.RequestPath |
HttpLoggingFields.RequestBody |
HttpLoggingFields.ResponseStatusCode |
HttpLoggingFields.ResponseBody |
HttpLoggingFields.Duration;
logging.MediaTypeOptions.AddText("application/json");
logging.MediaTypeOptions.AddText("text/plain");
logging.RequestBodyLogLimit = 4096;
logging.ResponseBodyLogLimit = 4096;
logging.CombineLogs = true;
});
После сборки звоню

Код: Выделить всё

app.UseHttpLogging();
В моем файле appsettings.json я добавил:

Код: Выделить всё

"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"
}
}
Тогда я получаю в своем журнале именно ту информацию, которую хочу:

Код: Выделить всё

info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[9]
Request and Response:
PathBase:
Path: /api/v1/products
StatusCode: 200
ResponseBody: {"products":[{"family":"ProductFamily","products":["C91C","C90C","C89C","C89B","C23A","C09C","C09B","A43A","A31B"]}]}
Duration: 533.5687
(В данном случае не было тела запроса)
Все это можно настроить примерно за 20 минут. Проблема в том, что Microsoft.AspNetCore.HttpLogging записывает только в консоль, нет поддержки записи в файл или любую другую цель.
Чтобы выполнить ведение журнала с помощью Serilog , очевидно, необходимо написать собственное промежуточное программное обеспечение для сбора информации. Мне нигде не удалось найти четкого ответа о том, как именно это настроить.
Здесь мы находим хорошее объяснение, как настроить промежуточное программное обеспечение для захвата тел запросов и ответов:
https://Exceptionnotfound.net/using-mid ... -net-core/
но он не включает в себя, как на самом деле регистрировать их с помощью Serilog.
Этот вопрос
Как добавить «тело запроса» в выходные данные Serilog .net core?
есть несколько ответов о том, как регистрировать тело запроса, но они неприменимы к телу ответа, поскольку его поток нельзя переместить так же, как поток запроса.
Эта публикация
https://github.com/serilog/serilog-aspn ... issues/168
и этот вопрос
Serilog регистрирует методы веб-API, добавляя свойства контекста внутри промежуточного программного обеспечения
кажется, это именно то, что мне нужно, но использование у меня они не сработали, потому что свойства добавлены через

Код: Выделить всё

LogContext.PushProperty("RequestBody", requestBodyPayload);
не отображаются в моем журнале, даже если я укажу

Код: Выделить всё

"Enrich": [ "FromLogContext" ]
в appsettings.json для Serilog. Невозможно самостоятельно получить свойства из LogContext, поэтому я не смог найти обходной путь.
Сначала я попробовал следующее: я активировал Serilog в сборщике. и поручите ему получить конфигурацию из настроек:

Код: Выделить всё

builder.Host.UseSerilog((context, configuration) =>
{
configuration.ReadFrom.Configuration(context.Configuration);
});
В моем файле appsettings.json у меня есть настройки для входа в консоль и файл:

Код: Выделить всё

"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{ "Name": "Console" },
{
"Name": "File",
"Args": {
"path": "Log_.log",
"rollingInterval": "Day",
"rollOnFileSizeLimit": true,
"filesizeLimitBytes": 67108864
}
}
],
"Enrich": [ "FromLogContext" ]
},
После того, как приложение создано, я звоню

Код: Выделить всё

app.UseSerilogRequestLogging();
Это дает мне хорошее журналирование запросов/ответов, но без тел.
Пример записи в журнале:

Код: Выделить всё

[16:02:21 INF] HTTP GET /api/v1/products responded 200 in 504.9038 ms
Затем я реализовал промежуточное программное обеспечение для захвата тел и отправки их в LogContext.

Код: Выделить всё

public class MyRequestResponseLoggingMiddlewareForSerilog
{
private readonly RequestDelegate _next;

public MyRequestResponseLoggingMiddlewareForSerilog(RequestDelegate next)
{
_next = next;
}

public async Task Invoke(HttpContext context)
{
// Allow multiple reading of request body
context.Request.EnableBuffering();

// Add the request body to the LogContext
string requestBodyText = await new StreamReader(context.Request.Body).ReadToEndAsync();
LogContext.PushProperty("RequestBody", requestBodyText);

// Rewind the Body stream so the next middleware can use it.
context.Request.Body.Position = 0;

//Save the reference to the original response body stream
var originalBodyStream = context.Response.Body;

// Create a new rewindable memory stream...
using (var responseBody = new MemoryStream())
{
//...and use that for the temporary response body
context.Response.Body = responseBody;

// Continue down the Middleware pipeline, eventually returning to this class
await _next(context);

// Get the response from the server and save it in the LogContext
context.Response.Body.Position = 0;
string responseBodyText = await new StreamReader(context.Response.Body).ReadToEndAsync();
context.Response.Body.Position = 0;
LogContext.PushProperty("ResponseBody", responseBodyText);

// Copy the contents of the new memory stream (which contains the response) to the original stream,
// which is then returned to the client.
await responseBody.CopyToAsync(originalBodyStream);
}
}
}
Я активирую его после активации Serilog, чтобы его контекст был доступен, когда элемент управления возвращается к регистрации запросов Serilog.

Код: Выделить всё

            app.UseSerilogRequestLogging(options =>
{
options.MessageTemplate =
"HTTP {RequestMethod} {RequestPath} {RequestBody} responded {StatusCode} with {ResponseBody} in {Elapsed:0.0000}";
}
);
app.UseMiddleware();
К сожалению, в журналах не видны тела. Вместо этого я вижу текст из шаблона {RequestBody} и {ResponseBody} без его замены.

Код: Выделить всё

[16:08:55 INF] HTTP GET /api/v1/products {RequestBody} responded 200 with {ResponseBody} in 520.2372
Чтобы наконец заставить его работать, я заменил свое промежуточное программное обеспечение следующим:

Код: Выделить всё

   public class MyRequestResponseLoggingMiddlewareForSerilog
{
private readonly RequestDelegate _next;

public MyRequestResponseLoggingMiddlewareForSerilog(RequestDelegate next)
{
_next = next;
}

public async Task Invoke(HttpContext context)
{
// Allow multiple reading of request body
context.Request.EnableBuffering();

// Create a new rewindable memory stream for the response which downstream middleware will use
context.Response.Body = new MemoryStream();

// Continue down the Middleware pipeline, eventually returning to this class
await _next(context);
}

public static async void EnrichDiagnosticContext(IDiagnosticContext diagnosticContext, HttpContext httpContext)
{
Stream requestBodyStream = httpContext.Request.Body;
requestBodyStream.Position = 0;
string requestBodyAsText = await new StreamReader(requestBodyStream).ReadToEndAsync();
requestBodyStream.Position = 0;
diagnosticContext.Set("RequestBody", requestBodyAsText);

Stream responseBodyStream = httpContext.Response.Body;
responseBodyStream.Position = 0;
string responseBodyAsText = await new StreamReader(responseBodyStream).ReadToEndAsync();
responseBodyStream.Position = 0;
diagnosticContext.Set("ResponseBody", responseBodyAsText);
}
}
и активировать «обогатитель» для Serilog, когда я активирую регистрацию запросов.

Код: Выделить всё

            app.UseSerilogRequestLogging(options =>
{
options.MessageTemplate =
"HTTP {RequestMethod} {RequestPath} {RequestBody} responded {StatusCode} with {ResponseBody} in {Elapsed:0.0000}";
options.EnrichDiagnosticContext = MyRequestResponseLoggingMiddlewareForSerilog.EnrichDiagnosticContext;
}
);
Это дает мне нужную запись в журнале:

Код: Выделить всё

[16:21:40 INF] HTTP GET /api/v1/products {} responded 200 with {"products":[{"family":"ProductFamily","products":["C91C","C90C","C89C","C89B","C23A","C09C","C09B","A43A","A31B"]}]} in 574.4265
НО у меня возникают следующие вопросы:
  • Действительно ли это то, что вам нужно делать это с Serilog, или я что-то упускаю? Это кажется слишком сложным для операции, которая, как я думаю, понадобится многим людям.
  • Мне нужно создать MemoryStream чтобы сделать поток тела ответа перематываемым, но я не могу разместить его где-либо в своем коде, поскольку он нужен последующему промежуточному программному обеспечению. Кроме того, исходный поток тела ответа также все еще существует, но больше не используется и, вероятно, также не удаляется. Как и где я могу разместить эти потоки для очистки?
  • Мог ли я перенести информацию из LogContext в журнал, сделав что-то по-другому ? Тогда я мог бы очистить поток, созданный в моем собственном коде.


Подробнее здесь: https://stackoverflow.com/questions/786 ... s-together

Вернуться в «C#»