# Аналитика и observability

## Паспорт документа

- Статус документа: working reference
- Актуально на: 28 марта 2026 года
- Владелец: backend/platform-команда
- Пересмотр: при изменении архитектурных решений, data layer или frontend data patterns
- Область применения: архитектурные и интеграционные справочные документы проекта
- Связанные документы:
  - [Текущее состояние](../project/current-state.md)
  - [Карта API-маршрутов](./api-routes.md)
  - [Платформенный design](../../specs/qadam-platform/design.md)

## Цель документа

Этот документ фиксирует текущее состояние аналитического и observability-контура Qadam: как устроены структурные логи, как работает интеграция с Axiom, как пишутся продуктовые события и где проходят границы текущей реализации.

## 1. Краткий вывод

В проекте уже есть рабочая база для observability и продуктовой аналитики:

- API использует структурные JSON-логи через `nestjs-pino`;
- в production поддерживается дополнительный `@axiomhq/pino` transport при наличии credentials;
- HTTP-трафик и application logs проходят через единый root logger;
- продуктовые события пишутся в append-only таблицу `EventLog` через Prisma;
- есть публичный endpoint `POST /api/v1/track` и server-side tracking через `@TrackEvent`.

При этом полноценной аналитической платформы пока нет:

- нет готового BI/dashboard-контура;
- нет documented retention/архивации для `EventLog`;
- нет выделенного consumer/job-слоя для аналитических пайплайнов;
- нет отдельного визуального monitoring/error-tracking слоя уровня Grafana/Sentry;
- Prometheus-compatible backend metrics baseline уже есть, но внешний uptime/TLS monitoring и alerting ещё не доведены до полного operational контура.

## 2. Источники данных

### 2.1 Структурные application и HTTP-логи

Источник: `apps/api/src/app.module.ts`, `apps/api/src/main.ts`.

Сейчас backend использует:

- `nestjs-pino`;
- `pino-http`;
- `pino-pretty` для non-production;
- `@axiomhq/pino` как optional production transport.

Логирование покрывает:

- HTTP request/response уровень;
- application logs из сервисов и фильтров;
- bootstrap/runtime warnings;
- ошибки записи событий в `EventLog`.

### 2.2 Продуктовые события

Источник: `apps/api/src/modules/tracking/*`, `packages/shared/src/schemas/track-event.ts`, `packages/prisma/schema.prisma`.

В проекте есть два канала продуктового трекинга:

1. `POST /api/v1/track`
2. `@TrackEvent(...)` + `EventLogInterceptor`

Оба сходятся в один write-path:

- `EventLogWriter`
- Prisma write в таблицу `EventLog`

Это важное архитектурное решение: независимо от того, пришло событие с клиента или было сгенерировано на сервере после успешного handler-response, запись делается через одну сервисную точку.

## 3. Логирование API

### 3.1 Pino-конфигурация

`LoggerModule.forRoot(...)` регистрируется в `AppModule`.

Режимы работы:

| Среда | Условия | Поведение |
|---|---|---|
| development/test | любые env | `pino-pretty` |
| production | `AXIOM_TOKEN` и `AXIOM_DATASET` заполнены | stdout JSON + Axiom transport |
| production | credentials отсутствуют | stdout JSON + bootstrap warning |

### 3.2 Что уходит в request log

Через `serializers.req` и `customProps` в лог попадают:

- `method`
- `url`
- `ip`
- `userAgent`
- `browser`
- `platform`
- `mobile`
- `referer`
- `accountId`
- `accountType`

`accountId` и `accountType` вытаскиваются из bearer token без верификации только для enrichment логов. Безопасность на этом не строится: верификация остаётся в guard.

### 3.3 Prisma и Redis в observability-контуре

`PrismaService` и `RedisService` логируют lifecycle-события через `PinoLogger`:

- подключение primary DB;
- подключение replica при наличии;
- пропуск внешних соединений в export/openapi режиме;
- ошибки инфраструктурного уровня.

Это означает, что data layer уже встроен в общий observability-контур, а не живёт как «немая» библиотека.

### 3.4 Monitoring baseline API

Источник: `apps/api/src/modules/monitoring/*`.

На backend теперь есть минимальный operational monitoring contour:

- `GET /api/v1/health` и `GET /api/v1/health/live` для быстрого liveness-check;
- `GET /api/v1/health/ready` для readiness-check с реальной проверкой PostgreSQL и Redis;
- `GET /api/v1/metrics` для Prometheus-compatible метрик.

Важно:

- `GET /metrics` сознательно доступен только с loopback-адресов сервера;
- readiness публикует machine-readable status и latency по `database` и `redis`;
- backend дополнительно пишет HTTP/runtime metrics через `prom-client` (`http_requests_total`, `http_request_duration_seconds` и default process metrics).

Это ещё не полный monitoring stack. Сейчас это именно baseline observability слоя, на который позже можно вешать uptime monitoring, scrape, dashboards и alerting.

## 4. EventLog и продуктовая аналитика

### 4.1 Таблица `EventLog`

Источник истины: `packages/prisma/schema.prisma`.

`EventLog` устроен как append-only таблица:

- `id`
- `source`
- `actorId`
- `sessionId`
- `action`
- `entityType`
- `entityId`
- `payload`
- `metadata`
- `createdAt`

Индексы уже есть по:

- `source, createdAt`
- `actorId, createdAt`
- `entityType, entityId`
- `createdAt`
- `action, createdAt`

Этого достаточно для базового аудита действий, ручной аналитики и будущей выгрузки в отдельный аналитический контур.

### 4.2 Клиентский tracking endpoint

`POST /api/v1/track` публичен и принимает body по `TrackEventSchema`:

- `action`
- `sessionId`
- `url`
- `entityType`
- `entityId`
- `payload`
- `metadata`

При записи дополнительно обогащаются:

- `userAgent`
- `referer`
- `cookieId`
- request IP
- `source` (`guest`, `buyer`, `seller`, `admin` и т.д.)

### 4.3 Server-side tracking

`@TrackEvent('verb_noun')` навешивается на controller methods.

`EventLogInterceptor` после успешного ответа пишет событие в `EventLog`. Это даёт два важных свойства:

- tracking не размазан по контроллерам вручную;
- ошибки handler-а не создают ложных «успешных» событий.

## 5. Env и эксплуатация

Ключевые переменные:

- `AXIOM_TOKEN`
- `AXIOM_DATASET`
- `DATABASE_URL`
- `DATABASE_REPLICA_URL`
- `REDIS_URL`

Поведение сейчас такое:

- если `AXIOM_TOKEN` / `AXIOM_DATASET` пустые, backend не падает;
- в production остаётся stdout JSON logging;
- warning печатается один раз на bootstrap.

Это правильно для graceful degradation, но не заменяет осознанного мониторинга наличия реальной отгрузки логов.

## 6. Что уже хорошо

- observability-контур унифицирован вокруг Pino;
- data layer и tracking уже пишут в один инженерный стек, а не в разрозненные логеры;
- `EventLog` уже лежит в основной схеме данных и доступен через Prisma;
- текущий контур не блокирует запросы из-за аналитики: запись событий fire-and-forget;
- наличие `Axiom` уже предусмотрено кодом и env-моделью.

## 7. Чего пока не хватает

- Нет documented event taxonomy для product/business analytics.
- Нет списка обязательных событий по buyer/seller/admin critical flows.
- Нет retention policy и архивации `EventLog`.
- Нет dashboards/queries/runbook для Axiom.
- Нет alerting на деградацию логирования и runtime probes.
- Нет отдельной аналитической витрины, materialized views или ETL-слоя.
- Нет автоматических тестов именно на observability-конфигурацию и tracking semantics.
- Нет внешнего uptime/TLS monitoring, несмотря на появившийся backend metrics/readiness baseline.

## 8. Рекомендуемые следующие шаги

1. Зафиксировать канонический словарь событий: `action`, `source`, `entityType`.
2. Выделить P0-события для buyer/seller/admin критичных потоков.
3. Добавить documented runbook по проверке Axiom ingestion.
4. Определить retention/архивацию для `EventLog`.
5. Подготовить отдельный analytics backlog поверх уже существующего `EventLog`, а не строить новую параллельную систему.

## 9. Правило идентификаторов для downstream analytics

Для аналитического отдела действует единое обязательное правило:

- продуктовые идентификаторы из backend и operational PostgreSQL приходят в аналитический контур как внешние `*_id_ext`;
- raw/staging слой не нормализует их в `bigint` “на лету”, а сохраняет как внешний технический ключ;
- внутри warehouse/analytics DB каждая сущность получает собственный внутренний surrogate key `bigint` (`*_id`);
- соответствие между внешним и внутренним идентификатором ведётся через отдельный mapping-слой `1:1`;
- все факты, витрины и тяжёлые join внутри аналитики строятся только по внутренним `bigint`, а `*_id_ext` используется для загрузки, сверки и трассировки обратно в продуктовый контур.

Это правило должно применяться автоматически ко всем аналитически значимым сущностям, а не оговариваться вручную по одной таблице за раз.
