четверг, 6 января 2011 г.

Контроль исключений ASP.NET с помощью модуля Elmah

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

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

image

Используя валидаторы ASP.NET на этапе постинга формы мы проверяем введенное значения на условия: не пустое, целочисленное, меньше 5. Если введенные пользователем значения не попадают под наши правила валидации, мы выдаем предупреждения:

Введите количество человек
Только целочисленные значение не более 5

Тем самым при постинге формы на сервер мы можем быть уверены, что данные проверены. Далее же мы делаем деление общей суммы на количество участников. Допустим мы забыли учесть, что и минимальный порог введенных значений должен начинаться с 1. В итоге, при вводе значения 0 в поле количества участников и отправки формы на сервер мы получим следующий ответ:

image

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

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

Чтобы добавить такую функциональность необходимо просто создать новую страницу (например error.aspx) и добавить следующую запись в конфигурационный файл web.config.

<customErrors mode="On" defaultRedirect="error.aspx">
</customErrors>


Теперь при постинге формы с нулевым значением количества участников мы получим следующий ответ сервера (для примера)


image


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


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


Для этого с помощью NuGet (расширение Visual Studio) добавим библиотеку Elmah (если расширение NuGet не установлено – рекомендую срочным образом исправить это :) так как очень много именно рутинной работы помогает решить столь незаметное для всего рабочего процесса приложение).


image


image




Итак, нажимая кнопку Install происходят следующие действия (замечу, что без NuGet эти действия необходимо было бы проделать вручную):



  • Закачивается последняя стабильная сборка Elmah библиотеки;

  • Библиотека добавляется в References вашего приложения (если тип проекта – WebSite – сборка добавляется в Bin директорию);

  • В конфигурационном файле регистрируется новая SectionGroup – elmah и регистрируются новые модули и хендлеры. В частности, регистрируется хендлер elmah.axd, который генерирует структурированный отчет об ошибках приложения;

Все. Несколько кликов и механизм доступа администратором ко всем ошибкам приложения реализован. Введем в нашу форму 0 и еще раз запостим нашу форму. Теперь перейдем на страницу elmah.axd в корне нашего приложения. Это зарегестрированный ранее хендлер, управляемый библиотекой Elmah.dll


image


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


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


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


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


Для решения данной проблемы мы можем использовать возможности ранее подключенной библиотеки Elmah. Для этого в наш конфигурационный файл добавим в секцию <configuration> и в секцию <httpModules> следующие найтройки:


 


<elmah>
<errorMail from="test@test.com"
to="test@test.com"
subject="Application Exception"
async="false"
smtpPort="25"
smtpServer="***"
userName="***"
password="***">
</errorMail>
</elmah>
...
<httpModules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
<add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
</httpModules>


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


Для этого добавим в наше веб-приложение отдельный класс VeryImportantException, унаследованный от базового класса Exception. Мы будет автоматически генерировать исключения данного типа в тех местах, где необходимо срочное вмешательство администратора и добавим несколько методов, фильтрующих логику обработки исключений в зависимости от их типа. Итак, новый класс будет предельно простым:


public class VeryImportantException : Exception
{
public VeryImportantException(Exception ex) : base(ex.Message, ex)
{
}
}


В global.asax добавим следующие методы:


public void ErrorLog_Filtering(object sender, ExceptionFilterEventArgs e)
{
Exception exception = e.Exception as VeryImportantException;
if (exception != null)
{
ErrorLog.GetDefault(HttpContext.Current).Log(new Error(exception.InnerException));
e.Dismiss();
}
}

public void ErrorMail_Filtering(object sender, ExceptionFilterEventArgs e)
{
if (!(e.Exception is VeryImportantException))
e.Dismiss();
}


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


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


int price = int.Parse(lblPrice.Text);
int count = int.Parse(txtPersonCount.Text);

try
{
double calcPrice = price / count;
}
catch (Exception ex)
{
ErrorSignal.FromContext(HttpContext.Current).Raise(new VeryImportantException(ex));
}


Теперь мы можем переловить опасное исключение и сообщить администратору на почту.


image

Комментариев нет:

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