Webrium
Подписаться

Что писать в трассировку, а что нет

Как проектировать трассировку под дежурного: какие поля писать в спанах, а какие выносить в архив, чтобы трейс сразу показывал, где сломался запрос

Четыре показателя-поля трейса: ID запроса, шаг вызова и код ошибки в норме, а тело запроса уходит за шкалу — его место в архиве, а не в трейсетело запроса

У трассировки два способа не сработать. Первый: открыть трейс во время инцидента и увидеть аккуратную лесенку шагов — вот вход, вот ошибка, вот ответ, но почему шаг решил упасть, не сказано. Второй: открыть трейс и утонуть — шагов больше, чем нужно, в каждом полное тело запроса, и ответ на вопрос «где сломалось» приходится выкапывать из-под мусора.

В обоих случаях сломано одно и то же: схему проектировали не для того, кто её читает. А читает её дежурный — в худший момент и без контекста. Всё, что дальше, выводится из этого рассогласования: спокойный автор схемы против ночного читателя.

Правило одного вопроса

Прежде чем перечислять поля, зафиксируем критерий. Списки ниже из него выводятся, а не придумываются.

Вопрос к каждому полю один: меняет ли его значение следующее действие дежурного? Если да — поле принадлежит трейсу. Если нет — это материал архива, и архивом должна быть другая система хранения: дешёвая и с другим поиском.

Отсюда сразу следует, чем трассировка не является: протоколом всего. Она отвечает на вопрос «что случилось с этим запросом и что делать дальше», и всё, что этому ответу не служит, — издержка. Про разницу между логами и трассировкой шагов у нас есть отдельный материал; здесь важна только её функция при разборе инцидента.

Что писать

Границы шагов. Спан — шаг, у которого есть собственная причина упасть. Проверка: если этот шаг вернёт ошибку, изменится ли ответ запроса или следующее действие дежурного? Не изменится — это не спан, а строка лога. Типичный лишний спан — разбор входных данных, который не падает отдельно от родительского шага.

Вход и выход на границе. Ровно столько, чтобы ответить «что пришло и что ушло», без полного тела. Форма зависит от того, что это за данные:

  • объект рендерится из шаблона — промпт, запрос по образцу — пишите идентификатор шаблона и значения переменных: результат рендера восстанавливается, хранить его незачем;
  • данные — размер, хэш и укорочанное начало той длины, которую вы себе разрешите; полное тело, если понадобится, лежит по ссылке в другом хранилище;
  • идентификаторы — целиком: они маленькие и несущие.

Развилки. Везде, где код выбирает путь, пишите значение предиката и имя выбранной ветки. Не контекст целиком, а исход решения. Без этого трейс показывает, что шаг случился, но не почему пошли именно туда.

Внешние вызовы. Имя сервиса, операция, статус, число повторов. Повтор — событие само по себе: он меняет действие (поднять таймаут? сменить инстанс?).

Ошибки. Текст ошибки дословно, в том спане, где она случилась. Не класс, а текст: «отказ соединения» и «таймаут соединения» ведут к разным следующим шагам.

Длительность. Храните как долю от длительности всего запроса, а не как абсолют. Бюджеты у всех свои, а доля сразу показывает, какой шаг съел запрос.

Чего в трейсе быть не должно

Полное тело запроса, повторённое в каждом спане, — объём, который растёт с трафиком при неизменной информативности. Информация живёт на границах шагов, а не внутри.

Итерации цикла как отдельные спаны. Агентский цикл — самый наглядный случай: каждая итерация порождает дочерний спан, и структура трейса тонет в однотипных повторах. Вместо этого один спан на цикл: сколько итераций, по какой причине вышли, на какой упало.

Производные значения. Писать одновременно размер, хэш, префикс и полное тело — значит не выбрать ничего. Поле, вычислимое из уже записанных, не пишется.

Статичная конфигурация. То, что одинаково у всех запросов сервиса, принадлежит метаданным сервиса, а не каждому трейсу.

Секреты и персональные данные. Что считать чувствительным, решается в момент записи: задним числом из хранилища трейсов это не вычищается — это отдельный проект, а не кнопка. Приём — хэшировать идентификаторы людей и никогда не писать токены.

Поля «на всякий случай». Поле, которое никто не открывает во время инцидента, — не ноль, а минус: оно стоит хранения, замедляет поиск и приучает читателя трейсу не доверять.

Тупик: «запишем всё, отфильтруем потом»

Первая мысль при проектировании схемы обычно такая: записать всё, лишнее отбросить при чтении, а объём победить сэмплированием. Мысль привлекательная — она не требует решений сейчас и звучит как осмотрительность.

Не работает она вот почему.

Сэмплирование не спасает. Чтобы выборка сохранила именно упавший запрос, решение надо принимать, когда исход уже известен. Выборка на входе решает до развязки и выкидывает редкие падения вместе со скучным большинством. Выборка на выходе сначала собирает и записывает трейс целиком — полная цена на пути записи уже заплачена, экономия остаётся только в хранении.

«Потом» не наступает само. У фильтрации нет дедлайна, а у шумных полей появляются потребители: на них строятся графики, от них зависит чужой код. Удалить поле, на которое кто-то смотрит, — ломающее изменение.

