Zidium
Скачайте исходные коды примеров с GitHub: https://github.com/Zidium/DotNetExamples

Описание проблемы

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

Что делать?

Большинство приложений делают следующее:

  • ошибка записывается в лог;
  • пользователю показывается страница ошибки "Упс, произошла ошибка... Приносим извинения".

Почему это плохо:

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

Решение

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

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

Реализация

Для обработки исключений в приложениях ASP.NET MVC используется метод Application_Error в Global.asax. Реализуем отправку ошибок в систему мониторинга в этом методе:

protected void Application_Error(object sender, EventArgs e)
{
    var exception = Server.GetLastError();

    if (exception == null)
        return;

    // обработка ошибки 404 - страница не найдена
    // источником ошибок 404 часто бывают боты, которые что то ищут на сайте
    // такие ошибки не будем считать ошибками нашего приложения
    var httpException = exception as HttpException;
    if (httpException != null)
    {
        if (httpException.GetHttpCode() == 404)
        {
            ShowErrorPage("Error404.cshtml", exception);
            return;
        }
    }

    // остальные ошибки залогируем
    LogManager.GetCurrentClassLogger().Error(exception);

    // получим уникальный номер ошибки в Zidium, чтобы показать его пользователю
    var errorNumber = Client.Instance.ExceptionRender.GetExceptionTypeCode(exception);

    // покажем страницу с текстом ошибки и номером
    ShowErrorPage("Error.cshtml", exception, errorNumber);
} 

Если Ваше приложение доступно в сети Интернет, то разные боты будут переодически создавать ошибки 404. Чтобы ошибки 404 не отвлекали, код выше их игнорирует (не отправляет в систему мониторинга).

Ошибки записываются в NLog, а подключенный адаптер ошибок (Zidium.Errors) создаёт в Zidium событие-ошибку.

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

  • Login
  • IP
  • Url
  • UserAgent
  • UrlReferrer

Заполнением свойств всех событий будет заниматься класс EventPreparer. Данный класс нужен, чтобы в каждом месте кода, которое отправляет ошибки (события), не писать одинаковый код по заполнению свойств.

/// <summary>
/// Заполняет свойства событий (ошибок) параметрами HTTP-запроса
/// </summary>
public class EventPreparer : IEventPreparer
{
	public void Prepare(SendEventBase eventObj)
	{
		var httpContext = HttpContext.Current;
		if (httpContext == null)
		{
			return;
		}
		if (httpContext.Handler == null)
		{
			// чтобы не было исключения при старте приложения,
			// когда httpContext.Request пустой.
			return;
		}
		var request = httpContext.Request;
		eventObj.Properties.Set("IP", request.UserHostAddress);
		eventObj.Properties.Set("UserAgent", request.UserAgent);
		eventObj.Properties.Set("Url", request.Url.AbsoluteUri);
		eventObj.Properties.Set("UrlReferrer", request.UrlReferrer);
		eventObj.Properties.Set("Login", httpContext.User.Identity.Name);
	}
}

EventPreparer подключается при старте приложения:

protected void Application_Start()
{
    try
    {
        // подключим заполнение свойств всех событий-ошибок
        Client.Instance.EventPreparer = new EventPreparer();

        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

        // сообщает, что запуск приложения успешно выполнен
        LogManager.GetCurrentClassLogger().Info("Приложение запущено");
    }
    catch (Exception exception)
    {
        // отправим ошибку запуска приложения в мониторинг
        LogManager.GetCurrentClassLogger().Fatal(exception);

        throw; // приложение не должно работать, если есть ошибка инициализации
    }
}

Проверим

Чтобы посмотреть, как работает обработка ошибок, напишем ErrorsController, который будет создавать исключения:

public class ErrorsController : ControllerBase
{
    public ActionResult Sample1()
    {
        throw new Exception("Ой, случилась ошибка (вариант 1)");
    }

    public ActionResult Sample2()
    {
        throw new Exception("Опять ошибка (вариант 2)");
    }

    public ActionResult Sample3()
    {
        try
        {
            throw new Exception("Ошибка в try-catch (вариант 3)");
        }
        catch (Exception exception)
        {
            LogManager.GetCurrentClassLogger().Error(exception);
        }
        return View();         
    }
}

Запустите приложение и выполните /Errors/Sample1 или /Errors/Sample2. Приложение отобразит страницу ошибки:

Проверим, что в системе мониторинга компонент имеет статус Warning (жёлтый цвет).

В дереве компонентов под компонентом "WebSite" есть жёлтая ссылка "События", кликнем на неё, чтобы посмотреть события (ошибки).

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

Все ошибки за нужный интервал времени можно увидеть на странице "Ошибки":

Здесь по ошибкам можно создать дефекты и далее работать с ними во встроенном баг-трекере.

Уровни лога Warning и Error создают ошибку жёлтого цвета. По умолчанию уведомления по email о жёлтом цвете не включены. Уровень лога Fatal создаёт ошибку красного цвета. О такой ошибке вы получите уведомление на email: