Ловим баги на клиенте: как мы написали свою систему для сбора клиентских ошибок

в 13:23, , рубрики: error reporting, php, Sentry, Блог компании Badoo, краш-репорт, отладка, ошибки, Тестирование IT-систем, Тестирование мобильных приложений
Ловим баги на клиенте: как мы написали свою систему для сбора клиентских ошибок - 1

У нас в Badoo довольно много клиентских приложений. Помимо основных продуктов Badoo и Bumble, у которых есть как веб-версии (десктопная и мобильная), так и клиенты под нативные платформы (Android и iOS), ещё есть с десяток внутренних инструментов со своими UI.  Для сбора клиентских ошибок мы используем собственную разработку под кодовым названием Gelatо. Последние два года я работал над её серверной частью и за это время открыл для себя много нового из мира разработки Error Tracking систем. 

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


Что вас ждёт:

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

  • дам краткий обзор нашей системы, её архитектуры и технологического стека;

  • перечислю особенности, которые нужно учитывать, если вы решите разрабатывать свою систему сбора клиентских ошибок.

Как мы используем информацию об ошибках

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

Второе — проводим анализ ошибок. Мы в Badoo релизимся довольно часто: 

— веб-приложения: один-два раза в день, включая серверную часть; 

— нативные приложения: раз в неделю (хотя многое зависит от того, как быстро билд примут в App Store и Google Play). 

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

Третье. Информация об ошибках, собранная в одном месте, значительно упрощает работу разработчиков и QA-инженеров. 

Что использовали раньше

Исторически для сбора клиентских ошибок в Badoo использовались две системы: HockeyApp для сбора краш-репортов из нативных приложений и самописная система для сбора JS-ошибок.

HockeyApp

HockeyApp полностью удовлетворяла наши потребности, до тех пор пока в 2014 году её не приобрела Microsoft и не начала менять политику использования, чтобы подтолкнуть людей к переходу на свою систему App Center. App Center на тот момент нашим требованиям не соответствовала: она находилась на стадии активной разработки, и часть необходимой нам функциональности отсутствовала, в частности деобфускация стек-трейсов Android-приложений с использованием DexGuard mapping-файлов, без которой невозможна группировка ошибок. О деобфускации я расскажу ниже; если вы слышите об этом впервые, значит, точно узнаете что-то новое. 

Microsoft установила дедлайн — 16 октября 2019 года, к этому дню все пользователи HockeyApp должны были мигрировать в App Center. К слову, поддержка DexGuard появилась в App Center лишь в конце декабря 2019 года, спустя несколько месяцев после официального закрытия HockeyApp.

В дополнение к этому мы столкнулись с проблемой некорректного подсчёта общего количества ошибок в HockeyApp. Так как Microsoft фактически остановила разработку HockeyApp, нам пришлось начать дублировать эту информацию во внутреннюю систему аналитики, чтобы понимать реальное количество ошибок. Это послужило ещё одним толчком к поиску альтернативного решения.

Наша система для сбора JS-ошибок

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

Архитектура у неё была довольно простой:

  • данные хранились в MySQL (мы хранили информацию о последних 10—20 релизах);

  • для каждой версии приложения была отдельная таблица;

  • работал поиск по фиксированному набору полей средствами MySQL, плюс мы использовали Sphinx для полнотекстового поиска.

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

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

Поэтому мы решили постепенно переходить на новую систему, которая бы покрывала весь существующий функционал и удовлетворяла всем нашим актуальным требованиям.

Требования к новой системе

  • Хранение всех клиентских ошибок в одном месте.

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

  • Лёгкое масштабирование. Периодически случаются ошибки, которые порождают миллионы событий. Система должна быть способна обработать такой поток данных.

  • Отказоустойчивость — чтобы минимизировать риск потери данных.

  • Способность давать ответ не только на вопрос «Сколько произошло ошибок?», но и на вопрос «Сколько пользователей это затронуло?».

  • Группировка похожих ошибок с сохранением мета-информации (время первой и последней ошибки в группе, в каком релизе появилась ошибка, статус ошибки и т. п.).

  • Гибкий поиск по любой комбинации полей с поддержкой полнотекстового поиска.

  • Разнообразная статистика: количество ошибок за период, количество ошибок в разбивке по релизам, по браузерам, по операционным системам и т. п.

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

  • Self-hosted. Мы хотим, чтобы система работала на нашем железе. Это обеспечит нам полный контроль над данными и возможность в любой момент изменить конфигурацию кластера.

