Sentry-javascript: Асинхронная загрузка и сбор ошибок

Созданный на 18 дек. 2013  ·  36Комментарии  ·  Источник: getsentry/sentry-javascript

Я хочу иметь возможность загружать raven.js асинхронно, но при этом иметь возможность фиксировать ошибки во время загрузки скрипта. (Что-то вроде того, как Google Analytics обрабатывает события, сохраняя их в переменной до загрузки библиотеки).

Вот что у меня есть на данный момент: https://gist.github.com/karolisdzeja/8010574

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

Самый полезный комментарий

Вот фрагмент, который мы используем для прозрачной очереди вызовов методов Raven: https://gist.github.com/Kl0tl/ed0a9e74462a2294f4c8842f5389d8ea.

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

Все 36 Комментарий

+1

+1

+1

+1

Все, кто это комментировал - что не так с решением @karolisdzeja?

В конечном счете, я не уверен, как мы можем добавить в исходный код Raven.js функцию, которая должна работать, когда исходный код Raven.js отсутствует на странице. Я думаю, что это всегда будет индивидуальное решение; в лучшем случае мы могли бы добавить «как» в наши документы.

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

На самом деле, гораздо лучшим решением было бы что-то вроде кода SDK Twitter для JS: https://dev.twitter.com/web/javascript/loading

Настройте очередь функций при загрузке страницы, которая затем потребляется при загрузке внешнего js, заменяя прокси-объект. И убедитесь, что все вызовы API проходят через что-то вроде вызова .ready () прокси.

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

Я бы хотел иметь возможность просто загружать raven.js асинхронно / отложенно и не беспокоиться.

Другие проблемы с сутью: он затирает window.onerror и вводит несколько неограниченных глобальных переменных.

Он также не использует полнофункциональную функцию traceKitWindowOnError, которую raven.js использует при загрузке.

Я немного переделал суть выше: https://gist.github.com/oroce/ec3786ba7eff59963842220c3ffc56b4

Нет утечки переменной. Я сохранил обработчик window.onerror , но не стесняйтесь использовать window.addEventListener('error', fn) .

Самым большим подспорьем на данный момент будет наличие traceKitWindowOnError в качестве функции, экспортированной из Raven. Поскольку эта функция вызывается при возникновении ошибки: https://github.com/getsentry/raven-js/blob/master/dist/raven.js#L2074

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

У этого есть и другие недостатки:

  • Полагаясь на window.onerror для обнаружения ошибок до загрузки Raven, трассировки стека доступны не для всех браузеров.

    • помимо отсутствия трассировки стека, это отрицательно повлияет на группировку

    • вот почему install() действительно пытается / ловить инструментарий

  • Синтетические трассировки не будут работать (все они будут отображаться как исходящие из этого кода)
  • Нет сбора хлебных крошек

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

@benvinegar, ты совершенно прав. В приложениях, которые не являются общедоступными (например, Google не достигает страниц), классический (блокирующий) способ raven вполне подходит, но как только у вас появится общедоступный сайт, на котором важны точки понимания страницы Google, нам нужно оптимизировать как мы загружаем сторонний код (это цена, которую мы готовы заплатить за пользовательский интерфейс, скорость и лучшую позицию в результатах поиска).

Более того, объединение raven в наш пакет - это решение, но как только вы приступите к оптимизации своего кода внешнего интерфейса с помощью оптимизаций выше сгиба, таких как разделение вашего пакета на несколько с помощью таких инструментов, как factor-bundle, или вы включаете несколько пакетов для увеличения скорости, Вышеупомянутое решение может быть лучшим imo, но я открыт для предложений.

У всех есть компромиссы, поэтому было бы здорово, если бы мы могли задокументировать все доступные стратегии, поэтому в зависимости от конкретного веб-приложения мы будем применять разные стратегии.
При использовании стратегии async мы должны загружать скрипт после события onload вместо загрузки только async, чтобы предотвратить блокировку рендеринга события onload .

/**
 * Setup Js error lazy tracking
 * - Pros: doesn't block rendering, onload event
 * - Cons: lower quality error reports for lazy errors
 *
 * <strong i="9">@author</strong> vinhlh
 *
 * <strong i="10">@param</strong>  {object} window
 * <strong i="11">@param</strong>  {object} labJs
 * <strong i="12">@param</strong>  {string} ravenCdn
 * <strong i="13">@param</strong>  {string} sentryDsn
 */
