Русский
Русский
English
Статистика
Реклама

Sentry

Sentry удаленый мониторинг багов в фронтенд приложениях React

03.07.2020 10:08:54 | Автор: admin

Мы изучаем использование Sentry с React.



Эта статья является частью серии, начинающейся с сообщения об ошибках Sentry на примере: Часть 1.


Реализация React


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


Мы вновь реализуем наши две кнопки, Hello и Error, приложение с React. Мы начинаем с создания нашего стартового приложения:


npx create-react-app react-app

Затем мы импортируем пакет Sentry:


yarn add @sentry/browser

и инициализируем его:


react-app / src / index.js


...import * as Sentry from '@sentry/browser';const RELEASE = '0.1.0';if (process.env.NODE_ENV === 'production') {  Sentry.init({    dsn: 'https://303c04eac89844b5bfc908ceffc6757c@sentry.io/1289887',    release: RELEASE,  });}...

Наблюдения:


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

Затем мы реализуем наши кнопки Hello и Error и добавляем их в приложение:


react-app / src / Hello.js


import React, { Component } from 'react';import * as Sentry from '@sentry/browser';export default class Hello extends Component {  state = {    text: '',  };  render() {    const { text } = this.state;    return (      <div>        <button          onClick={this.handleClick}        >          Hello        </button>        <div>{text}</div>      </div>    )  }  handleClick = () => {    this.setState({      text: 'Hello World',    });    try {      throw new Error('Caught');    } catch (err) {      if (process.env.NODE_ENV !== 'production') {        return;      }      Sentry.captureException(err);    }  }}

react-app / src / MyError.js


import React, { Component } from 'react';export default class MyError extends Component {  render() {    return (      <div>        <button          onClick={this.handleClick}        >          Error        </button>      </div>    )  }  handleClick = () => {    throw new Error('Uncaught');  }}

react-app / src / App.js


...import Hello from './Hello';import MyError from './MyError';class App extends Component {  render() {    return (      <div className="App">        ...        <Hello />        <MyError />      </div>    );  }}export default App;

Проблема (Исходные Карты)


Мы можем протестировать Sentry с производственной сборкой, введя:


yarn build

и из build папки введите:


npx http-server -c-1

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



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


Решения (Исходные Карты)


Решение этой проблемы сводится к запуску приложения с общедоступного веб-сервера. Одна простая кнопка ответа на него, чтобы использовать сервис GitHub Pages (бесплатно). Шаги для использования обычно следующие:


  1. Скопируйте содержимое папки build в папку docs в корневом каталоге репозитория.


  2. Включите GitHub Pages в репозитории (из GitHub), чтобы использовать папку docs в master ветви


  3. Перенесите изменения на GitHub



Примечание: после того, как я понял, что мне нужно использовать create-create-app функция домашней страницы для запуска приложения. Сводилось к добавлению следующего к package.json:


"homepage": "https://larkintuckerllc.github.io/hello-sentry/"

Окончательная версия запущенного приложения доступна по адресу:


https://larkintuckerllc.github.io/hello-sentry/


Иллюстрация Пойманных Ошибок


Давайте пройдем через нажатие кнопки Hello.



С ошибкой, появляющейся следующим образом:



Наблюдения:


  • Этот отчет об ошибке не может быть более ясным, BRAVO.

Иллюстрация Неучтенных Ошибок


Аналогично, давайте пройдем через нажатие кнопки Error.



С ошибкой, появляющейся следующим образом:



Лучшая обработка неучтенных ошибок (рендеринг)


Введение Границ Ошибок

Ошибка JavaScript в части пользовательского интерфейса не должна нарушать работу всего приложения. Чтобы решить эту проблему для пользователей React, React 16 вводит новое понятие "границы ошибок".

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



Новое поведение для необнаруженных ошибок

Это изменение имеет важное значение. Начиная с React 16, ошибки, которые не были пойманы какой-либо границей ошибок, приведут к размонтированию всего дерева компонентов React.

Dan Abramov Error Handling in React 16

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


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


react-app / src / MyRenderError


import React, { Component } from 'react';export default class MyRenderError extends Component {  state = {    flag: false,  };  render() {    const { flag } = this.state;    return (      <div>        <button          onClick={this.handleClick}        >          Render Error        </button>        { flag && <div>{flag.busted.bogus}</div> }      </div>    )  }  handleClick = () => {    this.setState({      flag: true,    });  }}

Наблюдение:


  • При нажатии кнопки, React будет отображаться flag.busted.bogus, которая порождает ошибку


  • Без границы ошибки все дерево компонентов будет размонтировано



Затем мы пишем наш код границы ошибки (использует новый метод жизненного цикла componentDidCatch); это, по сути, пример, приведенный в статье Дэна Абрамова:


react-app / src / ErrorBoundary.js


import React, { Component } from 'react';import * as Sentry from '@sentry/browser';export default class ErrorBoundary extends Component {  constructor(props) {    super(props);    this.state = { hasError: false };  }  componentDidCatch(err, info) {    this.setState({ hasError: true });    Sentry.captureException(err);  }  render() {    if (this.state.hasError) {      return <h1>Something went wrong.</h1>;    }    return this.props.children;  }}

Наконец, мы используем этот компонент:


react-app / src / App.js


...import MyRenderError from './MyRenderError';class App extends Component {  render() {    return (      <ErrorBoundary>        <div className="App">          ...        </div>      </ErrorBoundary>    );  }}...

При этом нажатие кнопки Render Error отображает резервный пользовательский интерфейс и сообщает об ошибке Sentry.




Завершение


Надеюсь, вам было это полезно.


P.S. Телеграм чат по Sentry https://t.me/sentry_ru

Подробнее..

Трассировка и логирование в микросервисах как мы втаскивали единый стандарт на 30 независимых команд

20.01.2021 12:12:03 | Автор: admin
Сервисы падали, падают и будут падать

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



Например, наша команда сопровождения маркетинга в Skyeng знала: пользователь с таким-то айдишником нажал в личном кабинете кнопку Сменить преподавателя постучался в наш сервис, дальше ушло три сообщения, в очереди было 2 вызова сервисов и эти сервисы ответили 200. Но на самом деле, что было у команд сопровождения учителей или биллинга, к которым мы стучались, не знал никто

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


