Мониторинг доступности Sentry self-hosted

2025.02.05
Разберемся как понять что сервер Sentry способен принимать ошибки от приложений, изучим способы доставки событий, напишем примеры скриптов для проверки, посмотрим на графики из реального мониторинга.

Развернули мы Sentry self-hosted на своем сервере и кажется что все работает хорошо, но как мониторить что Sentry действительно доступен?

Сходу на ум приходит идея использовать какую-то пинговалку вида:

curl -v http://sentry.local:9000/

Однако, на практике мы столкнулись с ситуациями когда веб-интерфейс Sentry работает, но события об ошибках не принимаются.

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

На Хабре я кратко затрагивал этот момент, и даже был сделан репозиторий с примерами, а сейчас рассмотрим подробнее. Мы не будем делать реализацию в конкретной системе мониторинга, но попробуем руками выполнить действия выдающие результат, а дальше эти действия каждый сам адаптирует под свою систему мониторинга.

Что значит доступность Sentry?

Доступность веб-интерфейса или API не может быть гарантом способности сервера Sentry принимать события.

Есть 2 вопроса:

Оба вопроса звучат сомнительно, особенно последний, однако это возможные ситуации на практике, с которыми мы столкнулись в ходе эксплуатации Sentry self-hosted.

Потеря в сети маршрутизаторов возможна, например если событие доставляется в Sentry не напрямую через HTTP, а через сеть доставки, например как это сделали мы через td-agent:

В итоге делаем вывод:

Сервер Sentry или маршрутизатор может принять событие, но необязательно запишет его в свое хранилище. Это редкий сценарий, но он возможен.

Однако, доступность API может быть первым этапом в многоуровневом мониторинге доступности, а на следующем шаге нам нужно убедиться что событие действительно появилось в Sentry.

В итоге, доступность Sentry можно описать так:

Теперь когда мы определились с необходимыми шагами определения доступности Sentry примемся за их реализацию!

Доставляемость: как ошибки поступают в Sentry

Для начала разберемся какими способами события поступают в Sentry. Для чего нам это нужно? Для того чтобы понимать механику работы.

Примеры напишем на bash и заранее договоримся что во всех скриптах у нас есть такие переменные, которые нужно инициализировать:

# идентификатор проекта
PROJECT_ID=0

# хост Sentry
HOST=''

# ключ из DSN
SENTRY_KEY=''

Store Endpoint

Story Endpoint - устаревшая версия, но еще поддерживается, по этой схеме работает Sentry PHP (как минимум до 8-ой версии PHP). Пример:

curl -v \
    -d '{"exception":{"values":[{"type":"Test issue","value":"store"}]}}' \
    -H 'Content-type: application/json' \
    -H "X-Sentry-Auth: Sentry sentry_version=7, sentry_key=$SENTRY_KEY" \
    "https://$HOST/api/$PROJECT_ID/store/"

Как видно это классический POST запрос с json в теле. Снаружи эта issue выглядеть будет так (все лаконично, говорит только о типе, который мы указали явно):

Envelope Endpoint

Envelope Endpoint - замена предыдущему story endpoint, по этой схеме уже давно работает Sentry JavaScript, здесь у нас уже вычурный многослойный json разделенный новыми строками:

# подготовка тела запроса
echo '{"sdk":{"name":"sentry.javascript.vue","version":"7.54.0"},"trace":{"environment":"monitoring"}}' > body.json
echo '{"type":"event"}' >> body.json
echo '{"exception":{"values":[{"type":"Test issue","value":"envelope"}]}}' >> body.json

curl -v \
  -H 'Content-type: text/plain' \
  --data-binary @body.json \
  "https://$HOST/api/$PROJECT_ID/envelope/?sentry_key=$SENTRY_KEY&sentry_version=7"

В Sentry эта issue будет выглядеть так:

Tunnel (обход блокировщиков рекламы)

Tunnel - это кастомный endpoint чтобы обойти блокировщиков рекламы для JavaScript. На Хабре я уже писал об этом подробнее.