(function(window, labJs, ravenCdn, sentryDsn) {
  var errors = [];
  var oldOnError = window.onerror;

  window.onerror = function() {
    errors.push(arguments);
    oldOnError && oldOnError.apply(this, arguments);
  };
  window.addEventListener('load', function() {
    labJs
      .script(ravenCdn)
      .wait(function() {
        window.onerror = oldOnError;
        Raven.config(sentryDsn).install();
        errors.forEach(function(args) {
          window.onerror.apply(this, args);
        });
      });
  });
})(window, $LAB, 'raven-js-3.8.1/dist/raven.js', 'https://[email protected]/9');

Я думаю, мы, вероятно, просто задокументируем асинхронный фрагмент, подобный приведенным выше, но упомянем, что он идет с компромиссами.

Еще один комментарий. Эти компромиссы могут показаться приемлемыми, но я имею дело с множеством обращений в службу поддержки от пользователей об ошибках низкой точности, с которыми они сталкиваются, которые (ошибочно) считаются производными от Raven.js. Я опасаюсь, что, если я буду поощрять людей использовать асинхронный подход, у меня будет все больше и больше людей, которые будут спрашивать меня «почему нет трассировки стека» и другие жалобы, когда это связано с тем, что этот подход имеет более низкую точность. Я готов принять это, но эту пилюлю сложно проглотить. 😓

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

@oroce - да, это на 100% не беспокоит людей в этой ветке, а людей, которые могут следовать этой стратегии, не понимая должным образом предостережений (например, просто копировать / вставлять).

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

Еще раз спасибо за ваше участие здесь / за то, что убедили меня сделать это.

Вот фрагмент, который мы используем для прозрачной очереди вызовов методов Raven: https://gist.github.com/Kl0tl/ed0a9e74462a2294f4c8842f5389d8ea.

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

Привет, ребята, просто интересно, есть ли что-нибудь не так с тем, как Raygun делает это асинхронно?
Я не уверен, но кажется, что он хорошо справляется с крайними случаями? Хотя я могу ошибаться :)

@ Kl0tl очень мило, спасибо

Это очень просто с помощью динамического импорта . Все еще на этапе 3, но поддерживается веб-пакетом.

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

const RavenPromise = import('raven-js'); // async load raven bundle

// initial setup
RavenPromise.then(Raven => {
    Raven.config('url-to-sentry', options).install();
}):

// exported log function
export const logMessage = (level, logger, text) => {
    RavenPromise.then(Raven => {
        Raven.captureMessage(text, {level, logger});
    });
};

Как и в случае с Google Analytics . Вот пример для analytics.js :

<script async src="https://www.google-analytics.com/analytics.js"></script>
<script>
    window.ga = window.ga || function () {
        (ga.q = ga.q || []).push(arguments)
    }
    ga.l = +new Date

    ga('create', 'UA-XXXXX-Y', 'auto')
    ga('send', 'pageview')
</script>

@benvinegar Возможно ли что-то подобное с Raven.js? Возможно в будущем?

