# Логирование (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 в шаблоне** — теряется
  структура (выше).
