[Перевод] Эй, где моя ошибка? Как OpenTelemetry фиксирует ошибки

В зависимости от языка программирования, на котором вы привыкли работать, у вас могут быть определённые представления о том, что такое ошибка, что такое исключение и как его следует обрабатывать. Например, в Go нет исключений — отчасти для того, чтобы отбить у программистов желание причислять слишком много обычных ошибок к «исключительным». С другой стороны, в Java, Python и т. п. поддержка для работы с исключениями встроена.

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

  • Ошибка может находиться в бэкенде не там, где ожидалось, или выглядеть не так, как ожидалось.

  • Как тип спана влияет на сообщения об ошибках.

  • Чем отличаются ошибки в спанах и логи.

73036f87fcf7fa0a4c046051afe73954.png

Чем ошибки отличаются от исключений

Прежде чем поговорить о том, как OpenTelemetry работает с ошибками и исключениями, давайте разберёмся, что это такое и чем они отличаются друг от друга. Существует куча определений этих терминов. Мы остановились на следующих вариантах (обратите внимание, что это не официальная терминология OTel; это определения, принятые в отрасли):

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

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

В некоторых языках (Python и JavaScript) ошибки и исключения рассматриваются как синонимы. В других (PHP и Java) это не так. Понимание разницы между ошибками и исключениями очень важно для эффективной работы с ними, поскольку позволяет применять более тонкие стратегии для обработки сбоев и восстановления после них.

Обработка ошибок в OpenTelemetry

Так как же OTel справляется со всеми этими концептуальными различиями между языками? Выручает спецификация. Она стандартизирует реализацию на всех языках, и именно на неё ориентируются программисты, работающие над различными частями проекта.

Поскольку языковые API и SDK реализуют спецификацию, существуют общие правила, запрещающие использовать то, что в ней не описано. Они выступают как база, помогающая организовать контрибуцию в проект. На практике есть несколько особенностей. Например, язык может прототипировать новую функцию как часть обновления спецификации, но функция может быть опубликована (обычно как альфа-версия или экспериментальная) ещё до того, как обновление будет принято.

Другое исключение — когда язык отклоняется от спецификации. Хотя обычно так делать не рекомендуется, но иногда есть веские причины, связанные с конкретным языком. Поэтому спецификация допускает определённую гибкость для каждого языка, позволяя реализовывать функции настолько свободно, насколько это возможно. Например, в большинстве языков реализована функция RecordException (например, в Python), а в Go то же самое делает RecordError.

Есть даже матрица соответствия спецификации для всех языков, но самую свежую информацию можно получить в репозитории конкретного языка. Теперь пора разобраться с тем, как ошибки обрабатываются в OTel. Начнём с того, как они выглядят в спанах и логах.

Ошибки в спанах

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

Скриншот, показывающий спаны в трейсе
Скриншот, показывающий спаны в трейсе

Добавляем в спаны метаданные

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

У спанов также есть поле span kind, которое содержит дополнительные метаданные, помогающие разработчикам устранять ошибки. OTel определяет несколько видов span kind’ов:

  • client — для исходящих синхронных удалённых вызовов (например, исходящий HTTP-запрос или вызов БД);

  • server — для входящих синхронных удалённых вызовов (например, входящий HTTP-запрос или удалённый вызов процедуры);

  • internal — для операций, которые не пересекают границы процессов (например, инструментирование вызова функции);

  • producer — для создания задачи, которая впоследствии может быть асинхронно обработана (например, вставлена в очередь задач);

  • consumer — для обработки задачи, созданной продюсером, которая может начаться спустя долгое время после завершения спана продюсера

Span kind определяется автоматически используемыми библиотеками инструментирования.

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

Добавляем в спаны события

Событие спана — это структурированное сообщение лога, встроенное в спан. События спана обогащают его, предоставляя описательную информацию о спане. У событий спанов также могут быть собственные атрибуты.

Ранее мы упоминали метод под названием RecordException. Согласно спецификации (выделено автором), «для облегчения записи исключения языки ДОЛЖНЫ предоставлять метод RecordException, если язык использует исключения… Сигнатура метода определяется каждым языком и может быть перезагружена по мере необходимости».

Поскольку Go не поддерживает «традиционную» концепцию исключений, в нём есть RecordError, который, по сути, делает то же самое. Придётся сделать дополнительный вызов, чтобы установить статус в Error (если он должен быть таким), поскольку автоматически статус не устанавливается. Аналогично RecordException можно использовать для записи событий в спане, не устанавливая статус спана в Error, то есть его можно использовать для записи любой дополнительной информации о спане.

Если отвязать статус спана от автоматической установки значения Error при возникновении исключения, возникает вариант использования, при котором событие исключения может иметь статус Ok или Unset. Это обеспечивает авторам инструментирования наибольшую гибкость.

Ошибки в логах

В OpenTelemetry лог — структурированное сообщение с меткой времени, сгенерированное сервисом или другим компонентом. Логи были добавлены в OTel сравнительно недавно. Для нас, пользователей, это означает ещё один способ сообщать об ошибках. Логи по традиции разбиваются на различные уровни серьёзности, отражающие тип выдаваемого сообщения: DEBUG, INFO, WARNING, ERROR и CRITICAL.

OTel позволяет соотносить (коррелировать) логи с трейсами, при этом сообщение лога может быть связано со спаном в трейсе посредством корреляции контекста трейса. Таким образом, поиск сообщения лога с уровнем ERROR или CRITICAL может дать дополнительную информацию о том, что привело к ошибке, если поднять соответствующий трейс.

