Все для обработки JavaScript error в проекте

Решил сделать что-то типа TODO списка “Что необходимо сделать для красивой обработки ошибок JavaScript в проекте”.

  • своя обертка-заглушка на объект console
  • отправка ошибок на сервер
  • переопределение обработчика window.onerror
  • создание своих классов ошибок
  • классификация ошибок
  • красивый вывод
  • режим отладки

Более подробно о каждом под катом.

Своя обертка-заглушка на объект console

Я думаю вы знаете, что часто для отладки javascript кода и для вывода какой-либо информации в консоль браузера используется объект console.

Так вот, для чего нужно писать для него обертку? Причины следующие:

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

По первому пункту все просто – код будет где-то такой(console + FireBug console):

if (!window.console)
{
    window.console = {
        "assert": function() { },
        "count": function() { },
        "clear": function() { },
        "debug": function() { },
        "dir": function() { },
        "dirxml": function() { },
        "info": function() { },
        "error": function() { },
        "getFirebugElement": function() { },
        "group": function() { },
        "groupEnd": function() { },
        "groupCollapsed": function() { },
        "log": function() { },
        "notifyFirebug": function() { },
        "profile": function() { },
        "profileEnd": function() { },
        "time": function() { },
        "timeEnd": function() { },
        "trace": function() { },
        "warn": function() { },
        "userObjects": [],
        "element": {},
        "firebug": "foo"
    };
};

Более подробно о втором пункте, который не так очевиден. Современные упаковщики-сжимальщики кода уже научились выкидывать ветки и методы, которые никогда не выполнятся:

if(false){
}

будет выкинуто также и

function something(){
    if(false){
    }
}

а самое главное, что даже все вызовы метода something будут также удалены.

Простой пример:

var debug_on = false
function dumpfunc(data)
{
    if (debug_on)
    {
        console.log(data);
    }
}
dumpfunc(5)

Если задать переменную debug равной false, то програма по сжиманию кода выбросит все вызовы метода dump. Это довольно удобно, хотя я бы порекомендовал всеже не держать отладочного кода в репозитории.

Вот тут можно проверить как это работает ( на Advanced режиме).

Отправка ошибок на сервер

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

Также можно продумать вариант отправки ошибок для случая, когда разрывается соединение. В данном варианте мы можем складывать дамп ошибок в локальную базу данный браузера(localStorage), а дождавшись восставноления соединение – отправить все вместе на сервер.

К сожалению, никакой готовой и полноценной библиотеки для сбора и отправки ошибок я не нашел. Если знаете – подскажите пожалуйста в комментариях. То, что частично удовлетворяло требованиям – jQuery.clientSideLogging.

Переопределение обработчика window.onerror

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

Зачем? Представим себе ситуацию: произошла ошибка. Хотим ли мы, чтобы ее обработал браузер и выдал нашему пользователю системное СООБЩЕНИЕ-предупреждение о поломке сайта – не думаю. Ведь намного лучше отловить ошибку, показать пользователю красивый диалог(о том, что в системе что-то пошло не так, но мы это знаем, мы котролируем ситуацию) и не пускать ее дальше на обработку браузера.

Реализовать данный подход в коде мы можем следующим образом:

window.onerror = function (message, source, lineNr) {
//тут идет наш красивый обработчик и логирование ошибки с отравкой на сервер
return true; // а это предотвращение дальнейших действий браузра
};

Создание своих классов ошибок

*в данном случае коректнее говорить не об ошибках, а о исключениях (Exceptions).

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

Пример стандартного класса Error, генерируем и ловим ошибку:

try {
    throw new Error("Something goes wrong!");
} catch (e) {
    alert(e.name + ": " + e.message);
}

Создание своего класса(наследуем от Error):

function MyError(message) {
    this.name = "MyError";
    this.message = message || "Default Message";
}
MyError.prototype = new Error();
MyError.prototype.constructor = MyError;
try {
    throw new MyError();
} catch (e) {
    console.log(e.name); // "MyError"
    console.log(e.message); // "Default Message"
}

Мы их можем и по разному обрабатывать, проверяя какому классу принадлежит ( через instanceof ):

try {
    foo.bar();
} catch (e) {
    if (e instanceof EvalError) {
        alert(e.name + ": " + e.message);
    } else if (e instanceof RangeError) {
        alert(e.name + ": " + e.message);
    }
    // ... etc
}

подробно о объекте Error читать тут.

Классификация ошибок

Тут очень сложно дать общие советы, которые отлично впишутся под структуру всех проектов.

Иногда имеет смысл разделять все ошибки на

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

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

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

  • fatal error
  • warning
  • notice

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

Красивый вывод

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

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

Необходимо определится что мы хотим выводить пользователю и в каком формате. Возможные вариаты:

  • модальное окно(modal dialog)
  • стиль уведомления(notification)
  • встроенные(inline)

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

Режим отладки

Стоит также задуматься над вопросом: хотим ли мы выводить пользователю и разработчку одни и теже ошибки/информацию о проблеме? либо при работе над сайтом и отладке мы хотим получать больше линформации? Мне кажется ответ очевиден – мы хотим чтобы разработчик имел полностью всю информацию: где произошла ошибка и что к этому привело; а для пользователя – ограничиться коротким соощением о технической неполадке.

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

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