Sentry-javascript: 异步加载和捕获错误

创建于 2013-12-18  ·  36评论  ·  资料来源: getsentry/sentry-javascript

我希望能够异步加载 raven.js,但仍然能够在加载脚本时捕获错误。 (类似于 Google Analytics 如何通过将事件存储在变量中直到库加载来处理事件)。

这是我到目前为止所拥有的: https :

但是这样做会丢失 Raven 通常提供的一些信息和细节。 有没有办法存储与此类似的完整错误信息?

Feature

最有用的评论

这是我们用来透明地对 Raven 方法的调用进行排队的代码片段: https :

模拟当然可以改进,但我们不需要复制更多功能。 Object.defineProperty允许我们在Raven将自身附加到窗口后立即挂钩,但也许脚本加载事件就足够了。 有一种官方的方式来实现这一点会很好。

所有36条评论

+1

+1

+1

+1

对此发表评论的每个人 - @karolisdzeja 的解决方案有什么问题?

最终,我不确定我们如何向 Raven.js 源添加一个功能,当 Raven.js 源不在页面上时,该功能应该可以工作。 我认为这最终将始终是一个定制的解决方案; 充其量我们可以在我们的文档中添加“操作方法”。

@benvinegar解决方案看起来不错,但如果得到官方支持和记录会更好。 我很高兴能够信任 Sentry 团队而不是评估随机要点。

实际上,更好的解决方案类似于 Twitter 的 JS SDK 代码: https :

在页面加载时设置一个函数队列,然后在加载外部 js 时使用它,替换代理对象。 并确保所有 API 调用都经过类似于 .ready() 调用的代理。

这确保了任何调用都可以在 js 加载之前排队,而不仅仅是 captureMessage,而不必单独代理每个函数。

我希望能够异步/延迟加载 raven.js 而不必担心。

要点的其他问题:它破坏了 window.onerror 并引入了一些未包含的全局变量。

它也没有使用 raven.js 在加载时使用的全功能 traceKitWindowOnError 函数。

我稍微重做了上面的要点: https :

没有泄漏变量。 我保留了window.onerror处理程序,但可以随意使用window.addEventListener('error', fn)

在这一点上最大的帮助是将traceKitWindowOnError作为 Raven 的导出函数。 由于这是发生错误时调用的函数: https :

我知道我们不会有那种非常合适的堆栈跟踪,但我们会有比现在更好的东西。

这样做还有其他缺点:

  • 通过依赖window.onerror在加载 Raven 之前捕获错误,堆栈跟踪不适用于每个浏览器

    • 除了没有堆栈跟踪之外,这将对分组产生负面影响

    • 这就是为什么install()会尝试/捕获检测

  • 合成跟踪将不起作用(它们都将显示为源自此代码)
  • 没有面包屑收集

因此,您可能会用更好的性能来换取较低质量的错误报告。 如果异步执行很重要,我会尽快建议您将 Raven 与您自己的代码捆绑在一起,以便它们一起提供。

@benvinegar你是完全正确的。 在非公开的应用程序中(即 google 无法访问页面),经典的(阻塞)raven 方式完全没问题,但是一旦您拥有一个面向公众的网站,其中 google 页面洞察点很重要,我们就需要优化我们如何加载第三方代码(这是我们愿意为支持用户体验、速度和更好的搜索结果位置而付出的代价)。

此外,将 raven 捆绑到我们的 bundle 中是一种解决方案,但是一旦您开始使用首屏优化来优化前端代码,例如使用factor-bundle等工具将您的 bundle 拆分为多个,或者您包含多个 bundle 以获得更高的速度,上述解决方案可能是一个更好的 imo,但我愿意接受建议。

他们都有权衡,所以如果我们能记录所有可用的策略,那就太好了,所以取决于每个特定的 Web 应用程序,我们将应用不同的策略。
使用异步策略,我们应该在onload事件之后加载脚本,而不是只加载异步,以防止阻塞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 :

模拟当然可以改进,但我们不需要复制更多功能。 Object.defineProperty允许我们在Raven将自身附加到窗口后立即挂钩,但也许脚本加载事件就足够了。 有一种官方的方式来实现这一点会很好。

嘿伙计们,只是想知道 Raygun 异步执行此操作的方式是否有问题?
我不确定,但它似乎可以很好地处理边缘情况? 不过我可能错了:)

@Kl0tl非常好,谢谢

使用动态导入非常简单。 仍处于 stage3,但已被 webpack 支持。

我们只是将 promise 用作队列。 一旦完成,所有回调将按顺序执行。

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});
    });
};

@zanona类似,我也更喜欢像 Raygun 或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>

我也有一些疑问:

  • 是否需要在script标签上定义crossorigin属性?
  • 只传递错误对象而不是其他解决方案就足够了吗?

你怎么认为? 作者 (@kamilogorek) 对此有何看法?

@kireerik当您将crossorigin="anonymous"放在脚本上时,它允许您使用window.onerror事件完全捕获错误(来自该外部脚本)。 它还可以防止浏览器通过 fetch 请求发送凭据,这通常是您想要使用 3rd 方资源的方式。 MDN 参考资料 1MDN 参考资料 2

你可以只传递错误,它会在_大多数_时间工作。 需要注意的是旧浏览器(例如版本 31 之前的 Firefox)不会将 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___替换</head>标签附近的头部某处。 或者,如果您是一个不再使用<head><body>标签的时髦人士,那么只需将其粘贴在任何关键/应用程序资源(例如 CSS)之后的顶部附近。 理想情况下,它应该任何其他 JavaScript

在我的快速测试中,没有任何问题,所以我看不出有任何理由不在默认同步版本上使用它。

如果有人有更好的方法的想法,我很想听听。

编辑:抱歉多次编辑此评论。 现在处于一个稳定的水平。 达到这个状态很有趣! :笑脸:

加载哨兵库后,错误报告质量与加载同步完全相同? (我假设是这样,只是检查)

同样在您可能想要添加的文档中,在加载 lib 之前您不能使用 Raven,也许在选项中提供回调函数以便您可以设置用户上下文等?

同意@dalyr95 。 回调函数会很有用。 也许是自定义 raven 加载事件。

我和@dalyr95 有类似的要求。 现在调用setUserContext()的唯一方法是修改加载器片段,它不像能够在主配置对象上传递回调那样干净。

您好,感谢您报告问题。

我们正在开发 SDK 的下一个主要版本。
因此,我们不得不搁置当前版本(主要或安全错误除外)的工作。
我们会尽快回复所有报告,请耐心等待。

谢谢你的理解,
干杯!

我的解决方案是在调用.install()后立即添加'undefined'!=k.setup&&k.setup(); ,然后我将一个名为setup的函数添加到SENTRY_SDK并使用我的 post init 代码。

使用异步加载器,我能够通过将其作为第二个参数传递给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只能通过后端 api 获取用户信息?

此页面是否有帮助?
0 / 5 - 0 等级