Как я Sentry настраивал 💥

 Публичный пост
13 июня 2023  1985

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

Мы начали попытки замутить систему сбора ошибок еще до 2022 года, но уже тогда в в компании тусовались ребята с грозным названием офицеры информационной безопасности, которые на корню зарубили идею хранить логи в каких-то забугорных облаках. Бомбило у меня тогда знатно, но как-то изменить это решение не вышло. Решили пойти другим путем — разворачивать Sentry в режиме on-prem. От идеи, как говорится, до реализации — один шаг. Всего-то нужно было выделить время SRE и сервачок. Мы еще тогда не знали что шагать мы будем пару лет, но уже тогда почувствовали, что дело не быстрое, и надо искать варианты.

Первые костыли

Перелопатив весь интернет мы так ничего и не нашли. В таких ситуациях на помощь пришел командный брейншторм: накидали кучу идиотских идей, просеяли через фильтр здравого смысла и обнаружили алмаз — передавать кастомные параметры в Яндекс.Метрику. А с ней все в порядке - российская компания, на российских серверах будет хранить российские ошибки в российском продукте российских же пользователей. Ну и тем более инфа обезличенная, передаем только то, что падает в консоль. Ну почти.

Для сбора ошибок рендеринга нам очень помогла фича реакта — Error Boundary. Мы обернули приложение в кастомную страничку с большой кнопкой "Обновить" и сожалениями. Пользователи перестали видеть белые экраны и писать в поддержку, а мы наконец получили первые боевые данные и стали понимать масштаб и природу проблем.

Код выглядел примерно так:

componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    const analyticEvent = {
    	eventType: AnalyticEventType.UserError,
    	errorName: error.name,
    	errorMessage: error.message,
    	errorDescription: errorInfo.componentStack,
    	errorStack: error.stack,
    };
    analyticsService.sendEvent(analyticEvent);
}

Конечно, дебажить минифицированные логи было очень сложно, но какие-то критичные моменты мы все-таки находили и оперативно фиксили. Ну вот типа таких:

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

Часть ошибок мы найти не могли и грезили о Sentry как о манне небесной и решении всех проблем. И вот этот день настал. Сервера включили, on-prem установили, архитектуру решения согласовали, аппрувы получили, осталось только интеграцию настроить, делов то. Но и без ограничений не обошлось.

Веб-приложения и клиентская часть в частности — штука очень бесячая для службы информационной безопасности, ведь код в открытом виде получает каждый пользователь. Кошмар, кошмар. Был бы я ИБшником, я бы вообще запретил код на прод заливать. Хорошо, что я не они. И нам код в продакшен заливать разрешают. Пока что. Но только минифицированный. Никаких sourcemap'ов конечно же. Если уж хакерам и достается код, то пусть хоть мучаются с деминификацией. Правда отзывов от хакеров я не слышал, но вот дебажить на проде добрым разработчикам действительно тяжело. Плюсы тоже есть — очень мотивирует писать хороший код, который никогда на проде не ломается, и дебажить там нам не приходится.

Интеграция

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

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

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

По-умолчанию, интеграция с Sentry очень простая. Она сама сходит и за .js и за .map файлами к вам на сервис и если их найдет, то все покажет как надо. Не наш вариант. Но если мы не хотим чтобы Sentry ходила к нам на сервер, то можем ей помочь, и залить в нее наши .map файлы. Тогда она будет брать их, и не будет ходить на сайт. То что нужно, жаль не работает.

Точнее работает, но не так, как мы ожидали. Соурсмапы в JS устроены следующим образом. Есть собственно сам файл мапы .map и есть минифицированный .js, в котором в конце есть директива

//# sourceMappingURL=script.min.js.map

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

Так ошибка выглядит, когда соурсмапы не подтянулись:

Не на много лучше нашего костыля с Яндекс.Метрикой.
Не на много лучше нашего костыля с Яндекс.Метрикой.

А так, когда подтянулись:

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

Так вот, мы придумали гениальный план: генерить билд с соурсмапами, грузить их в Sentry, а потом, перед деплоем, удалять. И все бы хорошо, но нам сразу завалило кибану 404 ошибками. А в девтулзах отсвечивало некрасивое:

Тут к нам на помощь пришел режим генерации SourceMaps в ViteJs!

То что нужно! Жаль не работает.
То что нужно! Жаль не работает.

Видимо Sentry без директивы //# sourceMappingURL=script.min.js.map не умеет связывать .js и .map, хотя очевидно же, что возьми название файла, и добавь в конец .map. Вот было бы круто, если бы разработчики Sentry так умели.

Мы уже даже практически смирились, что будем жить с 404 в девтулзах. Типа обычный пользователь туда все-равно не полезет, а хакеры поржут, ну и ладно, настроение людям поднимем.

Спустя сутки ресеча внезапно на странице плагина для Vite.js, который мы не используем, глаз зацепился за фразу, что можно загружать в Sentry не только .map файлы, но и .js.

То что нужно! Что же раньше то никто не сказал?
То что нужно! Что же раньше то никто не сказал?

Попробовал два варианта генерации соурсмапов hidden и true пришел к выводу что работает только режим true, то есть режим, в котором директива //# sourceMappingURL=script.min.js.map присутствует в .js файлах. Но такие файлы, как я уже говорил, мы грузить на прод не хотим. Решаем делать отдельный билд, но каким-то чудом в интернете нас находит ишью, где парни решают эту проблему простым совецким

# delete sourcemaps refs
find .next -type f -name '*.js' -exec sed -i -E 's/sourceMappingURL=[^ ]*\.js\.map//g' {} +

То что нужно! Удаляем перед деплоем соурспамы из каталога и чистим рефы. Успех!

Итоги

Sentry в целом оправдала ожидания. Теперь наши релизы выглядят вот так:

Код

Полный код докерфайла со всеми присяданиями:

# Копируем все в отдельную папочку, хотя наверное можно это и не делать
RUN set -xe \
    && mkdir -p /tmp/sentry_map \
    && find /build -type f -name '*.*.map' -exec mv -t /tmp/sentry_map {} +

RUN set -xe \
    && find /build -type f -name '*.js' -exec mv -t /tmp/sentry_map {} +

# Качаем Sentry cli
RUN set -xe \
    && curl -sL https://sentry.io/get-cli/ | bash

# Настраиваем настроечки и секретные секреты
ARG SENTRY_API_KEY
ARG SENTRY_URL
ARG SENTRY_ORG
ARG SENTRY_PROJECT
ENV SENTRY_API_KEY=$SENTRY_API_KEY
ENV SENTRY_URL=$SENTRY_URL
ENV SENTRY_ORG=$SENTRY_ORG
ENV SENTRY_PROJECT=$SENTRY_PROJECT

# Наконец-то все грузим в Sentry
RUN set -xe \
    && sentry-cli releases new $RELEASE \
    && sentry-cli releases files $RELEASE upload /tmp/sentry_map

# Удоляем соурсмапы
RUN set -xe \
    && find /build -type f -name '*.*.map' -exec rm -f {} \;

# Удоляем рефы на них 
RUN set -xe \
    && find /build -type f -name '*.js' -exec sed -i -E 's/sourceMappingURL=[^ ]*\.js\.map//g' {} \;

# Мы великолепны
16 комментариев 👇

Статья больше для хабра конечно. Ну и в целом эта проблема вот тут описывается: https://blog.sentry.io/4-reasons-why-your-source-maps-are-broken/#sentry-users-only-upload-your-source-files-as-artifacts

If you’re a Sentry user and your primary goal is making sure source maps are available so that Sentry can unminify your stack traces and provide surrounding source code, you have a third option: upload your source files as artifacts using sentry-cli or directly via our API.
Of course, if you do either of the first two options — either inlining your original files or hosting them publicly — Sentry will find the content that way too. It’s your call.

  Развернуть 1 комментарий

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

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

  Развернуть 1 комментарий

Не пробовали Sentry на своих серверах разворачивать?

  Развернуть 1 комментарий

@glader, Да, нам как раз пришлось разворачивать на своих серверах (on-premise в тексте как раз это и значит) из-за требований службы информационной безопасности, а так как серверов под это не было вся история затянулась на два года. Эти два года существовали на костыле с Яндекс.Метрикой, что в целом лучше чем ничего. Смекалочка.

  Развернуть 1 комментарий

@StipJey, а сделать альтернативный абсолютный url для мапы? Просто внутренний сервис в который Sentry сможет сходить но доступа извне не будет.

  Развернуть 1 комментарий

@elDante,
Средствами сборщика vite.js это невозможно из коробки. Можно постфактум менять в файлах линки отдельным скриптом, и грузить соурсмапы на отдельный закрытый ресурс. Но тогда на проде ссылки останутся и будет ошибка в девтулзах. Да и сложно это.

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

  Развернуть 1 комментарий
🕵️ Юзер скрыл свои комментарии от публичного просмотра...
Nikolai Lopin Фронтендмейстер 15 июня 2023

У вас он безлимитный по количеству ивентов? Мы, как я не борюсь, все время врезаемся в квоту и Сентри не очень много делает, чтобы ты за нее не вылез

  Развернуть 1 комментарий

@lopin, ну как "не очень много". Настойчиво предлагает повысить тарифный план :)

  Развернуть 1 комментарий

@lopin, а если на клиенте (на стороне юзера) настроить фильтры, чтобы отправлялись только из основных браузеров, только пара последних версий браузеров, только серьезные крэши и тд, то это не решит проблему лимитов?

  Развернуть 1 комментарий

@DeniDoman, Интересно, а как определить на стороне юзера насколько серьезный креш?

  Развернуть 1 комментарий

@StipJey, как минимум, начать с репорта только крешей, отсеяв остальные JS ошибки.

Определение крешей от фронта зависит. Я ненастоящий сварщик, но в React можно в какой-нибудь error boundary обернуть каждый компонент и генерить sentry эвент VSE_OCHEN_PLOHO, и настроить фильтр, чтобы только такие эвенты и отправлялись (плюс ограничения по браузерам/версиям).

  Развернуть 1 комментарий

@lopin, как и с любым shared ресурсом типа логов, извне разработчиков (особенно если команд много) очень сложно заставить внимательно следить за такими вещами.
Один из вариантов, как сделать так, чтобы они самостоятельно осознали ценность и пришли к "zero error policy" это бюджетирование - заложить затраты на потребленный ими кусок ресурса в бюджеты команд, в формате "больше потратили на хранение ошибок - потом меньше смогли потратить на инновации".
Другой вариант - shared дашборд с разбивкой по командам, который в real-time показывает, какая команда сколько отожрала, тогда есть шанс что горизонтальное саморегулирование начнет срабатывать (ну или все посрутся между собой).
Ну и плюс алерты всегда можно сделать, Sentry (on-premise по крайней мере) умеет отдавать RPS по проектам через API, можно орать, если он превышает какой-то установленный лимит и оперативно это эскалировать и фиксить.

  Развернуть 1 комментарий

😎

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

Что вообще здесь происходит?


Войти  или  Вступить в Клуб