Почему мы не выбрали готовое решение

Конечно, прежде чем писать своё решение, мы проанализировали существующие на рынке системы. 

Платные сервисы

Сбором клиентских ошибок занимаются многие SaaS-решения, и это неудивительно: быстрое обнаружение и исправление ошибок — один из ключевых аспектов современной разработки. Среди самых популярных решений можно выделить Bugsnag, TrackJS, Raygun, Rollbar и Airbrake. Все они обладают богатым функционалом и в целом соответствуют нашим требованиям, но мы не рассматривали облачные решения: не было уверенности в том, что ценовая политика и политика использования со временем не изменятся, как это случилось с HockeyApp. А миграция на новое решение — довольно сложная и длительная процедура.

Open-source-решения

С open-source-системами всё было не так радужно. Большинство из них либо перестали развиваться, либо так и не вышли из стадии разработки и были непригодны для использования в продакшене.

Фактически лишь Sentry продолжал активно развиваться и обладал функциональностью, покрывающей большую часть наших потребностей. Но на тот момент (начало 2018 года) восьмая версия сервиса не подошла нам по следующим причинам:

  • вместо хранения всех событий в ней использовалось семплирование;

  • в качестве базы данных использовалась PostgreSQL, с которой у нас не было опыта работы;

  • были сложности с добавлением новой функциональности, так как система написана на Python, а наши основные языки — PHP и Go;

  • отсутствие части необходимой нам функциональности (например, интеграции с Jira).

В июле 2018 года вышла девятая версия Sentry, в которой появилась интеграция с issue-трекерами и был заложен фундамент для одного из ключевых, на мой взгляд, улучшений — перехода на ClickHouse для хранения событий (рекомендую к прочтению серию статей об этом). Но к сожалению, на момент нашего ресёрча всего этого ещё не было даже в планах. Поэтому мы решили, что оптимальным вариантом в нашем случае будет реализация собственной системы, заточенной под наши процессы, которую легко интегрировать с другими внутренними продуктами. 

Так на свет появилась система под кодовым названием Gelato (General Error Logs And The Others), о разработке которой и пойдёт речь дальше.

Краткий обзор системы

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

На главной странице находятся список приложений и общая статистика ошибок по заданному критерию.

Ловим баги на клиенте: как мы написали свою систему для сбора клиентских ошибок - 2

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

Ловим баги на клиенте: как мы написали свою систему для сбора клиентских ошибок - 3

Кликнув на версию, мы попадём на страницу со списком ошибок.

Ловим баги на клиенте: как мы написали свою систему для сбора клиентских ошибок - 4

Здесь можно узнать, какие ошибки произошли, сколько их было, сколько пользователей это затронуло, когда ошибка встретилась впервые и время последнего события. Также есть сортировка по большинству полей и возможность создания тикета в Jira для любой ошибки.

Так эта страница выглядит для нативных приложений:

Ловим баги на клиенте: как мы написали свою систему для сбора клиентских ошибок - 5

Кликнув на ошибку, мы попадём на страницу с детальной информацией о ней.

Ловим баги на клиенте: как мы написали свою систему для сбора клиентских ошибок - 6

Здесь доступны общая информация об ошибке (1), график общего количества ошибок (2) и различная аналитика (3).

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

Ловим баги на клиенте: как мы написали свою систему для сбора клиентских ошибок - 7

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

Выбираем версии для сравнения: 

Ловим баги на клиенте: как мы написали свою систему для сбора клиентских ошибок - 8

И попадаем на страницу со списком ошибок, которые есть в одной версии и которых нет в другой:

Ловим баги на клиенте: как мы написали свою систему для сбора клиентских ошибок - 9

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

  • интеграция с нашим A/B-фреймворком — для отслеживания ошибок, которые появились в том или ином сплит-тесте;

  • система уведомлений о новых ошибках;

  • расширенная аналитика (больше графиков и диаграмм);

  • email-дайджесты со статистикой по приложениям.

