ERRORS: Мониторинг и сквозной контроль (End-to-End) системных состояний

Published

June 11, 2026

WarningОграничение публичной документации

В открытом доступе представлена демонстрационная версия метода. В настоящей публичной документации отображены не все шаги, технические сценарии и приватные эндпоинты для системы цифровых симуляторов бизнес-процессов.

  • Полная спецификация метода: Будет доступна только во внутреннем контуре разработки (Confluence / Swagger Enterprise).

1 Начало

1.1 Потоки записи от различных компонентов:

  1. Core Backend (Go): Пишет напрямую. Это основной поставщик логов. Перехватывает системные сбои, падение транзакций PostgreSQL, ошибки авторизации и таймауты сетевых запросов.
  2. ИИ-сервисы и Аналитические пакеты (FastAPI + PM4Py + Whisper + OCR):
  • При синхронном gRPC-сбое они возвращают ошибку в Go, и Go-бэкенд сам логирует её в ClickHouse, обогащая стэк-трейсом из Python.
  • При асинхронной работе (например, PM4Py взял тяжелую задачу из Kafka и упал по Out-Of-Memory) FastAPI-нода пишет в system_error_logs напрямую, указывая в observability_json тег “service”: “pm4py_engine”.
  1. Хранилища и Очереди (PostgreSQL / Kafka / MinIO): Сами по себе они в ClickHouse не пишут. Но когда Go-бэкенд или FastAPI теряют с ними связь, они генерируют лог с каноничным кодом (например, DB_BUSINESS_POSTGRES_DOWN), прикрепляя к нему сырую ошибку драйвера БД.

  2. Клиентское приложение (Flutter): Мобильное приложение может отправлять логи двух типов:

  • Сбои сети: Когда Nginx вообще недоступен и trace_id не создался, Flutter генерирует локальный UUID и шлет лог через специальный легковесный Ingress endpoint логирования.
  • UI-крэши: Если упал рендеринг интерфейса или произошел сбой в бизнес-логике на фронтенде, Flutter отправляет отчет о крэше с текущим trace_id сессии, чтобы инженеры видели, что техническая ошибка на бэкенде привела к «падению» экрана у пользователя.

1.2 Как это выглядит внутри system_error_logs (Пример сквозного сбоя по одному trace_id)

Представим сценарий: Пользователь загрузил аудиозапись бизнес-встречи. Система попыталась её распознать (Whisper), но ИИ-нода упала по таймауту. Если мы сделаем запрос:

SELECT * FROM system_error_logs WHERE trace_id = 'a1b2c3d4...' ORDER BY timestamp

, мы увидим цепочку из 3 записей, которые упали в базу от разных сервисов:

Timestamp   Component (из JSON) Canonical Code  Log Level   Message / Stack Trace

10:00:01.100    go-core AI_WHISPER_REQUEST_SENT INFO    Отправлен gRPC запрос на транскрибацию файла
10:00:06.150    fastapi-ai  AI_WHISPER_TIMEOUT  ERROR   [Python Runtime Error]: CUDA alloc timeout in whisper_model.py:142
10:00:06.155    go-core AI_WHISPER_TIMEOUT  ERROR   gRPC error: DEADLINE_EXCEEDED. Circuit breaker status changed to OPEN.
10:00:06.200    flutter-app UI_RENDER_FALLBACK  WARN    Отображено модальное окно AI_WHISPER_TIMEOUT для пользователя

Логи и метрики ошибок из system_error_logs НЕ должны идти через общую шину Kafka. Они пишутся в ClickHouse напрямую (обычно через буферизацию в памяти бэкенда или легковесные агенты доставки вроде Vector/FluentBit). Использование Kafka для трансляции технических логов в данном ландшафте — это классический антипаттерн, который может привести к «каскадному коллапсу» системы.

1.3 Как правильно организовать запись в ClickHouse system_error_logs?

Есть два каноничных и отказоустойчивых подхода для вашего стека:

Подход А. Прямая асинхронная вставка батчами из Go (In-Memory Buffering) Поскольку Go-бэкенд является оркестратором, он собирает логи в памяти (slice), а официальный драйвер clickhouse-go/v2 раз в 1-2 секунды (или при накоплении 1000 строк) сбрасывает их в ClickHouse одним быстрым INSERT запросом. ClickHouse обожает большие батчи и переваривает их мгновенно, не нагружая процессор.

