如何在主 JS 包加载之前加载 Sentry?

How do I load Sentry before the main JS bundle loads?

我使用 Sentry 来跟踪客户端错误。然而,我今天在 Edge 浏览器中加载了我的网络应用程序,却发现了一个空白页面。 Edge 引发了 TextEncoder is not defined 错误,因为我的包中的一个库引用了它不支持的 TextEncoder。 Sentry没有报错是因为错误发生在Sentry初始化之前。

我使用 vue-cli 创建了一个 Vue 项目,在主文件顶部附近初始化了 Sentry:

import { init } from '@sentry/browser';
import { environment } from '@/constants';
import { Vue as VueIntegration } from '@sentry/integrations';

export default function(Vue) {
  const debug = environment !== 'production';

  init({
    dsn: 'redacted',
    environment,
    debug,
    integrations: [new VueIntegration({ Vue, logErrors: debug })],
  });
}

我一直在考虑在 <body> 标记的开头附近使用脚本标记手动初始化 Sentry。但是,我使用 VueIntegration 插件这一事实使事情变得复杂。初始化哨兵两次安全吗?一次是在主包加载之前,一次是我在上面的例子中所做的?

我注意到文档中有关于 managing multiple Sentry clients 的内容,但我不确定这是否与我的具体情况相关。

我的一个想法是在加载任何其他内容之前只是一个准系统 window.onerror 挂钩,但我不确定如何在不拉入他们的 @sentry/browser 包的情况下与 Sentry 交互。理想情况下,我会使用简单的 XHR 请求和我的 DSN 与他们的服务进行通信。

我的问题是跟踪在主 JS 包中初始化 Sentry 之前发生的错误的推荐方法是什么?

我最终通过添加准系统 window.onerror 钩子解决了这个问题,该钩子在主包到达之前内联加载。该错误会立即发送到我们的 API,然后发送到我们的 Slack #alerts 频道。我添加了速率限制,这样人们就不会滥用它(太多)。

index.html(由 vue-cli 生成,除了新的脚本标签):

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
/>
    <title>App title</title>
  </head>
  <body>
    <script>
      // This gives us basic error tracking until the main app bundle loads and
      // initializes Sentry. Allows us to catch errors that would surface before
      // Sentry has a chance to catch them like Edge's `TextEncoder is not defined`.
      (function() {
        function sendBasicClientError(message, error) {
          var xhr = new XMLHttpRequest();
          var domain =
            window.location.hostname === 'localhost'
              ? 'http://localhost:5000'
              : 'https://example.com';

          xhr.open('POST', domain + '/api/v1/basic_client_errors');
          xhr.setRequestHeader(
            'Content-Type',
            'application/vnd.api+json; charset=utf-8'
          );
          xhr.send(
            JSON.stringify({
              data: {
                type: 'basic_client_error',
                attributes: {
                  error_message: 'Init error: ' + message + ' ' + navigator.userAgent,
                  error: error
                    ? JSON.parse(
                        JSON.stringify(error, Object.getOwnPropertyNames(error))
                      )
                    : null,
                },
              },
            })
          );
        }

        window.onerror = function(message, filename, lineno, colno, error) {
          sendBasicClientError(
            message + ' ' + filename + ':' + lineno + ':' + colno,
            error
          );
        };
      })();
    </script>

    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

就在 Sentry 加载之前,我们清除挂钩:

// Clears the simple `window.onerror` from `index.html` so that Sentry can
// take over now that it's ready.
window.onerror = () => {};

init({
  dsn: 'redacted',
  environment,
  debug,
  integrations: [new VueIntegration({ Vue, logErrors: debug })],
});

Rails 控制器:

module Api
  module V1
    class BasicClientErrorsController < ApplicationController
      def create
        # Can comment out if not using the `pundit` gem.
        skip_authorization

        # We use `sidekiq` and `slack-ruby-client` gems here.
        # Substitute whatever internal error tracking tool you use.               
        SlackNotifierWorker.perform_async(
          basic_client_error_params[:error_message], 
          '#alerts'
        )

        head :accepted
      end

      private

      def basic_client_error_params
        # We use the `restful-jsonapi` gem to parse the JSON:API format.
        restify_param(:basic_client_error).require(:basic_client_error).permit(
          :error_message
        )
      end
    end
  end
end

速率限制 rack-attack gem:

Rack::Attack.throttle('limit public basic client errors endpoint', limit: 1, period: 60.seconds.to_i) do |req|
  req.ip if req.path.end_with?('/basic_client_errors') && req.post?
end

你试过这个吗Laxy loading Sentry

这里提到如果你设置data-lazyno,或者使用forceLoad,它会尽快尝试获取Sentry SDK。

如前所述here:

,此问题应由加载程序处理

Current limitations Because we inject our SDK asynchronously, we will only monitor global errors and unhandled promise for you until the SDK is fully loaded. That means that we might miss breadcrumbs during the download.

For example, a user clicking on a button on your website is making an XHR request. We will not miss any errors, only breadcrumbs and only up until the SDK is fully loaded. You can reduce this time by manually calling forceLoad or set data-lazy="no".