@kireerik, это определенно будет реализовано (скорее всего, как

@kamilogorek Звучит отлично (мне не нравится обходной путь как решение). Без проблем!

Для всех, кому интересно, я изложил еще один способ асинхронной загрузки ravenjs:
https://gist.github.com/MaxMilton/e2338b02b7381fc7bef2ccd96f434201

Он основан на коде @oroce, но с ключевыми отличиями в том, что я использую обычный тег <script async src'='..."> в заголовке документа для повышения производительности (браузеры могут запланировать получение ресурса раньше) IIFE и другие мелкие хитрости.

@MaxMilton Хороший! Я создал свой аромат на основе вашего:

<script async src="https://cdn.ravenjs.com/3.22.1/raven.min.js" crossorigin="anonymous" id="raven"></script>
<script>
    (function (sentryDataSourceName) {
        var raven = document.getElementById('raven')
        , isNotLoaded = true
        , errors = []
        raven.onreadystatechange = raven.onload = function () {
            if (isNotLoaded) {
                Raven.config(sentryDataSourceName).install()
                isNotLoaded = !isNotLoaded
                errors.forEach(function (error) {
                    Raven.captureException(error[4] || new Error(error[0]), {
                        extra: {
                            file: error[1]
                            , line: error[2]
                            , col: error[3]
                        }
                    })
                })
            }
        }
        window.onerror = function (message, source, lineNumber, colmnNumber, error) {
            if (isNotLoaded)
                errors.push([message, source, lineNumber, colmnNumber, error])
        }
    })('https://<key>@sentry.io/<project>')
</script>

У меня тоже есть вопрос:

  • Это необходимо для определения crossorigin атрибута на script тега?
  • Достаточно ли передать только объект ошибки вместо другого решения ?

Что вы думаете? Каково мнение автора (@kamilogorek) по этому поводу?

@kireerik, когда вы помещаете crossorigin="anonymous" в скрипт, он позволяет вам полностью фиксировать ошибки (из этого внешнего скрипта) с помощью события window.onerror . Это также предотвращает отправку браузером учетных данных с запросом на выборку, что обычно является тем, что вы хотите со сторонними ресурсами. Ссылка MDN 1 , ссылка MDN 2 .

Вы можете просто передать ошибку, и она будет работать _ большую часть времени. Предостережение заключается в том, что старые браузеры (например, Firefox до версии 31) не передают свойства columnNo или Error Object в событие window.onerror . Так что, если вам нужна действительно хорошая совместимость, вам нужно сделать еще кое-что. Ссылка MDN .

РЕДАКТИРОВАТЬ: Бонусный совет: оказывается, когда вы помещаете crossorigin без какого-либо значения, он обрабатывается так же, как crossorigin="anonymous" .

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

  • много комментариев, чтобы объяснить, что на самом деле происходит
  • большая очистка + использование описательных имен переменных (всегда приятный бонус: wink:)
  • обернуть в IIFE, чтобы не загрязнять глобальное пространство имен
  • исправить неверные параметры, переданные в элемент массива ошибок

Посмотрите на суть, если вы хотите все понять ИЛИ если вы предпочитаете быстрое копирование + вставку, вот уменьшенная версия:

<!-- Sentry JS error tracking -->
<script async src="https://cdn.ravenjs.com/3.22.0/raven.min.js" crossorigin id="raven"></script>
<script>
(function(b,e,c,d){b.onerror=function(a,b,d,f,g){c||e.push([a,b,d,f,g])};b.onunhandledrejection=function(a){c||e.push([a.reason.reason||a.reason.message,a.type,JSON.stringify(a.reason)])};d.onreadystatechange=d.onload=function(){c||(c=!0,
Raven.config("___PUBLIC_DSN___").install(),
b.onunhandledrejection=function(a){Raven.captureException(Error(a.reason.reason||a.reason.message),{extra:{type:a.type,reason:JSON.stringify(a.reason)}})},e.forEach(function(a){Raven.captureException(a[4]||Error(a[0]),{extra:{file:a[1],line:a[2],col:a[3]}})}))}})(window,[],!1,document.getElementById("raven"));
</script>

<link rel="preconnect" href="https://sentry.io">

Замените ___PUBLIC_DSN___ своим DSN и вставьте его где-нибудь в голове рядом с закрывающим тегом </head> . Или, если вы хипстер, который больше не использует теги <head> и <body> , просто вставьте его вверху после любых критических ресурсов / приложений (например, CSS). В идеале это должно быть до любого другого JavaScript, чтобы вы могли фиксировать ошибки из скриптов, загружаемых после этого кода.

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

Если у кого-то есть идея лучшего подхода, я очень хочу ее услышать.

Изменить: извините за редактирование этого комментария так много раз. Сейчас он на стабильном уровне. Было весело доводить его до такого состояния! : смайлик:

После загрузки библиотеки часов качество отчетов об ошибках точно такое же, как при загрузке синхронизации? (Я так полагаю, просто проверяю)

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

согласен с @ dalyr95 . была бы полезна функция обратного вызова. возможно пользовательское событие загрузки ворона.

У меня просьба аналогичная @ dalyr95. Прямо сейчас единственный способ вызвать setUserContext() - это изменить фрагмент загрузчика, который не так чист, как возможность передать обратный вызов основному объекту конфигурации.

Здравствуйте, спасибо, что сообщили о проблеме.

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

Спасибо за понимание,
Ваше здоровье!

Мое решение заключалось в том, чтобы добавить 'undefined'!=k.setup&&k.setup(); сразу после вызова .install() , затем я добавил функцию setup в SENTRY_SDK с моим кодом инициализации публикации.

С помощью асинхронного загрузчика я смог установить контекст пользователя и другую информацию, передав ее в качестве второго аргумента в Raven.config :

<script>
    Raven.config("https://<mydsn>@sentry.io/<projectid>", 
      {"release":"0.3.1",
       "environment":"dev",
       "user": {"id":"7b031fa0-32ff-46fe-b94b-e6bc201c3c5f",
                "organisation-id":"b1a50c41-b85e-4c50-9cec-c55ff36cf6d1"}}).install();
</script>

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

@danielcompton может получать информацию о пользователе только через backend api?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги