ERRORS: Мониторинг и сквозной контроль (End-to-End) системных состояний
В открытом доступе представлена демонстрационная версия метода. В настоящей публичной документации отображены не все шаги, технические сценарии и приватные эндпоинты для системы цифровых симуляторов бизнес-процессов.
- Полная спецификация метода: Будет доступна только во внутреннем контуре разработки (Confluence / Swagger Enterprise).
1 Начало
1.1 Потоки записи от различных компонентов:
- Core Backend (Go): Пишет напрямую. Это основной поставщик логов. Перехватывает системные сбои, падение транзакций PostgreSQL, ошибки авторизации и таймауты сетевых запросов.
- ИИ-сервисы и Аналитические пакеты (
FastAPI+PM4Py+Whisper+OCR):
- При синхронном gRPC-сбое они возвращают ошибку в Go, и Go-бэкенд сам логирует её в
ClickHouse, обогащая стэк-трейсом изPython. - При асинхронной работе (например,
PM4Pyвзял тяжелую задачу изKafkaи упал по Out-Of-Memory)FastAPI-нода пишет в system_error_logs напрямую, указывая в observability_json тег “service”: “pm4py_engine”.
Хранилища и Очереди (
PostgreSQL/Kafka/MinIO): Сами по себе они вClickHouseне пишут. Но когда Go-бэкенд илиFastAPIтеряют с ними связь, они генерируют лог с каноничным кодом (например,DB_BUSINESS_POSTGRES_DOWN), прикрепляя к нему сырую ошибку драйвера БД.Клиентское приложение (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). Он:
- Перехватывает логи из консоли.
- Сам парсит их, если нужно.
- Напрямую пачками заливает их в ClickHouse по HTTP/TCP.
При этом, если ClickHouse на пару секунд уйдет в обслуживание, Vector придержит логи в своем локальном дисковом буфере, не нагружая оперативную память Go или Python.
- Разработаем детальную схему CDC-репликации (
Debezium/KafkaConnect) для контура 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).
- trace_id порождается на Nginx или на Go-бэкенде при старте любого запроса.
- Пробрасывается во все gRPC-вызовы к FastAPI (ИИ и Process Mining)
- Если происходит сбой, этот trace_id пишется в ClickHouse вместе с техническим логом (Stacktrace).
- Этот же 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:
- Самые частые фильтры (trace_id, log_level, canonical_code, timestamp) выносим в отдельные плоские колонки.
- Всю динамическую и специфичную для конкретного сбоя информацию (метки сервиса, аргументы запроса, стэк-трейс, ID пользователя) упаковываем в JSON, чтобы не менять схему данных при добавлении новых микросервисов.
- Добавляем 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;