Ошибки, которые мы любим

Алексей Андросов

Старший разработчик интерфейсов

FrontTalks, Екатеринюург, 5 июля 2013 года

Ошибки, которые мы любим

Алексей Андросов

Старший разработчик интерфейсов

FrontTalks, Екатеринюург, 5 июля 2013 года

Обо мне

@doochik

Баги – это нормально

Баги – это нормально

Баги – это нормально

Баги – это нормально

Если вы меряете эффективность своей работы по количеству багов, поздравляю, у вас серьёзные проблемы.

Первое правило:
вы — дурак,
а все вокруг — Д'Артаньяны

Второе правило:
ошибка не в вашем коде, а в коде вашей команды

Релизный цикл

Релизный цикл

Релизный цикл + мониторинг

Зачем мониторинг?

Зачем мониторинг?

Зачем мониторинг?

Зачем мониторинг?

На что смотрим

На что смотрим

На что смотрим

На что смотрим

А причем здесь фронтенд?

Что можно отследить в браузере

А еще можно считать...

Почему именно в браузере

Почему именно в браузере

Пример (запуск сервиса)

            $(projectNS.init);
        

Пример (запуск сервиса)

            if (window['jQuery']) {
                $(projectNS.init);
            } else {
              log({type: 'fatal', error: 'no-jquery'});
            }
        

Пример (запуск сервиса)

            if (window['jQuery']) {
              if (window['projectNS']) {
                $(projectNS.init);
              } else {
                log({type: 'fatal', error: 'no-project-js'});
              }
            } else {
              log({type: 'fatal', error: 'no-jquery'});
            }
        

Одна функция на все времена

function log(data) {
  var props = [];
  for (var key in data) {
    var value = encodeURIComponent(data[key]);
    var key = encodeURIComponent(key);
    props.push(key + '=' + value);
  }
  new Image().src = '/log?' + props.join('&');
}
        

window.onerror

window.onerror

            window.onerror = function(msg, url, line) {
              log({
                type: 'jserror',
                msg: msg,
                url: url,
                line: line
              })
            })
        

Но...

...первые логи вам выдадут вот такую статистику

            95% msg=Script error.&url=&line=0
            0.2% ...
            0.1% ...
        

Главная проблема window.onerror

If the location URL does not have a same origin as the origin, then set message to "Script error.", set location to the empty string, and set line to 0.

http://dev.w3.org/html5/spec/webappapis.html#report-the-error

Главная проблема window.onerror

Решается только в Firefox 13+ с помощью <script src="…" crossorigin="anonymous"></script>.

А сервер должен ответить CORS-заголовком Access-Control-Allow-Origin: *

Атрибут есть в черновике HTML 5.1. Ждем…

Главная проблема crossorigin="anonymous"

Если Chrome, Firefox или Opera не получит правильный заголовок Access-Control-Allow-Origin, то заблокирует загрузку скрипта.

try-catch?

Все обернуть в try-catch сложно, потому что JS – асинхронный язык.

Также стоит учитывать, что try-catch влияет на производительность.

Другие способы исполнения JS

Общий принцип

  1. Грузим JS как текст (XHR, localStorage)
  2. Исполняем в браузере в same-origin policy

createElement('script')

onerror
msg
onerror
url
onerror
line
catch
msg
catch
url
catch
line
Chrome 26 + - - + - *
Fx 22 + - - + - *
IE 9 + - - + - -
IE 10 + - - + - *
Opera 12.15 + - - + - *

* – строка без урла почти бесполезна

eval / new Function / setTimeout

onerror
msg
onerror
url
onerror
line
catch
msg
catch
url
catch
line
Chrome 26 + - * + - *
Fx 22 + - - + - -
IE 9 + - - + - -
IE 10 + - * + - *
Opera 12.15 + - * + - *

* – строка без урла почти бесполезна

<script src="data:..."

onerror
msg
onerror
url
onerror
line
catch
msg
catch
url
catch
line
Chrome 26 - - - + * **
Fx 22 - * - + * -
Opera 12.15 - - - + * **
<script src="data:text/javascript,...">

* – урлом является весь src (исходный текст)

** – код вытягивается в одну строку

<a href="javascript:..."

onerror
msg
onerror
url
onerror
line
catch
msg
catch
url
catch
line
Chrome 26 + - - + - **
Fx 22 + * - + * -
IE 9 + - - + - -
IE 10 + - ** + - **
Opera 12.15 - - - + * **

* – урлом является весь src (исходный текст)
** – код вытягивается в одну строку

Blob

            var blob = new Blob([code],
              {type: 'text/javascript'});
            var url = URL.createObjectURL(blob);
            var script = document.createElement('script');
            script.src = url;
            URL.revokeObjectURL(url);
            document.body.appendChild(script);
        

Blob

onerror
msg
onerror
url
onerror
line
catch
msg
catch
url
catch
line
Chrome 26 + * + + * +
Fx 22 + * + + * +
IE 10 + * + + * +

* – урлом является id Blob'а. Если исполняется несколько кусков кода, то можно сделать простой маппинг ресурсов

Что еще надо знать про window.onerror?

Избавляемся от вечного
"error at line 1"

После минификации весь код вытягивается в одну строку, из-за этого неудобно искать ошибки с символе 30768.

Поможет UglifyJS $ uglifyjs page.js -b beautify=false,max-line-len=100

Вместо msg может быть событие

...о незагрузке скрипта

            if (msg && typeof msg != 'string') {
              if (msg.type && msg.target) {
                try {
                  url = msg.target.src;
                } catch(e) {}
                msg = 'Error loading script';
              }
            }
        

Фильтруем не наши ошибки

            if (url && (
              /(miscellaneous|extension)_bindings/.test(url) ||
              /^chrome:/.test(url) ||
              /^file:/.test(url)
            ) {
              return;
            }
        

Обрабатываем JSONP

            if (url && /(&|\?)callback=/.test(url)) {
              errorType = 'noJSONPResponse';
            }
        

Что ещё можно логировать?

Неожиданное поведение

            if (data && data.status === 'OK') {
              // все хорошо
            } else {
              log({
                'type': 'something_went_wrong',
                'some_usefull_data': data
              )}
            }
        

Важный код в try-catch

            try {
              // все хорошо
            } сatch (e) {
              log({
                'type': 'exception',
                'msg': e.message || e.toString(),
                'stack': e.stacktrace || e.stack
              )}
            }
        

Транспорты и долговисящие соединения

Их можно отлаживать с помощью скрытой консоли, доступной по хоткею.

Логи для ленивых и богатых

Подводим итоги

Логируем поведение пользователя

Логируем поведение пользователя

Логируем поведение пользователя

Логируем поведение пользователя

И помните

  1. Если в каждом релизе количество ошибок вырастает на 1%, то через 10 релизов будет +11%

И помните

  1. Если в каждом релизе количество ошибок вырастает на 1%, то через 10 релизов будет +11%.
  2. Логов много не бывает!

Алексей Андросов

Старший разработчик интерфейсов

@doochik

github.com/doochik