Для записи ошибки в лог требуется указать либо атрибут exception.type, либо exception.message; также рекомендуется сохранять exception.stacktrace (дополнительные сведения см. в разделе «Семантические соглашения для исключений в логах»).

Логи или спаны для регистрации ошибок?

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

Спаны могут отлично подойти для регистрации ошибок: если операция выдаёт ошибку, спан помечается соответствующим образом. Так его легче обнаружить. С другой стороны, без фильтрации и выборочного сбора данных с акцентом на необычные спаны (tail sampling) есть риск пропустить ошибки, которые случаются редко, но всё же требуют обработки (особенно если система генерирует тысячи спанов в минуту).

Как насчёт использования событий спанов в сравнении с логами? Опять же, всё зависит от обстоятельств. Иногда удобно использовать события спанов: когда спан получает статус Error, автоматически создаётся событие спана с сообщением об исключении (и другими метаданными, которые, возможно, пригодятся).

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

Визуализация ошибок в различных бэкендах

OTel предоставляет исходные телеметрические данные, передаваемые системами, но не визуализирует и не интерпретирует их. За это отвечает бэкенд наблюдаемости. Поскольку OTel вендор-нейтральна, одну и ту же информацию можно визуализировать и интерпретировать различными бэкендами без необходимости инструментировать код другой библиотекой.

Jaeger

Давайте посмотрим, как ошибки OpenTelemetry выглядят в Jaeger. Представленные ошибки были сгенерированы кодом из этого репозитория. Ниже можно увидеть трейсы для сервиса py-otel-server. Как видно, спаны с ошибками отображаются в виде красных точек:

Список трейсов в интерфейсе Jaeger
Список трейсов в интерфейсе Jaeger

Если кликнуть на спан с ошибками, можно перейти в раздел «Логи» — так события спана отображаются в Jaeger — и просмотреть собранную информацию:

Атрибуты и другие метаданные для спана с ошибкой в Jaeger
Атрибуты и другие метаданные для спана с ошибкой в Jaeger

Спан явно помечен как ошибочный и включает событие спана с зарегистрированным исключением. Jaeger представляет событие спана в виде лога, но не визуализирует логи за пределами спанов.

Проприетарные бэкенды

Использовали собственный агент для мониторинга приложений и недавно перешли на OTel? Тогда вы наверняка заметили, что ошибка в OpenTelemetry может выглядеть не так, как в проприетарном агенте. Скорее всего, это связано с тем, что OTel просто по-другому представляет ошибки — не так, как это делают вендоры.

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

В качестве более конкретного примера можно привести представление OTel о типах спанов, которое может повлиять на то, как ошибка OpenTelemetry будет представлена в бэкенде. Например, если у вас есть трейс, содержащий одно исключение, и он находится на внутреннем спане со статусом Error, этот трейс будет помечен как ошибочный, но он может не учитываться в общем показателе ошибок приложения. Это связано с тем, что вендор может учитывать только ошибки в спанах, представляющих входные точки в систему (серверных спанах), и спанах, представляющих обработку задач, созданных продюсером.

Если ваш бэкенд поддерживает корреляцию трейсов и логов, у вас должна быть возможность переходить к связанному трейсу из лога и наоборот. Кроме того, хотя Jaeger визуализирует события спанов как логи, некоторые вендоры могут синтезировать собственный тип данных для событий спанов, что влияет на способ запроса этих данных.

Заключение

Мы рассмотрели проблемы обработки ошибок и исключений на разных языках программирования в архитектуре микросервисов и предложили OpenTelemetry в качестве решения для стандартизированной телеметрии и отчётности об ошибках. Спецификация OTel служит основой для стандартизации того, как обрабатываются ошибки в различных языках, предоставляя рекомендации по реализации, но допуская определённую гибкость.

Ошибки в спанах можно регистрировать, используя RecordException или его эквивалент в SDK вашего языка, а также обогащать события спана, добавляя кастомные атрибуты. Также можно записывать ошибки в логи, добавляя exception.type или exception.message, и захватывать трейс стека, добавляя exception.stacktrace, чтобы получить дополнительную информацию о том, что произошло.

После того как эти данные окажутся в бэкенде наблюдаемости (если вы ранее использовали проприетарный агент мониторинга), вы заметите разницу в визуализации ошибок, зафиксированных OTel, и представлении ошибок, зафиксированных агентом. Это связано в первую очередь с тем, что OTel представляет ошибки иначе, нежели это делают вендоры.

Используя возможности OpenTelemetry по регистрации ошибок в логах и спанах и дополняя их метаданными, можно получить более глубокое представление о поведении приложений и эффективнее диагностировать проблемы. Вы будете лучше подготовлены к созданию и поддержке устойчивых, надёжных и высокопроизводительных программных приложений в современных динамичных и требовательных окружениях. Дополнительные сведения см. в разделе «Обработка ошибок в OpenTelemetry».

P. S.

Читайте также в нашем блоге:

  • OpenTelemetry с нуля до 100: пример внедрения Норвежским управлением труда и соцобеспечения

  • Потребление ресурсов в Prometheus: кто виноват и что делать (обзор и видео доклада)

  • Обзор Coroot — Open Source-утилиты для наблюдаемости: установка, настройка, возможности, плюсы и минусы

Habrahabr.ru прочитано 16771 раз