В компании есть стандарт окружения: PHP 7.x, Symfony 4.x, RabbitMQ для системы сообщений, GuzzleHTTP для внешних и своих ip, Docker. Благодаря тому, что нам более-менее известно, что будет в каждой команде, мы написали библиотеку, которая помогла бы сделать трассировку запроса по всей системе с первого клика.

У нас два основных транспорта HTTP и RabbitMQ. Запрос приходит или от пользователя, или по cron.

  • Мы берем любой HTTP-запрос, а прежде чем прокинуть дальше, добавляем заголовок request-id от NGINX, у которого возможность генерации вшита прямо в модуле.
  • В случае с cron, сами генерируем request-id внутри нашего бандла.

Дальше прокидываем request-id всем следующим получателям в нашем облаке:

  • Автоматически добавляем в Guzzle-клиент для продолжения передачи request-id через HTTP при синхронных запросах к нашим сервисам.
  • Автоматически добавляем request-id ко всем AMQP-продюсерам для асинхронных запросов к нашим сервисам.
  • Также во всех местах, куда мы вклиниваемся для отправки, прикручен Jaeger opentracing это поможет построить всё дерево/карту запросов. Таким образом наш внутренний request-id в качестве атрибута уходит в трейсы opentracing.

Opentracing, маркировка через request-id и складывание в правильные места первые шаги к тому, чтобы в компании появились хорошие логи. Когда команды подключат нашу библиотеку, схема выше начнет работать: request-id начнет передаваться по HTTP и AMQP, каждый сервис будет знать, в рамках какого глобального запроса он сейчас работает. И мы увидим, как запросы расползаются по нашей инфраструктуре.

Логирование inspired by Java


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

В PHP нет таких аннотаций, поэтому многие прокидывают в конструктор логеры, как дополнительную зависимость. Нам не нравился такой подход, поэтому мы сделали trait, который служит почти как аннотация (т.к. мы используем Symfony, то trait опирается на функционал autowiring в результате получается практически как в Java).

Что мы логируем? Помимо request-id, это:

  • runtime-id и process-id идентификаторы текущего потока и процесса, внутри которых может обслуживаться несколько request-id,
  • user-id так как в приложениях есть авторизация, этот идентификатор должен быть везде,
  • имя канала канал можно использовать как бэкап для пользователя, если отвалилась база данных и что-то не записалось.
  • id контейнера, id сервиса, id задачи и прочее все возможные мета-данные о Docker-контейнере. Зачем? У нас был момент, когда приложение было раскатано на 3 контейнера, а взял и сглючил один конкретный, потому что он был развернут на глючном воркере.
  • Цепочки исключений на уровне базы, сервиса, всегда создаем исключения более высокого уровня, потому что часто именно в последнем исключении и кроется А что было-то?.
  • имя приложения в процессе оказалось, что многие команды ориентируются на имя репозитория, а приложение внутри не знает, как его зовут: пришлось дособирать эту информацию и дописывать upname в yaml-файл.
  • плюс служебные команды и переменные окружения.

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

В чем хранить логи на проде и тестингах


На приложения у нас заводится New Relic, но мы дополнительно остановились на разделении записей между Sentry и Kibana.

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



Наша панелька с небоскребами если они растут, значит, кто-то сегодня закончит работу попозже.

Если ткнуть глубже, видно, какие пользователи были затронуты, графики, какой браузер, теги, версия PHP статистики очень много. Плюс можно сразу сделать таску в Jira из ошибки, настроить алерты, назначить ответственных, которым придет оповещение.



Самое главное не класть ошибки, которые вы не собираетесь править. Например, access denied сюда не летит.

Но в Sentry есть и свой минус он не очень хорошо справляется с большим потоком. Если вы, например, поделили на 0 и выкатили в прод, хранилище может лечь. Плюс надо помнить о том, что ошибка в одном сервисе может породить вал ошибок в другом сервисе. Решение этой проблемы выходит за тему статьи, но в целом смотрите в сторону паттерна circuit breaker.

Лог всего, что не ошибки, мы прокидываем как JSON в Kibana. Для этого сборщик Beats ходит, собирает файлы, наш модуль для Filebeat отправляет их в Elasticsearch, а дальше мы уже в Kibana все вычитываем. Правильнее было бы вычитывать логи с вывода контейнера и местами мы так и делаем.



Наша Kibana Выглядит она как Kibana хороший полнотекстовый поиск, свой язык запросов, всякие поля.

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

Итак, мы молодцы. Сделали полезную вещь. Что должно было быть результатом этих действий? Все команды начнут использовать единообразные библиотеки как часть ядра. Все, от саппорта до CTO, будут знать, где, как искать и что искать в логах. А единая система алертов сделает жизнь еще прекраснее Внедряем? Да, но есть нюанс.

Куда вас пошлют Пошаговый разбор, как внедрить у себя


Зато понимающие люди будут говорить: Он внедрил логи по всей компании

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

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

Шаг 0. Начните с себя. Чтобы показать другим, как это классно, легко и красиво, вам нужно тестовое внедрение. Мы начали с собственной команды в маркетинге как раз было около 20 сервисов. Заодно поняли, какие вопросы и сложности скорее всего возникнут и начали готовиться к ним.

  • Например, сделали документацию с примерами и типовыми кейсами: как искать по логам, что делать, если нужен свой канал. Часть кейсов оказалось удобнее показать на уровне видео. Да, это значит, нужно делать презентацию, писать видео но в итоге оно того стоит.

Шаг 1. Вы идете к CTO и тимлидам. Вы думаете на языке PHP, они уже бизнесом, фичами, деньгами. Аргументируйте на их языке:

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

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

  • Красота и удобство Sentry продают. Начните с них.
  • Минимизируйте телодвижения разработчиков. Именно поэтому мы выбрали уровень библиотеки. Если делать здоровые конфиги или правила, из-за которых придётся менять код, никто этого делать не будет. А здесь мы сами написали бандлы, которые в большинстве своем конфигурируют себя сами и сами встраиваются, куда им надо. Их можно встраивать без конфигурации вообще но если надо, их конфигурацию можно перекрыть.
  • Плюс удобный trait для добавления логера, документация с заранее объявленными каналами, готовыми примерами доработок и расширения конфигов. Так, чтобы не надо было городить что-то свое при внедрении.

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

p.s. Пост основан на опыте и рассказах Вани, Макса и Димы, которые вместе делали этот проект. Мы не придумали, от чьего аккаунта это публиковать, и залили от аккаунта компании. В комментариях ребята готовы ответить от своих аккаунтов.