Подход Б. Использование Vector (Рекомендуемый для Production) Все ваши сервисы (Go, FastAPI, Nginx) просто пишут логи ошибок в стандартный поток вывода (stdout/stderr). Рядом с контейнерами поднимается крошечный, написанный на Rust агент Vector (от Datadog). Он:

  1. Перехватывает логи из консоли.
  2. Сам парсит их, если нужно.
  3. Напрямую пачками заливает их в ClickHouse по HTTP/TCP.

При этом, если ClickHouse на пару секунд уйдет в обслуживание, Vector придержит логи в своем локальном дисковом буфере, не нагружая оперативную память Go или Python.

  • Разработаем детальную схему CDC-репликации (Debezium/Kafka Connect) для контура Process Mining, раз мы затронули тему прохождения бизнес-данных?
  • Напишем структуру конфигурации для Vector, чтобы показать, как логи из stdout сервисов попадают в нашу таблицу system_error_logs?
  • Добавим в реестр ошибок специфичные коды для слоя PM4Py?

Свяжем технические логи бэкенда с реальным пользовательским опытом (UX) на мобильном устройстве через единую матрицу маппинга в ClickHouse.

Поймаем ошибку в логах бэкенда и обеспечим ее сквозную трассировку: от сбоя конкретного метода в Go/Python до понятного и предсказуемого отображения пользователю во Flutter-приложении.

В архитектуре используется единая матрица маппинга ошибок (Unified Error Mapping Matrix), которая собирается, хранится и анализируется в ClickHouse.


[ Сбой в Go / FastAPI ] ──► [ Сквозной Trace ID ] ──► [ Запись в ClickHouse ]
                                                             │
[ Модальное окно / Push ] ◄── [ Маппинг Error Code ] ◄───────┘

2 Сквозной идентификатор: Концепция trace_id

Любая ошибка, возникающая в системе, обязательно сопровождается уникальным ключом trace_id (UUIDv4).

  1. trace_id порождается на Nginx или на Go-бэкенде при старте любого запроса.
  2. Пробрасывается во все gRPC-вызовы к FastAPI (ИИ и Process Mining)
  3. Если происходит сбой, этот trace_id пишется в ClickHouse вместе с техническим логом (Stacktrace).
  4. Этот же trace_id возвращается во Flutter-клиент в JSON-ответе или Push-уведомлении.

3 Маппинг для ключевых сценариев приложения

3.1 Таблица маппинга

1. Код (canonical_code) 2. Уровень (log_level) 3. Статусы (transport_statuses) 4. Получатель (error_target) 5. JSON для Фронтенда (ui_payload) 6. Метрики для ClickHouse (observability_json)
AI_WHISPER_TIMEOUT ERROR {“grpc”: 4, “http”: 504} FLUTTER_CLIENT {“code”: “AI_WHISPER_TIMEOUT”, “presentation”: “MODAL”, “title”: “Ошибка аудио”, “message”: “Превышено время ожидания.”, “is_retryable”: true} {“metric”: “ai_timeout”, “labels”: {“service”: “fastapi”, “model”: “whisper”}}
IAD_AUTH_BAD_REQUEST WARN {“grpc”: 3, “http”: 400} FLUTTER_CLIENT {“code”: “IAD_AUTH_BAD_REQUEST”, “presentation”: “INLINE”, “target_field”: “email”, “message”: “Некорректный формат адреса.”} {“metric”: “contract_fail”, “labels”: {“validator”: “nginx”}}
DB_BUSINESS_POSTGRES_DOWN FATAL {“grpc”: 14, “http”: 500} INTERNAL_SYSTEM null {“code”: “DB_BUSINESS_POSTGRES_DOWN”, “metric”: “db_conn_lost”, “labels”: {“db”: “postgres_business”}}
KAFKA_KRAFT_REPLICA_LAG CRITICAL {“grpc”: null, “http”: null} INTERNAL_SYSTEM null {“code”: “KAFKA_KRAFT_REPLICA_LAG”, “metric”: “kafka_lag”, “labels”: {“cluster”: “kraft_main”}}