Общая схема работы

Теперь поговорим о том, как всё устроено под капотом. Схема довольно стандартная и состоит из трёх этапов:

  1. Сбор данных.

  2. Обработка данных.

  3. Хранение данных.

Схематически это можно изобразить следующим образом:

Ловим баги на клиенте: как мы написали свою систему для сбора клиентских ошибок - 10

Давайте для начала разберёмся со сбором данных.

Сбор данных

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

Что входит в задачи API:

  • прочитать данные;

  • проверить данные на соответствие требуемому формату, чтобы сразу отсечь «шум» (чаще всего это запросы от «мамкиных хакеров», которые «натравили» сканеры на наше API);

  • сохранить всё в промежуточную очередь.

Для чего нужна промежуточная очередь?

Если полагаться на то, что у нас достаточно низкий EPS (errors per second), и на то, что все части нашей системы будут работать стабильно всё время, то можно значительно упростить систему и сделать весь процесс синхронным.

Но мы-то с вами знаем, что в реальном мире так не бывает и на каждом из этапов в самый неподходящий момент может произойти нечто непредвиденное. И к этому наша система должна быть готова. Так, ошибка в одной из внешних зависимостей приложения приведёт к тому, что оно начнёт крашиться, что повлечёт за собой рост EPS (как это было с iOS Facebook SDK 10 июля 2020 года). Как следствие, значительно увеличится нагрузка на всю систему, а вместе с этим и время обработки одного запроса.

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

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

Что можно использовать в качестве очереди?

  1. Первое, что приходит в голову, — популярные брокеры сообщений, например Redis или RabbitMQ.

  2. Также можно использовать Apache Kafka, которая хорошо подходит для случаев, когда требуется хранить хвост входящих данных за определённый период (например, для какой-то внутренней аналитики). Kafka используется в частности в последней (десятой) версии Sentry.

  3. Мы остановились на LSD (Live Streaming Daemon). По сути, это очередь на файлах. Система используется в Badoo довольно давно и хорошо себя зарекомендовала, плюс у нас в коде уже есть вся необходимая обвязка для работы с ней.

Хранение данных

Тут нужно ответить на два вопроса: «Где хранить?» (база данных) и «Как хранить?» (модель данных).

База данных

При реализации прототипа системы мы остановились на двух претендентах: Elasticsearch и ClickHouse.

Elasticsearch

Из очевидных плюсов данной системы можно выделить следующие:

  • горизонтальное масштабирование и репликация из коробки;

  • большой набор агрегаций, что удобно при реализации аналитической части нашей системы;

  • полнотекстовый поиск;

  • поддержка UPDATE по условию (процесс обработки данных у нас асинхронный, и нужна возможность выполнять некоторые шаги пайплайна повторно, а значит, и возможность обновлять определённые поля у конкретных событий);

  • поддержка DELETE по условию (мы храним данные за полгода, а значит, нам нужна возможность удалять устаревшие данные);

  • гибкая настройка через API, что позволяет разработчикам менять настройки индексов в зависимости от задач.

Конечно, как у любой системы, у Elasticsearch есть и недостатки:

  • сложный язык запросов, поэтому документация всегда должна быть под рукой (в последних версиях появилась поддержка синтаксиса SQL, но это доступно только в платной версии (X-Pack) либо при использовании Open Distro от Amazon);

  • JVM, которую нужно «уметь готовить», в то время как наши основные языки — это PHP и Go (например, для оптимизации сборщика мусора под определённый профиль нагрузки требуется глубокое понимание того, как всё работает под капотом; мы столкнулись с этой проблемой при обновлении с версии 6.8 до 7.5, благо тема не нова и есть довольно много статей в интернете (например, тут и тут));

  • плохое сжатие строк; мы планируем хранить довольно много данных и, хотя железо с каждым годом дешевеет, хотим использовать ресурсы максимально эффективно (конечно, можно использовать deflate-сжатие вместо LZ4, но это увеличит потребление CPU, что может негативно сказаться на производительности всего кластера).

ClickHouse