p.p.s. В посте использованы цитаты из докладов Макса и Вани про этот проект.
Подробнее..

Как восстановить Sentry после не удачного обновления

22.07.2020 22:18:15 | Автор: admin
Всем привет. Я хочу рассказать о том, как проходило восстановление Sentry после неудачного обновления.

Что же такое Sentry?

Это система полного отслеживания ошибок с открытым исходным кодом, которая поддерживает широкий спектр серверных, браузерных, настольных и родных мобильных языков и сред, включая PHP, Node. js, Python, Ruby, C #, Java, Go, React, Angular, Vue, JavaScript и другие.

Немного о том как просто и не принуждённо всё поломалось.

Жили мы на версии 9.0.0 и пришло время обновиться. Пощупав web интерфейс 10.0.0 принял решение обновиться на неё. Это была самая большая ошибка!

Обновление прошло в штатном режиме. Сначала переход на 9.1.2 затем на 10.0.0 (иначе нельзя). Далее обнаружил что не запускается worker. Причиной этому добавление новых приложений и зависимостей.

Попытки собрать приложения и засунуть их в k8s не увенчались успехом.

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

После 3 часов работ и привлечения DBA эксперта данные были восстановлены до рабочего состояния.

Как?

Сделали рядом инстанс PG, закинули туда схему от версии 9.0.0 и начали потабличное восстановление. Конечно без ошибок и напильника тут не обошлось.

На этом история не заканчивается. Пришло время всё же обновиться, на версию 9.1.2.

Собрал образ, запускаю:

sentry upgrade

и видим вот такое чудесное сообщение:

 ! I'm not trusting myself; either fix this yourself by fiddling ! with the south_migrationhistory table, or pass --delete-ghost-migrations ! to South to have it delete ALL of these records (this may not be good).Exception in thread Thread-1 (most likely raised during interpreter shutdown)

После долгих поисков, проб и ошибок было найдено решение.

Необходимо восстановить схему данных текущей версии.

Для этого клонируем репозиторий:

git clone https://github.com/getsentry/onpremise.git

Переходим в ветку 9.1.2, так как ветки 9.0.0 уже нет.

cd onpremise && git checkout tags/9.1.2

Далее правим образ в Dockerfile:

#cat Dockerfile ARG SENTRY_IMAGEFROM ${SENTRY_IMAGE:-sentry:9.0.0}-onbuild
Затем правим compose file

docker-compose.yml
# NOTE: This docker-compose.yml is meant to be just an example of how# you could accomplish this on your own. It is not intended to work in# all use-cases and must be adapted to fit your needs. This is merely# a guideline.# See docs.getsentry.com/on-premise/server/ for full# instructionsversion: '3.4'x-defaults: &defaults  restart: unless-stopped  build:    context: .  depends_on:    - redis   # - postgres    - memcached    - smtp  env_file: .env  environment:    SENTRY_MEMCACHED_HOST: memcached    SENTRY_REDIS_HOST: redis    SENTRY_POSTGRES_HOST: 'sentry.cl.ats'    SENTRY_DB_USER: 'sentryDB'    SENTRY_DB_NAME: 'sentry_user'    SENTRY_DB_PASSWORD: 'sentry_passwd'    SENTRY_POSTGRES_PORT: 5432    SENTRY_EMAIL_HOST: smtp  volumes:    - sentry-data:/var/lib/sentry/filesservices:  smtp:    restart: unless-stopped    image: tianon/exim4  memcached:    restart: unless-stopped    image: memcached:1.5-alpine  redis:    restart: unless-stopped    image: redis:3.2-alpine  #postgres:  #  restart: unless-stopped  #  image: postgres:9.5  #  environment:  #    POSTGRES_HOST_AUTH_METHOD: 'trust'  #  volumes:  #    - sentry-postgres:/var/lib/postgresql/data  web:    <<: *defaults    ports:      - '9000:9000'  cron:    <<: *defaults    command: run cron  worker:    <<: *defaults    command: run workervolumes:    sentry-data:      external: true    #sentry-postgres:    #  external: true


Немного пояснений. Так как у меня есть отдельный инстанс базы данных, то создание через compose пропускаю, так же ненужен volume sentry-postgres.

После данных манипуляций запускаем сборку:

./install.sh 

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

docker-compose run --rm web sentry django migrate --delete-ghost-migrations

Примерный вывод
Creating network "onpremise_default" with the default driverCreating onpremise_memcached_1 ... doneCreating onpremise_smtp_1      ... doneCreating onpremise_redis_1     ... done21:30:26 [INFO] sentry.plugins.github: apps-not-configuredRunning migrations for sentry:- Nothing to migrate. - Loading initial data for sentry.Installed 0 object(s) from 0 fixture(s)Running migrations for nodestore:- Nothing to migrate. - Loading initial data for nodestore.Installed 0 object(s) from 0 fixture(s)Running migrations for search:- Nothing to migrate. - Loading initial data for search.Installed 0 object(s) from 0 fixture(s)Running migrations for social_auth:- Nothing to migrate. - Loading initial data for social_auth.Installed 0 object(s) from 0 fixture(s)Running migrations for tagstore:- Nothing to migrate. - Loading initial data for tagstore.Installed 0 object(s) from 0 fixture(s)Running migrations for jira_ac:- Nothing to migrate. - Loading initial data for jira_ac.Installed 0 object(s) from 0 fixture(s)Running migrations for hipchat_ac:- Nothing to migrate. - Loading initial data for hipchat_ac.Installed 0 object(s) from 0 fixture(s)


Далее можно запускать upgrade.

docker-compose run --rm web sentry upgrade

