ADR-0047: Structured Logging И Observability Standards
Статус
Принято.
Контекст
Pin — это распределённая система из 10+ микросервисов, работающих в k8s. Без единого observability стандарта:
- логи несравнимы между сервисами (разные field names, разные уровни);
- метрики несогласованы (нет единого naming convention);
- traces не связываются между сервисами (отсутствие propagation);
- при инциденте невозможно восстановить хронологию операции.
Для gambling регулятора необходимо доказать «что произошло» по конкретной транзакции — это невозможно без полноценного observability.
Решение
Structured Logging — Serilog
Каждый log entry содержит стандартный набор полей:
| Поле | Тип | Источник |
|---|---|---|
correlationId | string | HTTP header X-Correlation-Id или генерируется |
requestId | string | HttpContext.TraceIdentifier |
tenantId | int | IActorContext.TenantId |
brandId | int | IActorContext.BrandId |
actorId | Guid | IActorContext.ActorId |
actorType | string | IActorContext.ActorType |
service | string | env var SERVICE_NAME |
environment | string | env var ASPNETCORE_ENVIRONMENT |
Поля добавляются через LogContext.PushProperty() в middleware, один раз — не в каждом log call.
Log Level Семантика
| Level | Когда |
|---|---|
Information | Нормальный flow: операция начата, завершена, ключевые шаги |
Warning | Recoverable: idempotent duplicate, rate limit, validation fail, stale replica |
Error | Unexpected failures: DB error, provider error, unhandled exception |
Critical | Система нефункциональна: DB down, security breach, OOM |
Debug и Trace — только в local/staging, не попадают в production.
Metric Naming Convention
Формат: <service>_<entity>_<operation>_<unit>
Примеры:
accounting_withdrawal_create_duration_ms— гистограммаaccounting_withdrawal_approved_total— counteraccounting_deposit_pending_count— gaugekyc_verification_queue_size— gaugemasstransit_outbox_pending_count— gauge
Все метрики экспортируются через Microsoft.Extensions.Diagnostics.Metrics (OTEL).
Обязательные метрики для каждого handler:
_duration_ms(histogram)_errors_total(counter by error type)_requests_total(counter)
OTEL Tracing
Span создаётся для:
- каждого MediatR handler (command/query);
- каждого EF transaction;
- каждого HTTP/gRPC client вызова;
- каждого Kafka publish/consume;
- каждого Redis get/set;
- каждого Elasticsearch query;
- каждого provider adapter call.
Span attributes минимум: service.name, correlation.id, tenant.id, actor.id, db.statement (без значений параметров).
Correlation Propagation
correlationId распространяется через:
- HTTP header
X-Correlation-Id(inbound и outbound) - Kafka message header
correlation-id - MassTransit envelope field
CorrelationId - Log scope (
LogContext.PushProperty("correlationId", ...))
При отсутствии X-Correlation-Id — генерируется Guid.NewGuid().ToString() в middleware.
Sentry
Unexpected exceptions отправляются в Sentry. Перед отправкой:
SentryBeforeSendCallbackудаляет поля из[Sensitive]атрибута;- Нет raw PII, payment credentials, provider tokens в Sentry context.
Последствия
- По любому
correlationIdможно восстановить полную трассировку операции через все сервисы. - Метрики по единому naming convention собираются в единый Grafana dashboard.
- Log scanning в CI может обнаружить sensitive поля в логах по regex.
- Регулятор получает доказательную базу по любой транзакции.