App Crash Handler

Может быть, вы замечали у себя в системе процесс GoogleCrashHandler.exe, который постоянно работает, появляясь в системе с установкой браузера Chrome. Долгое время где-то в подкорке постоянно крутилась мысль о том, как же он может работать. Интуитивно мне было понятно, что это относится к браузеру, что этот процесс отслеживает падения браузера и отсылает отчеты с параметрами падения, чтобы инженеры в Гугл могли оценить причины прекращения работы и принять какие-то решения по улучшению работы. Я не поддерживаю конспирологические теории на счет того, что с помощью этого процесса собирается приватная информация о пользователе.

Скриншот не с моей машины.

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

Исходя из названия и общей идеи мне стало интересно сделать свой «велосипед», так как коммерческие решения на рынке существуют, а вот о бесплатных я как-то не слышал. Да, есть программы у Microsoft (бесплатно) или у RedGate (платно), по которым тебе будут высылаться все отчеты об ошибках, которые возникли во время работы, но для этого нужно, чтобы пользователь нажал на кнопку «отправить данные». Зарегистрировать свою программу у вендора для участия в таком сборе. В корпоративном секторе, когда разработка внутренняя и градус паранойи повышен, такие варианты слабо прокатывают – все должно быть внутри и за пределы компании не выходить из соображений безопасности.

Идея

Почему это может быть важно для вас?

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

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

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

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

Хочу обратить внимание на то, что это не система общего логирования и я ни в коей мере не собираюсь тягаться с такими монстрами как Log4Net, NLog, или системой логирования из EntLib. Тут решается одна конкретная задача: передать информацию о критической ошибке разработчикам как можно скорее.

Как это выглядит с технической точки зрения?

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

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

Сервис может работать и в сокращенном виде:

В данной схеме отсутствует центральный сервер, и запись в центральную базу осуществляется напрямую.

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

Плюсы использования и разработки:

  • Не теряются ошибки.
  • Легкость использования. Пара строк кода.
  • Работа с несколькими приложениями одновременно.
  • Возможность общего анализа по всем приложениям (мол, чуваки, вы все идиоты, не умеете работать с классом или технологией Х).
  • Не надо выдавать доступ к базе ошибок на запись для конечных пользователей.
  • Легкость перестройки записи в базу ошибок.
  • Клиентские сервисы могут самостоятельно настраиваться на основной сервис.

Так же имеется возможность произвольно вызывать сервис для записи важной информации о сбое, который был обработан. Я сделал два уровня опасности Warning и Fatal.

Почему не простое логирование?

Смотрите, как обычно логгеры работают?

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

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

 

Использование

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

Так оно и есть. Роль сервиса определяется файлом конфигурации.

Конфигурация

Файл конфигурации весьма прост и ограничивается на данный момент всего несколькими параметрами.

  • StandAlone –  задает режим работы приложения. Когда только один клиентский сервис, который пишет в базу, то значение true, если же используется многозвенка, то – false.
  • ServerAddress  – указывает адрес серверного сервиса. Имеет значение только если StandAlone = false.
  • Role – указывает на роль сервиса. Возможные значения: Client, Server. Имеет значение только если StandAlone = false.

Строка соединения имеет смысл только для серверного сервиса, либо если сервис в режиме StandAlone.

Установка и запуск

Для установки и запуска сервиса используется файл AppCrashHandlerService.exe с ключом «—install». Сервис поддерживает автоматическую установку, так что вам не потребуется возиться с утилитой InstallUtil.exe, просто запустите файл с правами администратора.

Для удаления сервиса необходимо запустить файл AppCrashHandlerService.exe с ключом «—uninstall», права администратора нужны по-прежнему.

После установки в панели администрирования сервисов у вас должен появиться новый сервис:

Использование в своем приложении

Для использования в своем приложении необходимо подключить библиотеку CrashHandlerContracts.dll и указать пространство имен VioletTape.CrashHandlerContracts.

После этого в методе Main() или в том, что его заменяет написать:

Необходимо:

  • Создать экземпляр класса CrashHandler с указанием имени приложения. Это будет основным идентификатором приложения в БД для учета ошибок ПО.
  • Подписаться на событие UnhandledException.
  • В событии вызвать метод ReportFatal(), в который передать экземпляр исключения.

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

Работа сервиса

Во время работы сервиса, не важно, в какой роли он выступает и в каком режиме работает, в директории сервиса появляется две новых папки:

  • DbErrorStorage – здесь хранится файл объектной базы данных, которая используется, если возникают трудности с пересылкой данных.
  • ErrorReports – в этой папке записываются все ошибки, переданные в сервис, используя JSON формат. Сервис создает новый файл с разбивкой по дням.

Данные в базе

Пример данных, которые сохраняются в базе данных с помощью AppCrashHandler:

Где скачать

Скачать можно с SVN репозитория по адресу: http://subversion.assembla.com/svn/wbr_appcrashhandler/

Все необходимое для сборки находится в репозитории. Все собрано на .Net 4.5, в качестве объектной базы выступает Eloquera, для постоянного хранилища используется MS SQL Server. Для .Net 4 есть отдельная ветка в репозитории.

Реализация

Сложность 300-400

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

Взяв приложение из репозитория вы увидите, что решение состоит из 5 проектов: 2 рабочие модули, 3 для тестирования.  Если у возникнут проблемы с модификацией или запуском — смело пишите письма.

Основной сервис реализован в проекте AppCrashHandlerServiceVT. Библиотека для приложений: CrashHandlerContracts.

CrashHandlerContract

В библиотеке объявлены классы для передачи данных между сервисами. Это фактически POCO объекты для разметкой для WCF сервиса. К ним относятся:

  • CrashReport – общие данные об ошибке;
  • LocalException – является урезанным отображением класса Exception;
  • Severity – перечисление для указания важности ошибки.

Единственный сервис – это CrashHadler. Задачами сервиса являются формирование подходящего сообщения для передачи и передача сформированного сообщения. Для передачи данных сервер пытается связаться с сервисом на локальной машине по именованному каналу (net.pipe).  При формировании сообщения исходный объект исключения разбирается на основные части, рекурсивно обходятся все внутренние исключения для получения плоского списка данных.

Код очень простой и в дополнительных пояснениях не нуждается на мой взгляд.

AppCrashHandlerServiceVT

Все самое интересное, конечно же, находится в этом проекте.

Связь

В зависимости от роли сервиса используются разные средства связи, поднятия канала прослушивания. Для локальных целей используются именованные каналы (WcfLocalListener), для связи клиент-сервер используется Net TCP (WcfGlobalListener). Реализация полностью совпадает с точностью до типов каналов. Я решил, что в данном случае будет выгоднее настроить каналы связи в коде, чтобы не загромождать файл конфигурации.

Для тех, кто хотя бы немного работал с WCF тут все ясно. Впрочем, если вы откроете любой учебник по WCF, то при программном объявлении каналов вы увидите точно такой же код:

  • Объявление адресов оконечных точек
  • Создание хоста
  • Определение типа связи
  • Определение служебной точки раскрытия протокола
  • Определение сервиса для оконечной точки

Сохранение

Сохранение данных осуществляется в несколько конечных контейнеров:

  • Текстовый файл (json формат) JsonExportService
  • Объектная база (Eloquera) LocalDbWriter
  • Реляционная база (MS Sql Server)

Плюс еще «виртуальный» способ сохранения, фактическая пересылка серверному сервису: SendReport.

Все эти классы небольшие и не интересны для рассмотрения.

Прием и обработка сообщения

Прием и обработка сообщений осуществляется в классе CrashReportService. В зависимости от роли сервиса может быть произведено либо сохранение в базу, либо пересылка на серверную часть. Здесь же я постарался реализовать «гарантированную» доставку сообщения.

 

Обработка сообщения:

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

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

«Гарантированная» доставка в моей реализации выглядит следующим образом:

Алгоритм такой:

  • Сначала читаем все записи из локальной объектной базы данных. Сделано для скорости, чтобы не терзать базу, а работать с тем, что изначально накопилось.
  • Если есть записи, то пытаемся их переслать. Все счетчики и таймеры скидываем в начальные положения.
  • Получаем первую запись из набора и определив роль сервиса пытаемся повторно записать/послать запись об ошибке.
  • Если это удается, то переходим к следующей записи.
  • Если не удается, то увеличиваем номер попытки, выставляем нужное ожидание и ждем. Ждем, может быть сервис оживет за время ожидания.
  • Если сервис не ожил за время всех попыток и ожидания, то помечаем что принимающая сторона «мертва» и прекращаем все будущие попытки, пока не придет новость о том, что принимающая сторона ожила.

Алгоритм перезапускается при старте сервиса.

Самоустановка службы

Самоустановка службы происходит с помощью того же класса, что и при использовании InstallUtil.exe.

Вот так просто и легко!

Развитие

В качестве возможного развития проекта можно представить автоматизированное создание тикетов в TFS c учетом повторений ошибок. Можно создать программу с графическим интерфейсом для анализа ошибок по файлам json, по базе. Так же можно нарисовать красивое окно для отображения пользователю во время  возникновения исключения. Можно ввести более детальную настройку сервисов.

Необходима вообще пока что опытная эксплуатация приложения. Думаю написать о результатах через месяца 2-3, как положительных, так и отрицательных.

Готовые решения

Есть, есть общие решения.

  • Программа Microsoft по которой после регистрации  своего приложения, тебе будут доставлятся отчеты о падении программы с дампом памяти, стек трейсом, сообщениями и еще может с чем-то там. По этим данным можно будет восстановить или изучить состояние программы на момент падения. Бесплатно.
  • Приложение от RedGate включенное в SmartAssembly, которое так же позволяет собирать данные о крушении программы централизованно(дампы памяти, стеки, сообщения и так далее). Подробнее с видео и картинками. Платно.
  • PreEmptive Analytics Server так же позволяет централизованно собирать данные о крушении программы (дампы памяти, стеки, сообщения и так далее). Встраивается в TFS, автоматом поднимает баги, отслеживает жизнь приложения. Подробнее на сайте. Платно.
  • Приложение от Gibraltar, которое позволяет инструментализировать приложение и так же централизованно собирать данные о крушении программ (дампы памяти, стеки, сообщения, состояния переменных и так далее). Обзор продукта на сайте. Платно.

 

 

Hard’n’Heavy!

 

Violet Tape

4 комментарий на “App Crash Handler

    • ага, ALM, приемочное тестирование, замороженные спецификации, армия тестировщиков, и тд и тп могут не спасти, так как приложение может упасть не само по себе, а из-за окружения. Все варианты предвидеть сложно.

        • перфекционисты, такие перфекционисты =)
          хорошую мысль тут прочитал: Есть очень неприятная особенность у новичков в программировании. Это эксепшинофобия. Новички панически боятся падения продукта. Когда надо панически бояться неправильной его работы. И тогда у них появляется желание ловить ексепшины, подавлять баги. У них цель — выживаемость программы. (!!!!!!)
          На мой взгляд. тут все правильно написано, программа не должна выживать любой ценой, она должна верно работать и быстрое падение, ключ к целым данным и быстрой починке.
          Без фанатизма конечно же подходить стоит к этому, пользователь не тестер )

Оставить комментарий