4 Структура таблиц маппинга в ClickHouse

При проектировании учитываем главные правила ClickHouse:

  1. Самые частые фильтры (trace_id, log_level, canonical_code, timestamp) выносим в отдельные плоские колонки.
  2. Всю динамическую и специфичную для конкретного сбоя информацию (метки сервиса, аргументы запроса, стэк-трейс, ID пользователя) упаковываем в JSON, чтобы не менять схему данных при добавлении новых микросервисов.
  3. Добавляем TTL (Time To Live), так как в контуре договоренностей было зафиксировано: хранить логи ровно 30 дней.

4.1 Таблица для сбора логов

CREATE TABLE sys_observability.system_error_logs
(
    -- 1. Временная метка с микросекундами (критично для высоконагруженных систем)
    timestamp          DateTime64(6) CODEC(DoubleDelta, ZSTD),
    
    -- 2. Сквозной идентификатор запроса для трассировки
    trace_id           UUID CODEC(ZSTD),
    
    -- 3. Каноничный код ошибки и уровень лога (для быстрой фильтрации и алертов)
    canonical_code     LowCardinality(String) CODEC(ZSTD),
    log_level          Enum8('INFO'=1, 'WARN'=2, 'ERROR'=3, 'CRITICAL'=4, 'FATAL'=5) CODEC(ZSTD),
    
    -- 4. Текстовое сообщение об ошибке (человекочитаемый контекст)
    message            String CODEC(ZSTD),
    
    -- 5. Системные метрики и лейблы из справочника маппинга + динамический контекст
    -- Сюда Go дописывает {"service": "go-core", "user_id": "123", "input_payload_size": 2048}
    observability_json String CODEC(ZSTD),
    
    -- 6. Сырой стэк-трейс или лог ошибки с ИИ-нод (Whisper/OCR) / СУБД / Kafka
    stack_trace        String CODEC(ZSTD),

    -- Индексы для мгновенного поиска по UUID трассировки
    INDEX idx_trace_id trace_id TYPE bloom_filter(0.01) GRANULARITY 1,
    INDEX idx_code canonical_code TYPE set(100) GRANULARITY 1
)
ENGINE = MergeTree()
-- Сортируем сначала по уровню лога (для дашбордов Grafana), затем по времени и коду
ORDER BY (log_level, timestamp, canonical_code)
-- Автоматическая очистка логов старше 30 дней, как зафиксировано в требованиях
TTL timestamp + INTERVAL 30 DAY
SETTINGS index_granularity = 8192;
  • CODEC(ZSTD) и DoubleDelta: Логи содержат много повторяющихся строк (коды ошибок, уровни логов). Эти кодеки уменьшат размер хранилища на диске в 5–7 раз по сравнению со стандартным сжатием.
  • LowCardinality(String) для canonical_code: Ошибок может быть миллиард, но уникальных кодов в нашей системе — всего пара сотен. ClickHouse внутри себя превратит эту колонку в числовые идентификаторы, что сделает поиск по коду мгновенным.
  • Индекс bloom_filter на trace_id: Поиск логов конкретного пользователя по его trace_id среди миллиардов записей без этого индекса превратился бы в полное сканирование диска. Блум-фильтр позволяет ClickHouse сразу пропускать блоки данных, где этого trace_id точно нет.

4.2 Таблица маппинга ошибок


CREATE TABLE sys_observability.error_mapping_directory
(
    canonical_code      String,                  -- Уникальный текстовый ID ошибки (PK)
    log_level           Enum8('INFO'=1, 'WARN'=2, 'ERROR'=3, 'CRITICAL'=4, 'FATAL'=5), -- Плоская колонка для моментальной фильтрации
    transport_statuses  String,                  -- JSON: {"grpc": 3, "http": 400}
    error_target        Enum8('FLUTTER_CLIENT'=1, 'INTERNAL_SYSTEM'=2), -- Кто обрабатывает
    ui_payload          String,                  -- JSON: Данные для фронтенда (включая canonical_code внутри)
    observability_json  String                   -- JSON: Только метрики и контекстные лейблы для логов
) ENGINE = Memory;

5 Что это дает проекту для мониторинга? (Аналитика в ClickHouse)