Stdout
Starting onpremise_smtp_1      ... doneStarting onpremise_memcached_1 ... doneStarting onpremise_redis_1     ... done21:30:48 [INFO] sentry.plugins.github: apps-not-configuredSyncing...Creating tables ...Installing custom SQL ...Installing indexes ...Installed 0 object(s) from 0 fixture(s)Synced: > django.contrib.admin > django.contrib.auth > django.contrib.contenttypes > django.contrib.messages > django.contrib.sessions > django.contrib.sites > django.contrib.staticfiles > crispy_forms > debug_toolbar > raven.contrib.django.raven_compat > rest_framework > sentry.plugins.sentry_interface_types > sentry.plugins.sentry_mail > sentry.plugins.sentry_urls > sentry.plugins.sentry_useragents > sentry.plugins.sentry_webhooks > sudo > south > sentry_plugins.slackNot synced (use migrations): - sentry - sentry.nodestore - sentry.search - social_auth - sentry.tagstore - sentry_plugins.jira_ac - sentry_plugins.hipchat_ac(use ./manage.py migrate to migrate these)Running migrations for sentry:- Nothing to migrate. - Loading initial data for sentry.Installed 0 object(s) from 0 fixture(s)Running migrations for nodestore:- Nothing to migrate. - Loading initial data for nodestore.Installed 0 object(s) from 0 fixture(s)Running migrations for search:- Nothing to migrate. - Loading initial data for search.Installed 0 object(s) from 0 fixture(s)Running migrations for social_auth:- Nothing to migrate. - Loading initial data for social_auth.Installed 0 object(s) from 0 fixture(s)Running migrations for tagstore:- Nothing to migrate. - Loading initial data for tagstore.Installed 0 object(s) from 0 fixture(s)Running migrations for jira_ac:- Nothing to migrate. - Loading initial data for jira_ac.Installed 0 object(s) from 0 fixture(s)Running migrations for hipchat_ac:- Nothing to migrate. - Loading initial data for hipchat_ac.Installed 0 object(s) from 0 fixture(s)Creating missing DSNsCorrecting Group.num_comments counter


И так, схема восстановлена. Можно обновиться на 9.1.2.

Для этого в Dockerfile меняем версию на 9.1.2:

ARG SENTRY_IMAGEFROM ${SENTRY_IMAGE:-sentry:9.1.2}-onbuild
И запускаем install.sh

./install.sh

Если всё сделано верно, то процесс завершится без ошибок. Вывод должен быть приметно такой:

Stdout
# ./install.sh Checking minimum requirements...Creating volumes for persistent storage...Created sentry-data..env already exists, skipped creation.Building and tagging Docker images...smtp uses an image, skippingmemcached uses an image, skippingredis uses an image, skippingBuilding webStep 1/2 : ARG SENTRY_IMAGEStep 2/2 : FROM ${SENTRY_IMAGE:-sentry:9.1.2}-onbuild# Executing 4 build triggers ---> Running in 6c97f9fcaf63DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-supportRemoving intermediate container 6c97f9fcaf63 ---> Running in 9e0f65ee3af6Removing intermediate container 9e0f65ee3af6 ---> Running in 09754c44319cRemoving intermediate container 09754c44319c ---> a12fa31c2675Successfully built a12fa31c2675Successfully tagged onpremise_web:latestBuilding cronStep 1/2 : ARG SENTRY_IMAGEStep 2/2 : FROM ${SENTRY_IMAGE:-sentry:9.1.2}-onbuild# Executing 4 build triggers ---> Using cache ---> Using cache ---> Using cache ---> Using cache ---> a12fa31c2675Successfully built a12fa31c2675Successfully tagged onpremise_cron:latestBuilding workerStep 1/2 : ARG SENTRY_IMAGEStep 2/2 : FROM ${SENTRY_IMAGE:-sentry:9.1.2}-onbuild# Executing 4 build triggers ---> Using cache ---> Using cache ---> Using cache ---> Using cache ---> a12fa31c2675Successfully built a12fa31c2675Successfully tagged onpremise_worker:latestDocker images built.Generating secret key...Secret key written to .envSetting up database...Starting onpremise_smtp_1      ... doneStarting onpremise_redis_1     ... doneStarting onpremise_memcached_1 ... done21:35:07 [WARNING] sentry.utils.geo: settings.GEOIP_PATH_MMDB not configured.21:35:10 [INFO] sentry.plugins.github: apps-not-configuredSyncing...Creating tables ...Installing custom SQL ...Installing indexes ...Installed 0 object(s) from 0 fixture(s)Migrating...Running migrations for sentry: - Migrating forwards to 0472_auto__add_field_sentryapp_author. > sentry:0424_auto__add_field_integration_status > sentry:0425_auto__add_index_pullrequest_organization_id_merge_commit_sha > sentry:0425_remove_invalid_github_idps > sentry:0426_auto__add_sentryappinstallation__add_sentryapp__add_field_user_is_sent > sentry:0427_auto__add_eventattachment__add_unique_eventattachment_project_id_event > sentry:0428_auto__add_index_eventattachment_project_id_date_added > sentry:0429_auto__add_integrationexternalproject__add_unique_integrationexternalpr > sentry:0430_auto__add_field_organizationintegration_status > sentry:0431_auto__add_field_externalissue_metadata > sentry:0432_auto__add_field_relay_is_internal > sentry:0432_auto__add_index_userreport_date_added__add_index_eventattachment_date_ > sentry:0433_auto__add_field_relay_is_internal__add_field_userip_country_code__add_ > sentry:0434_auto__add_discoversavedqueryproject__add_unique_discoversavedqueryproj > sentry:0435_auto__add_field_discoversavedquery_created_by > sentry:0436_rename_projectdsymfile_to_projectdebugfile > sentry:0437_auto__add_field_sentryapp_status > sentry:0438_auto__add_index_sentryapp_status__chg_field_sentryapp_proxy_user__chg_ > sentry:0439_auto__chg_field_sentryapp_owner > sentry:0440_auto__del_unique_projectdebugfile_project_debug_id__add_index_projectd > sentry:0441_auto__add_field_projectdebugfile_data > sentry:0442_auto__add_projectcficachefile__add_unique_projectcficachefile_project_ > sentry:0443_auto__add_field_organizationmember_token_expires_at > sentry:0443_auto__del_dsymapp__del_unique_dsymapp_project_platform_app_id__del_ver > sentry:0444_auto__add_sentryappavatar__add_field_sentryapp_redirect_url__add_field > sentry:0445_auto__add_promptsactivity__add_unique_promptsactivity_user_feature_org > sentry:0446_auto__add_index_promptsactivity_project_id > sentry:0447_auto__del_field_promptsactivity_organization__add_field_promptsactivit > sentry:0448_auto__add_field_sentryapp_is_alertable > sentry:0449_auto__chg_field_release_owner > sentry:0450_auto__del_grouphashtombstone__del_unique_grouphashtombstone_project_ha > sentry:0451_auto__del_field_projectbookmark_project_id__add_field_projectbookmark_ > sentry:0452_auto__add_field_sentryapp_events > sentry:0452_auto__del_field_releaseenvironment_organization_id__del_field_releasee > sentry:0453_auto__add_index_releasefile_release_name > sentry:0454_resolve_duplicate_0452 > sentry:0455_auto__add_field_groupenvironment_first_seen > sentry:0456_auto__add_dashboard__add_unique_dashboard_organization_title__add_widg > sentry:0457_auto__add_field_savedsearch_is_global__chg_field_savedsearch_project__ > sentry:0457_auto__add_monitorcheckin__add_monitor__add_index_monitor_type_next_che > sentry:0458_global_searches_data_migrationSaved Searchs: 100% |########################################################################################################################################################################| Time: 0:00:07 > sentry:0459_global_searches_unique_constraint > sentry:0460_auto__add_field_servicehook_organization_id > sentry:0461_event_attachment_indexes > sentry:0462_auto__add_servicehookproject > sentry:0462_releaseenvironment_project_id > sentry:0463_backfill_service_hook_project > sentry:0464_auto__add_sentryappcomponent__add_field_sentryapp_schema > sentry:0464_groupenvironment_foreignkeys > sentry:0465_sync > sentry:0466_auto__add_platformexternalissue__add_unique_platformexternalissue_grou > sentry:0467_backfill_integration_status > sentry:0468_auto__add_field_projectdebugfile_code_id__add_index_projectdebugfile_p > sentry:0468_recent_search > sentry:0469_fix_state > sentry:0470_org_saved_search > sentry:0471_global_saved_search_types > sentry:0472_auto__add_field_sentryapp_authorThe following content types are stale and need to be deleted:    sentry | dsymobject    sentry | dsymapp    sentry | useridentity    sentry | dsymsymbol    sentry | dsymsdk    sentry | globaldsymfile    sentry | versiondsymfile    sentry | projectdsymfile    sentry | grouphashtombstone    sentry | minidumpfile    sentry | dsymbundleAny objects related to these content types by a foreign key will alsobe deleted. Are you sure you want to delete these content types?If you're unsure, answer 'no'.    Type 'yes' to continue, or 'no' to cancel: yesRunning migrations for sentry.nodestore:- Nothing to migrate.Running migrations for sentry.search:- Nothing to migrate.Running migrations for social_auth:- Nothing to migrate.Running migrations for sentry.tagstore:- Nothing to migrate.Running migrations for sentry_plugins.hipchat_ac:- Nothing to migrate.Running migrations for sentry_plugins.jira_ac:- Nothing to migrate.Synced: > django.contrib.admin > django.contrib.auth > django.contrib.contenttypes > django.contrib.messages > django.contrib.sessions > django.contrib.sites > django.contrib.staticfiles > crispy_forms > debug_toolbar > rest_framework > sentry.plugins.sentry_interface_types > sentry.plugins.sentry_mail > sentry.plugins.sentry_urls > sentry.plugins.sentry_useragents > sentry.plugins.sentry_webhooks > sudo > southMigrated: - sentry - sentry.nodestore - sentry.search - social_auth - sentry.tagstore - sentry_plugins.hipchat_ac - sentry_plugins.jira_acCreating missing DSNsCorrecting Group.num_comments counterCleaning up...----------------You're all done! Run the following command get Sentry running:  docker-compose up -d


