# Логирование (Serilog) Структурные логи через Serilog. На каждый HTTP-запрос автоматически обогащаются метки `CorrelationId`, `OrgId`, `UserId` через `LogEnrichmentMiddleware`. Любой `ILogger<…>.Log*` внутри пайплайна наследует эти свойства — не нужно тащить их в каждый вызов руками. ## Где приземляются логи Текущая конфигурация (см. `appsettings.json` / `Program.cs`): - **Console** (Serilog.Sinks.Console) — в dev и docker (stdout читается docker logs / journalctl); - **File** (Serilog.Sinks.File) — ротация по дням. Для прод-ELK/Loki в будущем добавляется `Serilog.Sinks.Elasticsearch` или `Serilog.Sinks.Grafana.Loki`; формат вывода уже JSON-friendly, кардинальность лейблов под Loki не вылезает (`OrgId` гранулярный, но не на каждое движение, плюс ограничен текущим парком орг ≪10k). ## Корреляция между запросами Заголовок `X-Correlation-ID`: - если клиент прислал — middleware его уважает (для bridging с upstream'ом); - если нет — генерируется `Guid.NewGuid("N")`. Эхо в response-header чтобы клиент при ошибке отдал support'у конкретный id. ```bash curl -i http://localhost:5081/api/me -H "Authorization: Bearer …" # < X-Correlation-ID: 7f9b3c1a4e5d4f0a8b1c2d3e4f5a6b7c ``` ## Структурные бизнес-логи В коде используем именованные плейсхолдеры — Serilog кладёт каждое поле как отдельное property в LogEvent. Это позволяет фильтровать `OrgId = "..." AND SupplyNumber = "..."` без regex'ов. Хорошо: ```csharp _log.LogInformation( "Supply posted: {SupplyNumber} supplier={SupplierId} store={StoreId} lines={LinesCount} total={Total}", supply.Number, supply.SupplierId, supply.StoreId, supply.Lines.Count, supply.Total); ``` Плохо (теряем структуру, нельзя фильтровать): ```csharp _log.LogInformation($"Supply posted: {supply.Number} ..."); // string interpolation ``` ## Что уже логируется как business event - `Supply posted` — после успешного `/api/purchases/supplies/{id}/post`. - `RetailSale posted` — после успешного `/api/sales/retail/{id}/post`. В развитии: Demand.Post, Transfer.Post, Inventory.Post, Loss.Post — по тому же паттерну. Метки разные, имя события одинаковое для аналитики «сколько проведений в час по типам». ## Запросы (Serilog request logging) `app.UseSerilogRequestLogging()` пишет одну summary-строку на каждый HTTP-запрос: метод, путь, статус, длительность. Дополнительно обогащается `OrgId/UserId/CorrelationId` из LogContext. Шаблон в логе: ``` HTTP POST /api/purchases/supplies/{id}/post responded 204 in 87.3ms { OrgId: "8b0f...", UserId: "57c3...", CorrelationId: "7f9b..." } ``` ## Анти-паттерны - **Не логировать токены/пароли/email-пароли** — даже структурно. Identity events (SignIn / Reset Password) — нет, только статус и user-id. - **Не логировать тело запроса целиком** — может содержать PII. Только конкретные поля по необходимости. - **Не использовать string interpolation в шаблоне** — теряется структура (выше).