Инициализировать репозиторий «Дейл»
Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ, инструкция пользователя, техдокументация, код-стайл), бэклог, скрипты развёртывания и архив прототипа LeadRadar.
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting.Compact;
|
||||
|
||||
namespace Deal.Api.Logging;
|
||||
|
||||
// Serilog-конфигурация процесса core (Ruling 7/9, план Task 14).
|
||||
// Все четыре процесса (core + telegram/ai/ml-сервисы) логируют через Serilog: консоль — JSON в
|
||||
// prod-стиле (CompactJsonFormatter: одна JSON-строка на событие, поля @t/@mt/@l — парсинг
|
||||
// Loki/Promtail) либо текст в Development; плюс rolling-файл data/logs/deal-<процесс>.json
|
||||
// (под ContentRoot; у core каталог data смонтирован volume-ом, compose.prod). Уровень/каталог
|
||||
// переопределяются env: DEAL_LOG_LEVEL, DEAL_LOGS_DIR.
|
||||
// Конфигурация кодом, а не секцией appsettings: у трёх сервисов appsettings.json нет (весь конфиг —
|
||||
// env, Ruling 13), поэтому единый для четырёх хостов код-набор с env-переопределениями дешевле и не
|
||||
// расходится между процессами. Правило «секреты не логируются» (Ruling 13) соблюдается на уровне
|
||||
// сообщений (в лог-конфигурации секретов нет; запрос-логи Task 14 логируют метод/путь/статус без
|
||||
// query/заголовков/тел). OTel-метрики/трейсы и Prometheus в этапе 7 не добавляются (Ruling 7) —
|
||||
// стек: Serilog-логи → docker-логи → Promtail → Loki → Grafana.
|
||||
// Вызов — из Program.cs процесса (entry point): DealLogging.Configure(builder, "…") ДО
|
||||
// builder.Build(). Интеграционные тесты сервисов поднимают хост через *ServiceHost.Create
|
||||
// БЕЗ этого вызова (конфигурация логирования — забота production-точки входа), поэтому тесты не пишут
|
||||
// файлы-логи и не меняют своё логирование.
|
||||
internal static class DealLogging
|
||||
{
|
||||
// Env-ключ минимального уровня Serilog (Debug/Information/Warning/Error; дефолт Information).
|
||||
private const string MinimumLevelEnvKey = "DEAL_LOG_LEVEL";
|
||||
|
||||
// Env-ключ каталога rolling-файлов (дефолт data/logs под ContentRoot).
|
||||
private const string LogsDirectoryEnvKey = "DEAL_LOGS_DIR";
|
||||
|
||||
// Каталог логов по умолчанию (относительно ContentRoot): core — внутри volume /app/data.
|
||||
private const string DefaultLogsSubdirectory = "data/logs";
|
||||
|
||||
// Шаблон имени rolling-файла (Serilog добавляет дату перед расширением): deal-core-20260908.json.
|
||||
private const string LogFileNameTemplate = "deal-{0}-.json";
|
||||
|
||||
// Сколько rolling-файлов хранится (суток); старшие удаляются Serilog автоматически.
|
||||
private const int RetainedFileCount = 30;
|
||||
|
||||
// Текстовая разметка консоли в Development (цвета — дефолтной темой Serilog).
|
||||
private const string DevelopmentConsoleTemplate =
|
||||
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}";
|
||||
|
||||
// Категория EF Core: команды SQL логируются на Warning+ (шум запросов не попадает в Loki).
|
||||
private const string EntityFrameworkCoreCategory = "Microsoft.EntityFrameworkCore";
|
||||
|
||||
// Категория Grpc.AspNetCore: не ниже Information (внутренние Debug-события вызовов не дублируют access-лог).
|
||||
private const string GrpcCategory = "Grpc";
|
||||
|
||||
// Дефолтный уровень при пустом/невалидном env DEAL_LOG_LEVEL.
|
||||
private const LogEventLevel DefaultMinimumLevel = LogEventLevel.Information;
|
||||
|
||||
/// <summary>
|
||||
/// Подключает Serilog к хосту (builder.Host.UseSerilog). Регистрация отложенная: конфигурация
|
||||
/// логгера применяется при <c>builder.Build()</c>, когда среда/конфигурация (env) уже собраны.
|
||||
/// </summary>
|
||||
/// <param name="builder">Билдер WebApplication процесса (до Build).</param>
|
||||
/// <param name="processName">Имя процесса для имени файла-лога (core/telegram/ai/ml).</param>
|
||||
public static void Configure(WebApplicationBuilder builder, string processName)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(processName);
|
||||
builder.Host.UseSerilog((context, loggerConfiguration) =>
|
||||
Apply(loggerConfiguration, context.HostingEnvironment, context.Configuration, processName));
|
||||
}
|
||||
|
||||
// Собирает LoggerConfiguration процесса: уровень/фильтры, rolling-файл, консоль.
|
||||
// loggerConfiguration: Конфигурация Serilog (до CreateLogger).
|
||||
// environment: Окружение хоста (Development — текстовая консоль).
|
||||
// configuration: Конфигурация хоста (env DEAL_LOG_*).
|
||||
// processName: Имя процесса (суффикс имени rolling-файла).
|
||||
private static void Apply(
|
||||
LoggerConfiguration loggerConfiguration,
|
||||
IHostEnvironment environment,
|
||||
IConfiguration configuration,
|
||||
string processName)
|
||||
{
|
||||
loggerConfiguration
|
||||
.MinimumLevel.Is(ParseMinimumLevel(configuration[MinimumLevelEnvKey]))
|
||||
.MinimumLevel.Override(EntityFrameworkCoreCategory, LogEventLevel.Warning)
|
||||
.MinimumLevel.Override(GrpcCategory, LogEventLevel.Information)
|
||||
.Enrich.FromLogContext();
|
||||
|
||||
string logsDirectory = ResolveLogsDirectory(environment.ContentRootPath, configuration[LogsDirectoryEnvKey]);
|
||||
Directory.CreateDirectory(logsDirectory);
|
||||
string logFilePath = Path.Combine(
|
||||
logsDirectory,
|
||||
string.Format(LogFileNameTemplate, processName));
|
||||
loggerConfiguration.WriteTo.File(
|
||||
new CompactJsonFormatter(),
|
||||
logFilePath,
|
||||
rollingInterval: RollingInterval.Day,
|
||||
retainedFileCountLimit: RetainedFileCount);
|
||||
|
||||
if (environment.IsDevelopment())
|
||||
{
|
||||
// Dev: читаемый текст в консоли (Ruling 7: «dev можно текст»); файл — всегда JSON.
|
||||
loggerConfiguration.WriteTo.Console(outputTemplate: DevelopmentConsoleTemplate);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Prod-стиль: одна JSON-строка на событие — docker-логи собирает Promtail (Ruling 7/9).
|
||||
loggerConfiguration.WriteTo.Console(new CompactJsonFormatter());
|
||||
}
|
||||
}
|
||||
|
||||
// Каталог rolling-файлов: env DEAL_LOGS_DIR либо data/logs под ContentRoot процесса.
|
||||
// contentRootPath: ContentRoot хоста (/app в контейнере).
|
||||
// configuredDirectory: Значение env DEAL_LOGS_DIR (null/пусто — дефолт).
|
||||
// Возвращает: Абсолютный путь каталога логов.
|
||||
private static string ResolveLogsDirectory(string contentRootPath, string? configuredDirectory)
|
||||
=> string.IsNullOrWhiteSpace(configuredDirectory)
|
||||
? Path.Combine(contentRootPath, DefaultLogsSubdirectory)
|
||||
: configuredDirectory.Trim();
|
||||
|
||||
// Разбирает env DEAL_LOG_LEVEL; пустое/невалидное значение — DefaultMinimumLevel.
|
||||
// rawValue: Сырое значение env.
|
||||
// Возвращает: Уровень Serilog.
|
||||
private static LogEventLevel ParseMinimumLevel(string? rawValue)
|
||||
=> Enum.TryParse(rawValue, ignoreCase: true, out LogEventLevel parsedLevel)
|
||||
? parsedLevel
|
||||
: DefaultMinimumLevel;
|
||||
}
|
||||
Reference in New Issue
Block a user