Всё, версия 9.1.2 установлена.

Выводы.

Это всем известные прописные истины. Не спешите, делайте резервные копии и проверяйте всё несколько раз.

Благодарю за внимание!
Подробнее..

Как восстановить Sentry после неудачного обновления

23.07.2020 00:16:58 | Автор: admin
Всем привет. Я хочу рассказать о том, как проходило восстановление Sentry после неудачного обновления.

Что же такое Sentry?

Это система полного отслеживания ошибок с открытым исходным кодом, которая поддерживает широкий спектр серверных, браузерных, настольных и родных мобильных языков и сред, включая PHP, Node. js, Python, Ruby, C #, Java, Go, React, Angular, Vue, JavaScript и другие.

Немного о том, как просто и непринуждённо всё поломалось.

Жили мы на версии 9.0.0 и пришло время обновиться. Пощупав web интерфейс 10.0.0, принял решение обновиться на неё. Это была самая большая ошибка!

Обновление прошло в штатном режиме. Сначала переход на 9.1.2 затем, на 10.0.0 (иначе нельзя). Далее обнаружил, что не запускается worker. Причиной этому добавление новых приложений и зависимостей.

Попытки собрать приложения и засунуть их в k8s не увенчались успехом.

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

После 3 часов работ и привлечения DBA эксперта данные были восстановлены до рабочего состояния.

Как?

Сделали рядом инстанс PG, закинули туда схему от версии 9.0.0 и начали потабличное восстановление. Конечно, без ошибок и напильника тут не обошлось.

На этом история не заканчивается. Пришло время всё же обновиться на версию 9.1.2.

Собрал образ, запускаю:

sentry upgrade

и видим вот такое чудесное сообщение:

 ! I'm not trusting myself; either fix this yourself by fiddling ! with the south_migrationhistory table, or pass --delete-ghost-migrations ! to South to have it delete ALL of these records (this may not be good).Exception in thread Thread-1 (most likely raised during interpreter shutdown)

После долгих поисков, проб и ошибок было найдено решение.

Необходимо восстановить схему данных текущей версии.

Для этого клонируем репозиторий:

git clone https://github.com/getsentry/onpremise.git

Переходим в ветку 9.1.2, так как ветки 9.0.0 уже нет.

cd onpremise && git checkout tags/9.1.2

Далее правим образ в Dockerfile:

#cat Dockerfile ARG SENTRY_IMAGEFROM ${SENTRY_IMAGE:-sentry:9.0.0}-onbuild
Затем правим compose file

