Вопрос или проблема
Я хотел бы записывать дополнительные данные с активным контекстом логирования, но мне нужно использовать разные методы: Например, я пытаюсь записывать запросы к веб-API с помощью следующих настроек:
В Program.cs вверху:
Log.Logger = new Serilog.LoggerConfiguration()
.CreateBootstrapLogger();
При регистрации сервиса:
services.AddHttpContextAccessor();
services.AddHttpLogging(o =>
{
o.CombineLogs = true;.
o.LoggingFields = HttpLoggingFields.All;
o.ResponseHeaders.Remove("Content-Type");
o.ResponseHeaders.Remove("Accept");
o.RequestHeaders.Remove("Content-Type");
o.RequestHeaders.Remove("Accept");
o.RequestBodyLogLimit = 8096;
o.ResponseBodyLogLimit = 8096;
});
services.AddHttpLoggingInterceptor<SampleHttpLoggingInterceptor>();
var columnOptions = new ColumnOptions
{
AdditionalDataColumns = new List<DataColumn>
{
new DataColumn("CorrelationId", typeof(string)),
new DataColumn("TraceId", typeof(string)),
new DataColumn("SpanId", typeof(string)),
new DataColumn("ChannelId", typeof(string)),
new DataColumn("RequestPath", typeof(string)),
new DataColumn("Duration", typeof(decimal)),
new DataColumn("SourceContext", typeof(string)),
}
};
//columnOptions.Properties.ExcludeAdditionalProperties = true;
//columnOptions.LogEvent.ExcludeAdditionalProperties = true;
//columnOptions.Store.Remove(StandardColumn.MessageTemplate);
// нам не нужны XML данные
columnOptions.Store.Remove(StandardColumn.Properties);
services.AddSerilog((services, lc) => lc
.ReadFrom.Configuration(config)
.ReadFrom.Services(services)
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Hosting", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Mvc", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning)
//.MinimumLevel.Override("System.Net.Http.HttpClient", LogEventLevel.Warning)
.MinimumLevel.Override("System.Net.Http.HttpClient.X", LogEventLevel.Warning) //Временный
.MinimumLevel.Override("Polly", LogEventLevel.Warning) //выраженные политики, такие как Retry, Timeout...
//Serilog.AspNetCore.RequestLoggingMiddleware > Warning
.Enrich.FromLogContext()
//.Enrich.WithProperty("MyTraceId", string.Empty)
//.Enrich.WithProperty("SourceContext", string.Empty)
//.Enrich.WithProperty("TraceId", string.Empty)
//.Enrich.WithProperty("SpanId", string.Empty)
.Enrich.WithProperty("ChannelId", string.Empty)
.Enrich.WithProperty("Duration", string.Empty)
.Enrich.WithClientIp()
//.Enrich.WithCorrelationId()
.Enrich.WithCorrelationId("CorrelationIdHeader", addValueIfHeaderAbsence: true)
.Enrich.WithSpan()
.WriteTo.Conditional(
_ => isDeveloment,
wt => wt.Console(theme: AnsiConsoleTheme.Code
)
)
.WriteTo.Oracle(cfg =>
cfg.WithSettings(
"User Id=...",
tableSpaceAndTableName: "LogTable",
tableSpaceAndFunctionName: string.Empty, columnOptions: columnOptions
)
.UseBurstBatch() //.UsePeriodicBatch(1)
.CreateSink(),
restrictedToMinimumLevel: LogEventLevel.Information
)
);
При использовании сервисов:
app.UseHttpLogging();
app.UseSerilogRequestLogging(options =>
{
options.IncludeQueryInRequestPath = true;
});
С вышеуказанными настройками я могу записывать следующие строки (кроме channelId):
Чтобы иметь возможность добавить channelId, который является пользовательским столбцом, мне нужно добавить следующие строки в действие контроллера, в противном случае я получу значение null:
_diagnosticContext.Set("ChannelId", request.Channel); //если SourceContex - "Serilog.AspNetCore.RequestLoggingMiddleware"
LogContext.PushProperty("ChannelId", request.Channel); //если SourceContext общий или HttpClient
HttpContext.Items.Add("CustomKey_ChannelId", request.Channel); //если SourceContext - Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware
Для HttpLoggingMiddleware я использую Интерсептор для добавления данных в контекст логирования:
private void EnrichResponse(HttpLoggingInterceptorContext logContext)
{
var list = logContext.HttpContext.Items.Where(w => w.Key.ToString().StartsWith("CustomKey_")).ToList();
if (list.Count > 0)
logContext.AddParameter(list[0].Key.ToString().Replace("CustomKey_", ""), list[0].Value.ToString());
Мне не должно быть нужно использовать разные методы для передачи пользовательских данных в контекст логирования для разных SourceContext. Либо есть единый способ, и я не могу его найти, либо есть структурная проблема.
Есть ли правильный способ это сделать? Я сомневаюсь, особенно используя интерсептор.
Ответ или решение
Эффективное использование Serilog для добавления дополнительных данных в лог-контекст
С учетом вашего вопроса о добавлении дополнительных данных в активный лог-контекст с использованием Serilog, следует отметить, что структура логирования может вызывать некоторые трудности, особенно если нужно работать с несколькими источниками контекста. Рассмотрим более детально, как оптимизировать этот процесс.
Проблема с различиями в подходах
Как вы упомянули, в вашем случае с использованием различных методов для добавления данных в контекст лога, таких как _diagnosticContext.Set
, LogContext.PushProperty
, и HttpContext.Items
, можно столкнуться с неэффективностью и перегруженностью кода. Это связано с тем, что каждый метод применяется в зависимости от источника контекста, что усложняет процесс логирования.
Решение через унификацию подхода
Чтобы сделать процесс логирования более предсказуемым и управляемым, я рекомендую следующий подход:
-
Использование одного места для управления контекстом: Вместо создания нескольких методов для добавления данных в лог-контекст, создайте единый метод-обертку, который будет принимать ключ и значение, а затем определять, какой метод использовать в зависимости от источника контекста.
public void EnrichLogContext(string key, object value) { if (HttpContext.Items.ContainsKey("CustomKey_" + key)) { HttpContext.Items["CustomKey_" + key] = value; } else { LogContext.PushProperty(key, value); _diagnosticContext.Set(key, value); } }
-
Создание единичного логирования: Рекомендуется создать единый интерфейс для логирования, который будет вызывать этот метод, что уменьшит количество логики в ваших контроллерах и других местах использования.
public class Logger { public static void LogEvent(string channelId) { EnrichLogContext("ChannelId", channelId); // Входные данные для других параметров лога } }
-
Стратегия цифр логирования: Если ваш проект подразумевает множество источников и необходимость в однообразии, можно использовать паттерн "Стратегия", где каждый источник может иметь своё поведение, но обработка будет унифицирована через общий интерфейс.
Использование HttpLoggingInterceptor для добавления информации
При использовании HttpLoggingInterceptor
добавьте вызов вашего единого метода для обогащения данных:
private void EnrichResponse(HttpLoggingInterceptorContext logContext)
{
if (logContext.HttpContext.Items.TryGetValue("CustomKey_ChannelId", out var channelId))
{
logContext.AddParameter("ChannelId", channelId);
}
}
Эта конструкция также будет работать в вашем методе EnrichLogContext
, что позволит вам уменьшить дублирование кода.
Вывод
С помощью единого метода для управления контекстом логирования вы избавитесь от необходимости использовать несколько подходов и методов для разных источников. Это не только повысит читабельность и поддерживаемость кода, но и обеспечит более предсказуемую логику добавления данных в лог-контекст.
Таким образом, ваша система логирования станет более устроенной, и вы сможете легко добавлять новые параметры по мере необходимости, не ухудшая структуру кода.