По сути это то же самое что и envelope endpoint, только вместо прямой отправки в Sentry используется промежуточная отправка на другой endpoint (произвольный), который вам нужно самостоятельно реализовать.

Код отправки такой же, но в теле мы явно указываем что это tunnel и для нашей реализации в первом json отправляем dsn:

# подготовка тела запроса
# авторизация в теле прямо из клиента, нам она не нужна, но для правдоподобности оставим
echo '{"sdk":{"name":"sentry.javascript.vue","version":"7.54.0"},"trace":{"environment":"monitoring"},"dsn":"https://'$SENTRY_KEY'@'$HOST'/'$PROJECT_ID'"}' > body.json
echo '{"type":"event"}' >> body.json
echo '{"exception":{"values":[{"type":"Test issue","value":"envelope tunnel"}]}}' >> body.json

# мы отправляем туда же, куда обычно шлем запросы, потому что под капотом свой обработчик
# он преобразует запрос и перенаправит его на сервер Sentry
curl -v \
  -H 'Content-type: text/plain' \
  --data-binary @body.json \
  "https://$HOST/api/$PROJECT_ID/envelope/$SENTRY_KEY/"

Это только пример, в своей реализации вы можете сделать что угодно.

В веб-интерфейсе Sentry выглядит это так:

Доставляемость: как это может выглядеть

Для событий мониторинга мы сделали отдельный проект в Sentry и на данный момент он у нас выглядит так:

Как видно здесь всего 3 issue для каждого из способов. После первой успешной отправки запроса сформируется issue - группа похожих ошибок. Все последующие запросы с теми же данными будут попадать в эту же issue.

На скрине выше видно, что для frontend (через envelope и tunnel) график не равномерный, это говорит о проблемах с доставкой событий до Sentry (Sentry находится за VPN, а мы принимаем ошибки с веб-страниц из интернета через Gateway, вот с этим Gateway были проблемы на скринах).

А по <unlabeled event> (это store endpoint для ошибок из PHP) ровный график, здесь с доставкой все хорошо. Вообще здесь практически всегда все хорошо, потому что все наши приложения и Sentry работают в одной VPN сети.

Мы настроили мониторинг в Zabbix и вот как у нас выглядит один из графиков:

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

Принимаемость: действительно ли в Sentry пришло событие?

Теперь мы понимаем механику отправки событий в Sentry, и в любой момент времени можем протестировать Sentry на факт приема событий. Но помним что отправка запроса и 200-ый ответ это не гарантия приема события.

На этот случай нам нужно настроить проверку приема запроса со стороны Sentry для каждого issue, которое мы создали на предыдущем этапе. Делать это мы будем при помощи API Sentry:

ISSUE_ID=0
SENTRY_TOKEN=""
HOST=''

lastSeen=$(curl -s -H "Authorization: Bearer $SENTRY_TOKEN" "https://$HOST/api/0/issues/$ISSUE_ID/" | grep -o -m 1 '"lastSeen":"[^"]*' | grep -o -m 1 '[^"]*$')
timeDiff=$(( $(date -d "$lastSeen" +"%s") - $(date +%s) ))
timeDiff=${timeDiff#-}

echo $timeDiff

В итоге мы имеем время последнего события в определенной issue, проще говоря когда последний раз была отправка запроса по этому issue.

Теперь встает вопрос: как настроить проверку? Мы настроили проверку каждые 15 минут, то есть:

Таким образом мы заложили небольшой запас времени чтобы не создавались ложноположительные триггеры от:

Заключение

Спустя полтора года использования Sentry self-hosted показанная схема мониторинга доставляемости и принимаемости ошибок всегда работала исправно, в любой момент времени мы знали: может ли Sentry принимать ошибки из наших приложений или нет. Это позволяло не упускать критические проблемы и своевременно их обнаруживать.

Если появляется триггер значит есть проблема, ложноположительные срабатывания маловероятны.

В телеграм канале DevOps от первого лица можно оставить комментарий или почитать интересные истории из практики DevOps