docker-compose.yml
# NOTE: This docker-compose.yml is meant to be just an example of how# you could accomplish this on your own. It is not intended to work in# all use-cases and must be adapted to fit your needs. This is merely# a guideline.# See docs.getsentry.com/on-premise/server/ for full# instructionsversion: '3.4'x-defaults: &defaults  restart: unless-stopped  build:    context: .  depends_on:    - redis   # - postgres    - memcached    - smtp  env_file: .env  environment:    SENTRY_MEMCACHED_HOST: memcached    SENTRY_REDIS_HOST: redis    SENTRY_POSTGRES_HOST: 'sentry.cl.ats'    SENTRY_DB_USER: 'sentryDB'    SENTRY_DB_NAME: 'sentry_user'    SENTRY_DB_PASSWORD: 'sentry_passwd'    SENTRY_POSTGRES_PORT: 5432    SENTRY_EMAIL_HOST: smtp  volumes:    - sentry-data:/var/lib/sentry/filesservices:  smtp:    restart: unless-stopped    image: tianon/exim4  memcached:    restart: unless-stopped    image: memcached:1.5-alpine  redis:    restart: unless-stopped    image: redis:3.2-alpine  #postgres:  #  restart: unless-stopped  #  image: postgres:9.5  #  environment:  #    POSTGRES_HOST_AUTH_METHOD: 'trust'  #  volumes:  #    - sentry-postgres:/var/lib/postgresql/data  web:    <<: *defaults    ports:      - '9000:9000'  cron:    <<: *defaults    command: run cron  worker:    <<: *defaults    command: run workervolumes:    sentry-data:      external: true    #sentry-postgres:    #  external: true


Немного пояснений. Так как у меня есть отдельный инстанс базы данных, то создание через compose пропускаю, также не нужен volume sentry-postgres.

После данных манипуляций запускаем сборку:

./install.sh 

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

docker-compose run --rm web sentry django migrate --delete-ghost-migrations

Примерный вывод
Creating network "onpremise_default" with the default driverCreating onpremise_memcached_1 ... doneCreating onpremise_smtp_1      ... doneCreating onpremise_redis_1     ... done21:30:26 [INFO] sentry.plugins.github: apps-not-configuredRunning migrations for sentry:- Nothing to migrate. - Loading initial data for sentry.Installed 0 object(s) from 0 fixture(s)Running migrations for nodestore:- Nothing to migrate. - Loading initial data for nodestore.Installed 0 object(s) from 0 fixture(s)Running migrations for search:- Nothing to migrate. - Loading initial data for search.Installed 0 object(s) from 0 fixture(s)Running migrations for social_auth:- Nothing to migrate. - Loading initial data for social_auth.Installed 0 object(s) from 0 fixture(s)Running migrations for tagstore:- Nothing to migrate. - Loading initial data for tagstore.Installed 0 object(s) from 0 fixture(s)Running migrations for jira_ac:- Nothing to migrate. - Loading initial data for jira_ac.Installed 0 object(s) from 0 fixture(s)Running migrations for hipchat_ac:- Nothing to migrate. - Loading initial data for hipchat_ac.Installed 0 object(s) from 0 fixture(s)


Далее можно запускать upgrade.

docker-compose run --rm web sentry upgrade

Stdout
Starting onpremise_smtp_1      ... doneStarting onpremise_memcached_1 ... doneStarting onpremise_redis_1     ... done21:30:48 [INFO] sentry.plugins.github: apps-not-configuredSyncing...Creating tables ...Installing custom SQL ...Installing indexes ...Installed 0 object(s) from 0 fixture(s)Synced: > django.contrib.admin > django.contrib.auth > django.contrib.contenttypes > django.contrib.messages > django.contrib.sessions > django.contrib.sites > django.contrib.staticfiles > crispy_forms > debug_toolbar > raven.contrib.django.raven_compat > rest_framework > sentry.plugins.sentry_interface_types > sentry.plugins.sentry_mail > sentry.plugins.sentry_urls > sentry.plugins.sentry_useragents > sentry.plugins.sentry_webhooks > sudo > south > sentry_plugins.slackNot synced (use migrations): - sentry - sentry.nodestore - sentry.search - social_auth - sentry.tagstore - sentry_plugins.jira_ac - sentry_plugins.hipchat_ac(use ./manage.py migrate to migrate these)Running migrations for sentry:- Nothing to migrate. - Loading initial data for sentry.Installed 0 object(s) from 0 fixture(s)Running migrations for nodestore:- Nothing to migrate. - Loading initial data for nodestore.Installed 0 object(s) from 0 fixture(s)Running migrations for search:- Nothing to migrate. - Loading initial data for search.Installed 0 object(s) from 0 fixture(s)Running migrations for social_auth:- Nothing to migrate. - Loading initial data for social_auth.Installed 0 object(s) from 0 fixture(s)Running migrations for tagstore:- Nothing to migrate. - Loading initial data for tagstore.Installed 0 object(s) from 0 fixture(s)Running migrations for jira_ac:- Nothing to migrate. - Loading initial data for jira_ac.Installed 0 object(s) from 0 fixture(s)Running migrations for hipchat_ac:- Nothing to migrate. - Loading initial data for hipchat_ac.Installed 0 object(s) from 0 fixture(s)Creating missing DSNsCorrecting Group.num_comments counter


Итак, схема восстановлена. Можно обновиться на 9.1.2.

Для этого в Dockerfile меняем версию на 9.1.2:

ARG SENTRY_IMAGEFROM ${SENTRY_IMAGE:-sentry:9.1.2}-onbuild
И запускаем install.sh

./install.sh

Если всё сделано верно, то процесс завершится без ошибок. Вывод должен быть примерно такой:

Stdout
# ./install.sh Checking minimum requirements...Creating volumes for persistent storage...Created sentry-data..env already exists, skipped creation.Building and tagging Docker images...smtp uses an image, skippingmemcached uses an image, skippingredis uses an image, skippingBuilding webStep 1/2 : ARG SENTRY_IMAGEStep 2/2 : FROM ${SENTRY_IMAGE:-sentry:9.1.2}-onbuild# Executing 4 build triggers ---> Running in 6c97f9fcaf63DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-supportRemoving intermediate container 6c97f9fcaf63 ---> Running in 9e0f65ee3af6Removing intermediate container 9e0f65ee3af6 ---> Running in 09754c44319cRemoving intermediate container 09754c44319c ---> a12fa31c2675Successfully built a12fa31c2675Successfully tagged onpremise_web:latestBuilding cronStep 1/2 : ARG SENTRY_IMAGEStep 2/2 : FROM ${SENTRY_IMAGE:-sentry:9.1.2}-onbuild# Executing 4 build triggers ---> Using cache ---> Using cache ---> Using cache ---> Using cache ---> a12fa31c2675Successfully built a12fa31c2675Successfully tagged onpremise_cron:latestBuilding workerStep 1/2 : ARG SENTRY_IMAGEStep 2/2 : FROM ${SENTRY_IMAGE:-sentry:9.1.2}-onbuild# Executing 4 build triggers ---> Using cache ---> Using cache ---> Using cache ---> Using cache ---> a12fa31c2675Successfully built a12fa31c2675Successfully tagged onpremise_worker:latestDocker images built.Generating secret key...Secret key written to .envSetting up database...Starting onpremise_smtp_1      ... doneStarting onpremise_redis_1     ... doneStarting onpremise_memcached_1 ... done21:35:07 [WARNING] sentry.utils.geo: settings.GEOIP_PATH_MMDB not configured.21:35:10 [INFO] sentry.plugins.github: apps-not-configuredSyncing...Creating tables ...Installing custom SQL ...Installing indexes ...Installed 0 object(s) from 0 fixture(s)Migrating...Running migrations for sentry: - Migrating forwards to 0472_auto__add_field_sentryapp_author. > sentry:0424_auto__add_field_integration_status > sentry:0425_auto__add_index_pullrequest_organization_id_merge_commit_sha > sentry:0425_remove_invalid_github_idps > sentry:0426_auto__add_sentryappinstallation__add_sentryapp__add_field_user_is_sent > sentry:0427_auto__add_eventattachment__add_unique_eventattachment_project_id_event > sentry:0428_auto__add_index_eventattachment_project_id_date_added > sentry:0429_auto__add_integrationexternalproject__add_unique_integrationexternalpr > sentry:0430_auto__add_field_organizationintegration_status > sentry:0431_auto__add_field_externalissue_metadata > sentry:0432_auto__add_field_relay_is_internal > sentry:0432_auto__add_index_userreport_date_added__add_index_eventattachment_date_ > sentry:0433_auto__add_field_relay_is_internal__add_field_userip_country_code__add_ > sentry:0434_auto__add_discoversavedqueryproject__add_unique_discoversavedqueryproj > sentry:0435_auto__add_field_discoversavedquery_created_by > sentry:0436_rename_projectdsymfile_to_projectdebugfile > sentry:0437_auto__add_field_sentryapp_status > sentry:0438_auto__add_index_sentryapp_status__chg_field_sentryapp_proxy_user__chg_ > sentry:0439_auto__chg_field_sentryapp_owner > sentry:0440_auto__del_unique_projectdebugfile_project_debug_id__add_index_projectd > sentry:0441_auto__add_field_projectdebugfile_data > sentry:0442_auto__add_projectcficachefile__add_unique_projectcficachefile_project_ > sentry:0443_auto__add_field_organizationmember_token_expires_at > sentry:0443_auto__del_dsymapp__del_unique_dsymapp_project_platform_app_id__del_ver > sentry:0444_auto__add_sentryappavatar__add_field_sentryapp_redirect_url__add_field > sentry:0445_auto__add_promptsactivity__add_unique_promptsactivity_user_feature_org > sentry:0446_auto__add_index_promptsactivity_project_id > sentry:0447_auto__del_field_promptsactivity_organization__add_field_promptsactivit > sentry:0448_auto__add_field_sentryapp_is_alertable > sentry:0449_auto__chg_field_release_owner > sentry:0450_auto__del_grouphashtombstone__del_unique_grouphashtombstone_project_ha > sentry:0451_auto__del_field_projectbookmark_project_id__add_field_projectbookmark_ > sentry:0452_auto__add_field_sentryapp_events > sentry:0452_auto__del_field_releaseenvironment_organization_id__del_field_releasee > sentry:0453_auto__add_index_releasefile_release_name > sentry:0454_resolve_duplicate_0452 > sentry:0455_auto__add_field_groupenvironment_first_seen > sentry:0456_auto__add_dashboard__add_unique_dashboard_organization_title__add_widg > sentry:0457_auto__add_field_savedsearch_is_global__chg_field_savedsearch_project__ > sentry:0457_auto__add_monitorcheckin__add_monitor__add_index_monitor_type_next_che > sentry:0458_global_searches_data_migrationSaved Searchs: 100% |########################################################################################################################################################################| Time: 0:00:07 > sentry:0459_global_searches_unique_constraint > sentry:0460_auto__add_field_servicehook_organization_id > sentry:0461_event_attachment_indexes > sentry:0462_auto__add_servicehookproject > sentry:0462_releaseenvironment_project_id > sentry:0463_backfill_service_hook_project > sentry:0464_auto__add_sentryappcomponent__add_field_sentryapp_schema > sentry:0464_groupenvironment_foreignkeys > sentry:0465_sync > sentry:0466_auto__add_platformexternalissue__add_unique_platformexternalissue_grou > sentry:0467_backfill_integration_status > sentry:0468_auto__add_field_projectdebugfile_code_id__add_index_projectdebugfile_p > sentry:0468_recent_search > sentry:0469_fix_state > sentry:0470_org_saved_search > sentry:0471_global_saved_search_types > sentry:0472_auto__add_field_sentryapp_authorThe following content types are stale and need to be deleted:    sentry | dsymobject    sentry | dsymapp    sentry | useridentity    sentry | dsymsymbol    sentry | dsymsdk    sentry | globaldsymfile    sentry | versiondsymfile    sentry | projectdsymfile    sentry | grouphashtombstone    sentry | minidumpfile    sentry | dsymbundleAny objects related to these content types by a foreign key will alsobe deleted. Are you sure you want to delete these content types?If you're unsure, answer 'no'.    Type 'yes' to continue, or 'no' to cancel: yesRunning migrations for sentry.nodestore:- Nothing to migrate.Running migrations for sentry.search:- Nothing to migrate.Running migrations for social_auth:- Nothing to migrate.Running migrations for sentry.tagstore:- Nothing to migrate.Running migrations for sentry_plugins.hipchat_ac:- Nothing to migrate.Running migrations for sentry_plugins.jira_ac:- Nothing to migrate.Synced: > django.contrib.admin > django.contrib.auth > django.contrib.contenttypes > django.contrib.messages > django.contrib.sessions > django.contrib.sites > django.contrib.staticfiles > crispy_forms > debug_toolbar > rest_framework > sentry.plugins.sentry_interface_types > sentry.plugins.sentry_mail > sentry.plugins.sentry_urls > sentry.plugins.sentry_useragents > sentry.plugins.sentry_webhooks > sudo > southMigrated: - sentry - sentry.nodestore - sentry.search - social_auth - sentry.tagstore - sentry_plugins.hipchat_ac - sentry_plugins.jira_acCreating missing DSNsCorrecting Group.num_comments counterCleaning up...----------------You're all done! Run the following command get Sentry running:  docker-compose up -d