Чувствительное, записанное «временно», остаётся навсегда.

И главное: цена мусора падает не на автора схемы, а на читателя. Инструмент, который должен сокращать поиск, удлиняет прокрутку.

Правильный порядок обратный: сначала схема — по правилу одного вопроса, — потом сэмплирование под то, что осталось. А если трассировку у вас сейчас собирают из логов, склеивая их по идентификатору запроса, — это отдельная большая тема, мы разбирали её в материале «Трассировка шагов вместо логов».

Практика: пересобрать схему по шагам

  1. Инвентаризация пути. Выпишите стадии запроса от входа до ответа и к каждой примените проверку «своя причина упасть». Прошедшие становятся спанами, непрошедшие вливаются в родителя. Если стадии не вспоминаются по памяти — идите по коду от точки входа; заодно станет ясно, почему трейс был пустым.

  2. Представления на границах. Для каждого спана выберите форму входа и выхода: шаблон плюс переменные, размер-хэш-префикс, идентификатор целиком. Условие применимости: если тела у вас маленькие, нечувствительные и хранение дешёвое, полные тела на границах допустимы — вся политика выше про дорогой случай.

  3. Развилки. Списком выпишите предикаты, которые выбирают путь, и добавьте к каждому значение и имя ветки. Это самый быстрый способ закрыть дыру из раздела про развилки.

  4. Циклы. Один спан на цикл: счётчик итераций, причина выхода, номер падающей итерации. Расширять до поитерационных спанов стоит, только если деталь конкретной итерации меняет действие дежурного.

  5. Чувствительное. Белый список полей, которые можно писать; всё новое — через явное добавление. Условие применимости: если схема меняется с каждым релизом, белый список гниёт и его начинают обходить — тогда чёрный список плюс проверка на ревью.

  6. Ночной тест. Откройте один здоровый трейс и один упавший. На упавшем ответ «что сломалось и что делать дальше» должен находиться без открытия исходников; если он тонет в прокрутке — в схеме лишнее. Тест бесплатный, гонять его стоит после каждого изменения схемы.

И последнее: схема трассировки — часть регламента дежурства. Разбор инцидента начинается с чтения трейса, и трейс без ответа останавливает регламент на первом шаге. Про сам порядок действий у нас есть материал «Дежурство по агенту».

Позиция редакции

Мы считаем, что трассировка — инструмент принятия решений, а не архив. Писать нужно то, что меняет следующее действие читателя; остальное либо не пишется, либо уходит в другое хранилище по ссылке. Все списки выше — следствия этого тезиса, а не самостоятельные правила.

Мы готовы назвать условие, при котором эта позиция неверна. Если главный потребитель трейса — не человек, а автоматический анализ, критерий «меняет действие дежурного» не применим. Скажем, вы исследуете сами ответы модели как продукт: тогда тело ответа и есть предмет изучения, полнота важнее читаемости, и это уже не трассировка, а датасет — с требованиями датасета.

Близкий случай — молодая система, где ещё неизвестно, как она отказывает. Пока непонятно, какие поля меняют действия, щедрая трассировка — честная разведка, а не неряшливость. Но у разведки должен быть срок: без него она и есть тупик из раздела выше.

С чем это связано

Метка над заголовком говорит, зачем туда идти: продолжить тему, перейти к практике или увидеть возражение.

Сетка из пятнадцати ячеек изображает ленту лога: девять заняты событиями, а рядом отдельный счётчик показывает лишь четыре причины решений — лента отвечает «гдепричины решений
Углубление

Трассировка шагов вместо логов

Лог отвечает не на тот вопрос: он показывает, где упало, но не почему агент решил именно так. Как заменить ленту логов на дерево шагов — и почему такая трассировка ломается.

Линия жёстких отказов агента пробивает порог и зажигает сигнал дежурному, а мягкие отказы остаются под порогом и сигнала не даютжёсткий отказэпизоды дежурства
Усиление

Дежурство по агенту: порядок действий

Практическое следствие: по трассе одного эпизода дежурный по агенту отличает жёсткий отказ от мягкого и находит причину без рестарта и отката.

Поле, разделённое швом: элементы OpenTelemetry пересекают границу между тем, что уже стало стандартом, и тем, что остаётся удобным клеемСтало стандартомУдобный клей
Углубление

OpenTelemetry: что стало стандартом, а что ещё нет

Какие слои OpenTelemetry можно считать настоящими стандартами, а какие остались удобным клеем — и почему граница проходит именно между ними. Отдельно разбирается, стандарт ли коллектор или всё-таки продукт.

Два пути узнать о поломке сайта: короткий дорогой — внешний робот-мониторинг сообщает сразу; длинный дешёвый — владелец узнаёт последнимробот — сразусам — последним
Просто

Как вообще узнают, что сайт сломался

Та же тема, но без терминов: почему владелец узнаёт о поломке последним и почему робот, который открывает страницу, врёт в обе стороны.

Письмо по вторникам

Один разбор недели и короткий список того, что изменилось. Без дайджестов на сорок ссылок.

Начните вводить — материалы появятся здесь.

выбрать · Enter открыть · Esc закрыть