Плюсы данной базы:

  • отличная производительность на запись; 

  • хорошее сжатие данных, особенно длинных строк;

  • MySQL-совместимый синтаксис запросов, что избавляет от необходимости учить новый язык запросов, как в случае с Elasticsearch;

  • горизонтальное масштабирование и репликация из коробки, хоть это и требует больше усилий по сравнению с Elasticsearch.

Но на начало 2018 года в ClickHouse отсутствовала часть необходимых нам функций:

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

  • поддержка UPDATE по условию: ClickHouse заточена под неизменяемые данные, поэтому реализация обновления произвольных записей — задача не из лёгких (этот вопрос не раз поднимался на GitHub — и в конце 2018 года функцию всё же реализовали, но она не подходит для частых обновлений);

  • полнотекстовый поиск (была опция поиска по RegExp, но он требует сканирования всей таблицы (full scan), а это довольно медленная операция).

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

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

Модель данных

Теперь немного поговорим о том, как мы храним события.

Все данные у нас разбиты на несколько групп и хранятся в отдельных индексах:

  • мета-информация;

  • сырые события.

Данные изолированы в рамках конкретного приложения (отдельный индекс) — это позволяет кастомизировать настройки индекса в зависимости от нагрузки. Например, для непопулярных приложений можно хранить данные на warm-нодах в кластере (мы используем hot-warm-cold-архитектуру).

Для того чтобы в одной системе можно было хранить и JS-ошибки, и краш-репорты нативных приложений, мы вынесли на верхний уровень всё, что используется для формирования общей статистики (время ошибки, в каком релизе она произошла, информация о пользователе, ключ группировки). А то, что уникально для каждого типа ошибки, хранится во вложенном поле attributes со своим маппингом. 

Сама идея была позаимствована из Sentry и незначительно доработана в процессе эксплуатации. В Sentry у события есть базовые поля, есть поле tags для данных, по которым нужна возможность поиска, и поле extra — для всех остальных специфических данных.

Обработка данных

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

Начнём со случая попроще.

Обработка краш-репортов из Android-приложений

Чтобы максимально уменьшить размер приложения, в Android-мире принято при сборке билда использовать специальные утилиты, которые:

  • удаляют весь неиспользуемый код (code shrinking — сокращение);

  • оптимизируют всё, что осталось после первого этапа (optimization — оптимизация);

  • переименовывают классы, методы и поля в соответствии со специальным форматом, что позволяет уменьшить размер кодовой базы, а также усложнить процесс реверс-инжиниринга приложения (obfuscation — обфускация).

Подробнее про это можно узнать из официальной документации.

Сегодня есть несколько популярных утилит:

  • ProGuard (бесплатная версия); 

  • DexGuard на базе ProGuard (платная версия с расширенным функционалом);

  • R8 от Google.

Если приложение собрано с режимом обфускации, то стек-трейс будет выглядеть примерно так:

o.imc: Error loading resources: Security check required
  at o.mef.b(:77)
  at o.mef.e(:23)
  at o.mef$a.d(:61)
  at o.mef$a.invoke(:23)
  at o.jij$c.a(:42)
  at o.jij$c.apply(Unknown Source:0)
  at o.wgv$c.a_(:81)
  at o.whb$e.a_(:64)
  at o.wgs$b$a.a_(:111)
  at o.wgy$b.run(:81)
  at o.vxu$e.run(:109)
  at android.os.Handler.handleCallback(Handler.java:790)
  at android.os.Handler.dispatchMessage(Handler.java:99)
  at android.os.Looper.loop(Looper.java:164)
  at android.app.ActivityThread.main(ActivityThread.java:6626)
  at java.lang.reflect.Method.invoke(Native Method)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:811)

Из него мало что можно понять, за исключением сообщения об ошибке. Чтобы извлечь полезную информацию из такого стек-трейса, его нужно расшифровать. Процесс дешифрования обфусцированных классов и методов называется деобфускацией, для этого необходим специальный файл mapping.txt, который генерируется в момент сборки билда. Вот фрагмент такого файла:

AllGoalsDialogFragment -> o.a:
    java.util.LinkedHashMap goals -> c
    kotlin.jvm.functions.Function1 onGoalSelected -> e
    java.lang.String selectedId -> d
    AllGoalsDialogFragment$Companion Companion -> a
    54:73:android.view.View onCreateView(android.view.LayoutInflater,android.view.ViewGroup,android.os.Bundle) -> onCreateView
    76:76:int getTheme() -> getTheme
    79:85:android.app.Dialog onCreateDialog(android.os.Bundle) -> onCreateDialog
    93:97:void onDestroyView() -> onDestroyView

Следовательно, нам нужен сервис, которому мы могли бы скормить обфусцированный стек трейс и mapping-файл — и на выходе получить оригинальный стек-трейс.

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

На её базе наши Android-разработчики написали простенький сервис на Kotlin, который: 

  • принимает на вход стек-трейс и версию приложения;

  • скачивает необходимый mapping-файл из Ceph (маппинги заливаются автоматически при сборке релиза в TeamCity);

  • деобфусцирует стек-трейс.

Обработка краш-репортов из iOS-приложений

Краш-репорты из iOS-приложения содержат в себе довольно много полезной информации, включая стек-трейсы всех запущенных в момент падения тредов (подробнее про формат краш-репортов можно почитать тут и тут). Но есть одна загвоздка: стек-трейсы содержат лишь информацию об адресах памяти, по которым располагаются классы и методы.

Thread 0:
0   libsystem_kernel.dylib              0x00000001bf3468b8 0x1bf321000 + 153784
1   libobjc.A.dylib                     0x00000001bf289de0 0x1bf270000 + 105952
2   Badoo                               0x0000000105c9c6f4 0x1047ec000 + 21694196
3   Badoo                               0x000000010657660c 0x1047ec000 + 30975500
4   Badoo                               0x0000000106524e04 0x1047ec000 + 30641668
5   Badoo                               0x000000010652b0f8 0x1047ec000 + 30667000
6   Badoo                               0x0000000105dce27c 0x1047ec000 + 22946428
7   Badoo                               0x0000000105dce3b4 0x1047ec000 + 22946740
8   Badoo                               0x0000000104d41340 0x1047ec000 + 5591872

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

Что можно использовать для символикации?

  • Можно написать свой сервис на базе консольных утилит, но, скорее всего, он подойдёт только для ручной символикации и будет доступен только на macOS. У нас же, например, всё работает на Linux.

  • Можно взять сервис Symbolicator от Sentry, который недавно был выложен в открытый доступ (рекомендую почитать статью о том, как он разрабатывался). Мы какое-то время экспериментировали с ним и пришли к выводу, что as is этот сервис будет сложно интегрировать в нашу схему: пришлось бы допиливать его под наши нужды, а опыта использования Rust у нас нет. 

  • Можно написать свой сервис на базе библиотеки Symbolic от Sentry, которая, хоть и написана на Rust, но предоставляет C-ABI — её можно использовать в языке с поддержкой FFI.

Мы остановили свой выбор на последнем варианте и написали сервис на Golang с учётом всех наших нюансов, который под капотом обращается к Symbolic через cgo.

Группировка ошибок

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

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

Мы решили не усложнять свою систему и остановились на группировке по хешу:

  • JS-ошибки группируются по полям message, type и origin;

  • Android краш-репорты группируются по первым трём строчкам стек-трейсов (плюс немного магии);

  • iOS краш-репорты группируются по первому несистемному фрейму из упавшего треда (тред, который отмечен как crashed в краш-репорте).

В качестве заключения

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

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

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

Если резюмировать, то:

  • храните как можно больше данных: это может сэкономить вам драгоценные часы и нервы, которые в противном случае придётся потратить на дебаг;

  • будьте готовы к резкому росту количества ошибок;

  • чем больше аналитики вы построите, тем лучше;

  • символикация iOS краш-репортов — это сложно, присмотритесь к Symbolicator;

  • предусмотрите систему уведомления о новых ошибках: это даст вам больше контроля и возможность своевременно реагировать на резкий рост ошибок; 

  • запаситесь терпением и приготовьтесь к увлекательному путешествию в мир разработки систем сбора ошибок.

Автор: Евгений Тупиков

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js