Всё, версия 9.1.2 установлена.

Выводы.

Это всем известные прописные истины. Не спешите, делайте резервные копии и проверяйте всё несколько раз.

Благодарю за внимание!
Подробнее..

Apple убивает TeamCity, Bitrise, Appcenter, Fastlane, Firebase, Sentry и иже с ними. Краткий обзор Xcode Cloud

10.06.2021 14:04:32 | Автор: admin

Заголовок конечно громковат, может не убивает, но уменьшит им доходы точно. Давайте кратко посмотрим что представила Apple на WWDC 2021, что такое Xcode Cloud?

Xcode Cloud - это сервис CI/CD, встроенный в Xcode и разработанный специально для разработчиков Apple. Он ускоряет разработку и доставку приложений, объединяя облачные инструменты, которые помогают создавать приложения, параллельно запускать автоматические тесты, доставлять приложения тестировщикам, а также просматривать отзывы пользователей и управлять ими.

Цикл разработки по мнению Apple заключается в этапах 1) Написать код 2) протестировать его 3) Интегрировать (в текущий) 4) Доставить до пользователя 5) Улучшить, и по новой. На то он и цикл. В принципе похоже на правду, так оно и есть.

Если вы хоть раз настраивали CI/CD для iOS приложений, вы знаете примерно какие там шаги, ничего сложного, но это может включать в себя использование нескольких сервисов, генерации сертификатов и тд и тп.

Теперь же Apple предлагает нам сделать это все не выходя из Xcode, давайте взглянем на процесс.

Для начала нам нужно настроить первый workflow, а потом уже который будет пробегать при PR/MR (pull request/merge request) на main/develop ветку в системе контроля версий.

I CI/CD

1) Жмем в новом Xcode при подключенном сервисе Xcode Cloud кнопку "создать workflow" и видим настройки

Name - название воркфлоу, Start condition когда запускать воркфлоу (например при изменении в main ветке), Environment - можно выбрать стабильную версию Xcode или новую бета версию, Actions - что собственно надо сделать, обычно выполнить archive и опубликовать например в TestFlight, после прогонки тестов, Post-Actions - что сделать после того как воркфлоу пройден, например написать в slack/telegram канал об этом событии

2) Выбираем репозиторий где хранится наш код

3) Выбираем с какой ветки собрать билд (при первой настройке)

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

Давайте теперь посмотрим как выглядит управлени воркфлоу (выше показан путь настройки первой сборки)

1) Выбираем "управление воркфлоу"

2) Выбираем настройки (например при pull/merge request что-то выполнять)

3) Выбираем какие тесты мы хотим прогнать в воркфлоу (UI или Unit тесты), я так понимаю речь именно про нативные тесты, про Appium и тд, пока ничего не известно.

4) И выбираем отправить сообщение в Slack после того как воркфлоу пройден

5) Готово

II Тесты

1) Давайте посмотрим как выглядит интерфейс работы с тестами, мы видим тут тесты которые пройдены при сборке а также устройства на которых они прогонялись

2) Посмотрим какие конкретно тесты прогнались на iPad Air, видим что тест кейс с Light mode, портретный режим, с Английским языком, далее видим какие конкретно тесты пройдены

3) Ну и совсем чудеса, можно смотреть скриншоты пройденных тестов

4) Можно также посмотреть какой тест упал, можно также пометить тест как Flaky (Флаки тест или другими словами тест неактуальный, который надо либо удалить либо переписать), для этого используется XCTExpectFailure (что в переводе логично видно по названию метода ожидаемый фейл)

Удобно.

III - Работа с системой контроля версий (и переписка прямо в коде в Xcode)

1) Изменения теперь видно еще нагляднее (привет всем кто пользуется визуальными штуками, а не через консоль при работе с git). Сверху мы видим наши локальные изменения (которые мы накодили) а снизу "висящие" pr/mr реквесты, которые можно посмотреть, и дать свой комментарий или approve (одобрение на слияние кода)

2) Даже видно какой тест план для этой фичи, которая просится в главную ветку

3) Переписка,комменты прямо в Xcode при pr/mr (а не на веб мордах gitlab/github/bitbucket и тд)

В общем очень круто и удобно

IV - Улучшения (Crashes/Сбои/Ошибки)

1) Все краши/сбои теперь видно прямо в Xcode (а не в веб морде Firebase или Sentry), код приходит сразу символизированный (symbolized log), то есть человекопонятный с указанием что и как произошло

2) А тестер (возможно и пользователь) может оставить комментарий при краше который вы сможете прочитать (и даже ответить!)

3) Ну и самое интересное вы сможете кликнуть открыть место краша в проекте

4) И вас за ручку проведут к вашему куску кода который натворил зло

Послесловие

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

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

Можете подать заявку на бета-тест здесь https://developer.apple.com/xcode-cloud/

Сколько он будет стоить пока тоже неизвестно.

И пока непонятно что с Android потому что обычно сервисы CI/CD используют сразу для двух платформ, так как приложения обычно тоже для двух платформ разрабатывают. Но может быть когда нибудь приложения для Android можно будет писать и в Xcode))

Изображение и информацию брал из видеосессий WWDC 2021, кому интересно как это выглядит вот видео про Xcode Cloud https://developer.apple.com/videos/play/wwdc2021/102/

Подробнее..

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

08.10.2020 16:23:54 | Автор: admin

У нас в 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 (мы хранили информацию о последних 1020 релизах);

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

  • работал поиск по фиксированному набору полей средствами 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), о разработке которой и пойдёт речь дальше.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Сбор данных

Мы исходим из того, что разработчики клиентского приложения уже позаботились об обработке ошибок на своей стороне и всё, что требуется от нашего сервиса, это предоставить 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 requiredat 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 + 1537841   libobjc.A.dylib                     0x00000001bf289de0 0x1bf270000 + 1059522   Badoo                               0x0000000105c9c6f4 0x1047ec000 + 216941963   Badoo                               0x000000010657660c 0x1047ec000 + 309755004   Badoo                               0x0000000106524e04 0x1047ec000 + 306416685   Badoo                               0x000000010652b0f8 0x1047ec000 + 306670006   Badoo                               0x0000000105dce27c 0x1047ec000 + 229464287   Badoo                               0x0000000105dce3b4 0x1047ec000 + 229467408   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;

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

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

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru