Наблюдаемость в продакшене: минимальные логи, метрики и трейсы на первый день, плюс понятный поток триажа для жалоб "у нас медленно".

Когда новая версия попадает в продакшен, проблемы почти всегда звучат одинаково: «у нас медленно», «не открывается», «иногда падает». Без базовых сигналов поиск причин превращается в угадайку и споры про «то ли сеть, то ли база, то ли фронт».
Минимальная наблюдаемость нужна не ради красивых графиков. Она нужна, чтобы за 5 минут ответить на главные вопросы и решить, что делать дальше: откатывать релиз, выключать фичу, расширять лимиты или искать конкретный баг.
Продакшен должен быстро отвечать на четыре вопроса:
Без этого один и тот же симптом выглядит одинаково, хотя причины разные. Например, запросы к API стали в 2 раза медленнее. Это может быть переставший использоваться индекс, рост ретраев к внешнему сервису или новый код, который делает лишние запросы. Если есть только «пользователи жалуются», вы начинаете с конца.
Минимум обычно состоит из трех типов сигналов и одной связки между ними:
Логи: редкие, но информативные события с контекстом.
Метрики: числа, которые показывают тенденции.
Трейсы: цепочка вызовов, где видно, на чем тратится время.
Корреляция: общий request_id или trace_id, чтобы из метрики перейти к трейсу, а из трейса - к логам.
Важно не пытаться «собрать все» в первый день. Лучше меньше сигналов, но стабильных: одинаковые поля в логах, одинаковые метки у метрик, один способ прокидывать идентификатор запроса. Тогда при первой же аварии вы не будете чинить наблюдаемость вместо продукта.
Наблюдаемость часто ломается об одно: команда не знает, как выглядит нормальная работа. В итоге любой график можно трактовать по-разному, а фраза «стало медленно» превращается в спор вместо действий.
Начните с 2-3 ключевых пользовательских действий. Это не список всех API и не карта микросервисов. Это то, ради чего люди вообще открывают продукт: например, вход в аккаунт, поиск, оформление заказа.
Дальше задайте две цели, которые легко проверить: доступность и время ответа. Для времени ответа берите перцентиль, обычно P95 (95% запросов быстрее этого значения). Среднее время часто маскирует проблемы.
Пример: вы договорились, что «поиск» в норме отвечает за 1,2 секунды по P95, а доступность 99,9%. Этого уже достаточно, чтобы отличать обычные колебания от реальной деградации.
Теперь зафиксируйте, что вы называете инцидентом. Важно указать не только порог, но и длительность, иначе будете реагировать на каждую минутную яму. Удобная формула: «если хуже порога дольше N минут». Например:
И отдельно разведите три класса проблем, потому что расследуются они по-разному:
Если это записано одной страничкой (хоть в заметках), дальше проще: вы знаете, какие метрики строить, какие логи искать и что проверять первым, когда приходит «у нас медленно».
Если метрики отвечают на вопрос «когда стало хуже», то логи помогают понять «почему». В продакшене важнее не количество строк, а то, насколько быстро вы находите нужную запись и связываете ее с конкретным запросом.
С первого дня договоритесь о структурированных логах (обычно JSON). Так их проще искать, фильтровать и сопоставлять между сервисами.
Минимальные поля, которые стоит писать в каждую запись:
time (единая зона для всех)level (info, warn, error)service (имя сервиса или компонента)env (prod, stage)request_id (или trace_id, если используете трассировку)request_id должен приходить с входящим запросом или генерироваться на границе системы и прокидываться дальше: в вызовы сервисов, в задачи очереди, во внешние API.
Не пытайтесь логировать все подряд. Логи должны фиксировать ключевые точки, где запрос может «сломаться» или стать медленным: начало обработки, внешние вызовы, ретраи, таймауты, ошибки. В info пишите только то, что реально помогает разбирать инциденты, иначе полезные записи утонут.
Когда логируете ошибку, добавляйте контекст, который помогает действовать:
user_id как число)warn оставляйте для деградации, когда система еще работает, но уже «плохо»: ретрай с 3 попытки, внешний сервис отвечает дольше порога, очередь растет. error используйте для явных отказов.
Отдельно зафиксируйте правила безопасности. Не пишите пароли, токены, ключи, полные персональные данные и большие тела запросов. Если нужен фрагмент, сохраняйте «срез»: длину, тип, несколько первых символов, хеш или маску.
Пример: пользователь жалуется «оформление заказа тормозит». По request_id вы находите старт запроса (info), затем внешний вызов (warn: 2.4s), ретрай (warn), потом таймаут (error). Уже понятно, где копать дальше.
Метрики нужны, чтобы быстро понять: проблема в росте нагрузки, в ошибках или в том, что сервис уперся в ресурс. Для минимума достаточно договориться о нескольких показателях и считать их одинаково во всех сервисах.
Latency (задержка), Traffic (трафик), Errors (ошибки), Saturation (насыщение по ресурсам или очередям). Смотрите не только среднее, а перцентили.
Начните с метрик уровня запроса, одинаковых для API, фоновых задач и ключевых операций:
Дальше добавьте метрики зависимостей. Они часто объясняют «почему стало медленно», даже если ваш код не менялся. Для PostgreSQL полезны время запросов, количество активных соединений, занятость пула, блокировки. Для кэша - hit rate и время ответа. Для внешних API - время ответа, ошибки, таймауты и ретраи. Если бэкенд на Go, отдельно следите за количеством горутин и паузами GC.
Алерты должны ловить деградацию, а не «каждый чих». Держите их простыми и привязанными к пользовательскому эффекту:
Полезная привычка: разделяйте алерты на «срочно разбудить» и «посмотреть утром».
Трейсы полезны там, где по логам и метрикам видно только симптом («медленно»), но непонятно, где теряется время. Максимум отдачи они дают в микросервисах, при работе с очередями, при вызовах внешних API и в местах со сложными запросами к базе.
Идея простая: у каждого входящего запроса есть trace_id и внутри него цепочка span-ов. trace_id живет до конца обработки и прокидывается дальше: в БД, во внешние сервисы, в сообщения очередей. Тогда жалоба «страница открывается 8 секунд» превращается в картинку: 200 мс на обработчик, 6.5 с на внешний сервис, 1.1 с на БД.
Начните с самых дорогих и частых операций:
Чтобы трейсы можно было фильтровать и сравнивать, добавляйте понятные атрибуты: route (шаблон пути), status_code, метод, имя сервиса, технический контекст клиента (например, tenant_id, тариф/план без персональных данных). Для ошибок полезны тип и короткое сообщение.
Практичный старт по семплингу: 100% для ошибок и медленных запросов и небольшой процент для успешных (часто 1-10% в зависимости от нагрузки). Если есть возможность, сохраняйте больше трейсинга для важных маршрутов вроде логина и оплаты.
Наблюдаемость начинает работать по-настоящему, когда можно быстро ответить на вопрос: «Эта ошибка и этот всплеск задержек - про один и тот же запрос или про разные вещи?». Для этого логи, метрики и трейсы нужно склеить общими идентификаторами и одинаковыми именами.
Первый кирпич - единый идентификатор запроса. Для HTTP это обычно request_id и/или trace_id. Важно, чтобы он:
Второй кирпич - перенос контекста между компонентами, включая фоновые задачи. Частая ловушка: в HTTP все хорошо, а как только запрос положили в очередь или запустили джобу, идентификатор теряется. Договоритесь, что минимум (trace_id, request_id, account_id/user_id, job_id) хранится в сообщении очереди или параметрах задания и снова попадает в логи и трейсы при обработке.
Третий кирпич - одинаковые имена. Один и тот же endpoint не должен называться по-разному в трех местах. Выберите формат и придерживайтесь его в метриках, в спанах и в логах.
И держите простую «карту сервисов»: компоненты и зависимости (сервисы, PostgreSQL/кэш, очереди, внешние API, точки входа web/mobile). На старте этого достаточно, чтобы быстрее находить первопричину.
За один день реально поставить базу, если ограничиться минимумом и сразу договориться о корреляции между логами, метриками и трейсами.
Выберите 3 главных пользовательских сценария. Для каждого определите по 3 метрики: доля ошибок, P95 задержки и объем (RPS/количество операций).
Добавьте структурированные логи в точки входа (HTTP handler, очередь, cron) и в обработку ошибок. В каждом логе держите одинаковые поля: request_id/trace_id, endpoint/operation, статус (ok/error), latency_ms и краткую причину ошибки. Чтобы не утонуть в шуме, пишите логи ровно в двух местах: на входе и при ошибке.
Подключите базовые метрики HTTP и инфраструктуры: количество запросов, доли 5xx/4xx, распределение задержки (P50/P95), плюс CPU, память, диск, пул соединений к БД. Первые алерты делайте грубыми: рост 5xx и P95 выше нормы в течение 5-10 минут.
Добавьте трейсы там, где чаще всего пропадает время: внешние HTTP вызовы и запросы к базе данных. Включите захват ошибок в трейс и прокиньте trace_id в логи.
Прогоните тестовый трафик и убедитесь, что по одному request_id виден весь путь: входной запрос -> ключевые шаги -> БД/внешние вызовы -> итоговый статус.
Когда кто-то пишет «все тормозит», важно быстро сузить круг причин.
Сначала зафиксируйте симптомы так, чтобы их можно было проверить: кто жалуется (один клиент или многие), что именно медленно, с какого времени, как часто, есть ли точные примеры (время запроса, аккаунт, регион, устройство).
Дальше двигайтесь по шагам:
Проверьте доступность и явные ошибки за нужный период. 5xx, таймауты, рост отказов, заполнение очередей, перезапуски.
Подтвердите деградацию по задержке. Сравните P95/P99 с «обычным» уровнем и найдите самый проблемный endpoint или экран.
Откройте трейсы для медленных запросов. Найдите самый длинный span: БД, внешнее API, кэш, очередь, тяжелая бизнес-операция.
Перейдите из трейса в логи по trace_id. Проверьте ретраи, лимиты, ожидание блокировок, переполнение пула соединений, долгие SQL, повторные вызовы.
Примите временное решение и зафиксируйте его. Откат/отключение фичи, временное увеличение ресурсов, деградация при сбое внешнего сервиса (кеш, заглушка, таймауты, снижение частоты).
Простой пример: пользователи мобильного приложения жалуются, что «оплата» стала открываться 15 секунд. Метрики показывают рост P95 только у одного endpoint. В трейсе самый длинный span - запрос в PostgreSQL. По trace_id в логах видно: после вчерашнего изменения включился ретрай, а запрос держит блокировку. Временное решение: откатить фичу или убрать ретрай, затем исправить запрос и добавить индекс.
Наблюдаемость часто «не работает» не потому, что инструменты плохие, а потому что сигналы собраны без правил.
Когда пишут «все подряд», поиск превращается в гадание. Договоритесь, какие события важны: вход запроса, ключевые шаги, ошибки, ретраи, внешние вызовы.
График показывает деградацию, но по логам нельзя найти конкретные запросы, а трейсы живут отдельно. Решает простое правило: у каждого запроса должен быть один и тот же request_id или trace_id везде.
Шумные алерты приучают игнорировать уведомления. Оставьте только те сигналы, которые требуют действия прямо сейчас, и привяжите их к понятным порогам и окнам времени.
Пять типичных промахов, которые стоит исправить в первую очередь:
trace_id/request_id) в логах, метриках и трейсеroute, method, status, db.system, peer.service)Чтобы наблюдаемость работала, нужна простая привычка: перед релизом проверить минимум, а при инциденте собирать одни и те же факты.
request_id и/или trace_id, и они попадают в логи.endpoint, код ответа, время, краткая причина.Чтобы инциденты не повторялись, заведите два артефакта: короткий шаблон отчета (что случилось, как нашли, как починили, почему произошло, что делаем дальше) и список принятых действий (например, «добавили индекс», «ограничили таймаут», «поставили алерт на рост очереди»).
Если вы делаете приложение в TakProsto (takprosto.ai), удобно заранее фиксировать SLO в planning mode, а для безопасных выкладок использовать snapshots и rollback. Это помогает не спорить «на глаз», а опираться на договоренности и данные.
Минимум нужен, чтобы за несколько минут ответить на четыре вопроса: что сломалось, где, кого задело и когда началось/что изменилось. Это помогает быстро выбрать действие (откат, отключение фичи, увеличение лимитов, поиск конкретного бага), а не спорить «то ли сеть, то ли база».
Выберите 2–3 ключевых пользовательских сценария (например, логин, поиск, оплата) и зафиксируйте для них:
Дальше определите, что считается инцидентом: «хуже порога дольше N минут», чтобы не реагировать на минутные провалы.
Пишите структурированные логи (обычно JSON) и держите один формат во всех сервисах. Минимальные поля:
Логируйте то, что помогает разбирать инциденты, а не «все подряд»:
Практичное правило: в info — только полезные точки, warn — деградация (ретраи, медленные зависимости), error — явный отказ.
Не пишите в логи:
Если нужен контекст — сохраняйте безопасный «срез»: длину, тип, первые символы в маске, хеш, user_id как число без лишних данных.
Минимальный набор для уровня запросов:
Этого хватает, чтобы увидеть «не работает», «иногда ошибается» и «медленно».
Ставьте простые алерты, привязанные к пользовательскому эффекту и с окном 5–10 минут:
Разделяйте «срочно разбудить» и «посмотреть утром», иначе алерты начнут игнорировать.
Трейсы нужны, когда по метрикам видно «медленно», но непонятно, где теряется время. Стартовая разметка:
Обязательно прокидывайте trace_id дальше, чтобы цепочка не обрывалась.
Сделайте единый идентификатор (request_id/trace_id) обязательным:
Так вы можете перейти от всплеска на графике к конкретному трейсу и дальше к нужным логам без ручного «сведения» событий.
Держите простой поток:
trace_id: ретраи, лимиты, блокировки, пул соединений, долгие SQL.Цель — быстро сузить круг причин и не тратить время на догадки.
time (единая зона)level (info/warn/error)serviceenv (prod/stage)request_id или trace_idГлавное — чтобы идентификатор запроса прокидывался дальше по цепочке вызовов.