Sentry-javascript: Asynchrones Laden und Erfassen von Fehlern

Erstellt am 18. Dez. 2013  ·  36Kommentare  ·  Quelle: getsentry/sentry-javascript

Ich möchte raven.js asynchron laden, aber trotzdem Fehler beim Laden des Skripts erfassen können. (So ​​etwas wie Google Analytics behandelt Ereignisse, indem es sie in einer Variablen speichert, bis die Bibliothek geladen wird).

Folgendes habe ich bisher: https://gist.github.com/karolisdzeja/8010574

Aber auf diese Weise gehen einige der Informationen und Details verloren, die Raven normalerweise bereitstellt. Gibt es eine Möglichkeit, die vollständigen Fehlerinformationen ähnlich zu speichern?

Feature

Hilfreichster Kommentar

Hier ist der Ausschnitt, den wir verwenden, um Aufrufe von Raven-Methoden transparent in die Warteschlange zu stellen: https://gist.github.com/Kl0tl/ed0a9e74462a2294f4c8842f5389d8ea.

Der Mock könnte sicherlich verbessert werden, aber wir müssen nicht mehr Funktionalitäten replizieren. Object.defineProperty ermöglicht es uns, direkt einzuhaken, nachdem Raven selbst an das Fenster angehängt hat, aber vielleicht reicht das Skript-Lade-Ereignis aus. Wäre schön, wenn es eine offizielle Möglichkeit gäbe, dies zu aktivieren.

Alle 36 Kommentare

+1

+1

+1

+1

Alle, die das kommentiert haben – was ist falsch an der Lösung von @karolisdzeja?

Letztendlich bin ich mir nicht sicher, wie wir der Raven.js-Quelle eine Funktion hinzufügen können, die funktionieren soll, wenn die Raven.js-Quelle nicht auf der Seite ist. Ich denke, dies wird letztendlich immer eine individuelle Lösung sein; bestenfalls könnten wir unseren Dokumenten ein "How To" hinzufügen.

@benvinegar die Lösung sieht gut aus, aber es wäre besser, wenn dies offiziell unterstützt und dokumentiert würde. Ich bin glücklicher, wenn ich dem Sentry-Team einfach vertrauen muss, anstatt einen zufälligen Kern zu bewerten.

Eigentlich wäre eine viel bessere Lösung so etwas wie der JS SDK-Code von Twitter: https://dev.twitter.com/web/javascript/loading

Richten Sie beim Laden der Seite eine Funktionswarteschlange ein, die dann verbraucht wird, wenn die externe js geladen wurde, und ersetzt das Proxy-Objekt. Und stellen Sie sicher, dass alle API-Aufrufe so etwas wie einen .ready()-Aufruf an den Proxy durchlaufen.

Dadurch wird sichergestellt, dass jeder Aufruf in die Warteschlange eingereiht werden kann, bevor js geladen wurde und nicht nur captureMessage, ohne dass jede Funktion einzeln als Proxy übergeben werden muss.

Ich würde gerne raven.js einfach asynchron/deferred laden können und mir keine Sorgen machen.

Andere Probleme mit dem Kern: Es überlagert window.onerror und führt ein paar nicht enthaltene globale Variablen ein.

Es verwendet auch nicht die voll funktionsfähige traceKitWindowOnError-Funktion, die raven.js beim Laden verwendet.

Ich habe das Wesentliche oben etwas überarbeitet: https://gist.github.com/oroce/ec3786ba7eff59963842220c3ffc56b4

Es gibt keine undichte Variable. Ich habe den window.onerror Handler beibehalten, aber Sie können gerne window.addEventListener('error', fn) .

Die größte Hilfe dabei ist, traceKitWindowOnError als exportierte Funktion von Raven zu haben. Da dies die Funktion ist, die aufgerufen wird, wenn ein Fehler auftritt: https://github.com/getsentry/raven-js/blob/master/dist/raven.js#L2074

Ich weiß, dass wir nicht den richtigen Stacktrace hätten, aber wir hätten etwas Besseres als das, was wir jetzt haben.

Dies hat auch andere Nachteile:

  • Indem man sich darauf verlässt, dass window.onerror Fehler abfängt, bevor Raven geladen wird, sind Stacktraces nicht für jeden Browser verfügbar

    • Abgesehen davon, dass kein Stack-Trace vorhanden ist, wirkt sich dies negativ auf die Gruppierung aus

    • Aus diesem Grund versucht install() Instrumentierung

  • Synthetische Spuren funktionieren nicht (sie werden alle als von diesem Code stammend erscheinen)
  • Keine Breadcrumb-Sammlung

Sie tauschen also möglicherweise eine bessere Leistung gegen Fehlerberichte von geringerer Qualität. Wenn die asynchrone Ausführung wichtig ist, würde ich eher empfehlen, Raven mit Ihrem eigenen Code zu bündeln, damit er zusammen bereitgestellt wird.

@benvinegar du hast vollkommen recht. In Anwendungen, die nicht öffentlich sind (auch bekannt als Google erreicht die Seiten nicht) ist der klassische (blockierende) Raben-Weg völlig in Ordnung, aber sobald Sie eine öffentlich zugängliche Website haben, bei der die Einblicke der Google-Seite wichtig sind, müssen wir optimieren wie wir Code von Drittanbietern laden (dies ist der Preis, den wir für ux, Geschwindigkeit und bessere Position der Suchergebnisse zu zahlen bereit sind).

Außerdem Bündelung Rabe in unser Bündel eine Lösung ist, aber sobald Sie in der Optimierung Ihrer Frontend - Code bekommen , über den Falten Optimierungen, wie Splitting Ihr Bündel in mehrere diejenigen Tools wie mit Faktor-Bündel oder Sie umfassen mehrere Bündel mehr Geschwindigkeit zu gewinnen, die obige lösung kann imo eine bessere sein, aber ich bin offen für vorschläge.

Sie alle haben Kompromisse, daher wäre es großartig, wenn wir alle verfügbaren Strategien dokumentieren könnten. Abhängig von der jeweiligen Webanwendung werden wir unterschiedliche Strategien anwenden.
Bei der asynchronen Strategie sollten wir das Skript nach dem onload Ereignis laden, anstatt nur asynchron zu laden, um zu verhindern, dass das onload Ereignis beim Rendern blockiert wird.

/**
 * 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');

Ich denke, wir werden wahrscheinlich nur ein asynchrones Snippet dokumentieren, wie das oben bereitgestellte, aber erwähnen Sie, dass es mit Kompromissen einhergeht.

Nur noch ein Kommentar. Diese Kompromisse mögen akzeptabel erscheinen, aber ich beschäftige mich mit vielen Support-Tickets von Benutzern über Low-Fidelity-Fehler, die bei ihnen auftreten und von denen (fälschlicherweise) angenommen wird, dass sie von Raven.js stammen. Meine Befürchtung ist, dass, wenn ich die Leute dazu ermutige, den asynchronen Ansatz zu verwenden, ich immer mehr Leute fragen werde "Warum gibt es keinen Stack-Trace" und andere Beschwerden, wenn dies daran liegt, dass dieser Ansatz eine geringere Wiedergabetreue hat. Ich bin bereit, das zu akzeptieren, aber es ist eine schwer zu schluckende Pille. 😓

@benvinegar Ich

@oroce – ja, das betrifft die Leute in diesem Thread zu 100% nicht, sondern Leute, die diese Strategie verfolgen könnten, ohne die Vorbehalte richtig zu verstehen (zB nur kopieren/einfügen).

Ich werde dieses Thema offen halten, mit dem Plan, das Snippet zu den Install -Dokumenten hinzuzufügen – und ich werde überall eine Reihe von Warnungen platzieren.

Nochmals vielen Dank für Ihre Teilnahme hier / Ihre Überzeugung, dies zu tun.

Hier ist der Ausschnitt, den wir verwenden, um Aufrufe von Raven-Methoden transparent in die Warteschlange zu stellen: https://gist.github.com/Kl0tl/ed0a9e74462a2294f4c8842f5389d8ea.

Der Mock könnte sicherlich verbessert werden, aber wir müssen nicht mehr Funktionalitäten replizieren. Object.defineProperty ermöglicht es uns, direkt einzuhaken, nachdem Raven selbst an das Fenster angehängt hat, aber vielleicht reicht das Skript-Lade-Ereignis aus. Wäre schön, wenn es eine offizielle Möglichkeit gäbe, dies zu aktivieren.

Hey Leute, ich frage mich nur, ob an der Art und Weise, wie Raygun das asynchron macht, etwas nicht stimmt?
Ich bin mir nicht sicher, aber es scheint gut mit Randfällen umzugehen? Ich könnte mich aber irren :)

@Kl0tl sehr schön, danke

Das geht ganz einfach mit einem dynamischen Import . Noch in Stufe3, aber von Webpack unterstützt.

Wir verwenden das Versprechen einfach als Warteschlange. Sobald sie erfüllt sind, werden alle Callbacks der Reihe nach ausgeführt.

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

Ähnlich wie bei @zanona hätte ich auch lieber einen einfachen Tracking-Code wie Raygun oder Google Analytics . Hier ist ein Beispiel für 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 Ist so etwas mit Raven.js möglich? Vielleicht in der Zukunft?

@kireerik es wird definitiv implementiert (höchstwahrscheinlich als Dokumentations- Datumsschätzung geben.

@kamilogorek Klingt großartig (ich mag den Workaround als Lösung nicht). Kein Problem!

Für alle Interessierten habe ich eine weitere Möglichkeit zum asynchronen Laden von ravenjs aufgeführt:
https://gist.github.com/MaxMilton/e2338b02b7381fc7bef2ccd96f434201

Es basiert auf dem Code von @oroce, aber mit den Hauptunterschieden verwende ich ein <script async src'='..."> Tag im Dokumentkopf für eine bessere Leistung (Browser können das Abrufen der Ressource früher planen) + ich kümmere mich nicht darum, es einzuschließen ein IIFE und andere kleine Optimierungen.

@MaxMilton Schön! Ich habe meinen eigenen Geschmack basierend auf Ihrem kreiert:

<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>

Ich habe auch eine Frage:

  • Ist es erforderlich , die definieren crossorigin Attribut für das script - Tag?
  • Reicht es aus, das Fehlerobjekt anstelle der anderen Lösung zu übergeben ?

Was denkst du? Was ist die Meinung des Autors (@kamilogorek) dazu?

@kireerik Wenn Sie crossorigin="anonymous" in Skripte eingeben , können Sie Fehler (aus diesem externen Skript) mit dem Ereignis window.onerror vollständig erfassen. Es verhindert auch, dass der Browser Anmeldeinformationen mit der Abrufanforderung sendet, was normalerweise bei Ressourcen von Drittanbietern erwünscht ist. MDN-Referenz 1 , MDN-Referenz 2 .

Sie können den Fehler einfach übergeben und er wird _die meiste_ Zeit funktionieren. Der Vorbehalt, dass alte Browser (zB Firefox vor Version 31) die Eigenschaften columnNo oder Error Object nicht an das window.onerror Ereignis übergeben. Wenn Sie also eine wirklich gute Kompatibilität wünschen, müssen Sie dieses zusätzliche bisschen tun. MDN-Referenz .

BEARBEITEN: Bonus-Tipp: Es stellt sich heraus, dass wenn Sie crossorigin ohne Wert eingeben, es genauso behandelt wird wie crossorigin="anonymous" .

Zu Ihrer Information , ich habe meinen

  • viele Kommentare, um zu erklären, was eigentlich los ist
  • großes Aufräumen + beschreibende Variablennamen verwenden (immer ein netter Bonus :wink: )
  • Wrap in IIFE, um den globalen Namensraum nicht zu verschmutzen
  • Korrigieren Sie falsche Parameter, die an das Fehler-Array-Element übergeben wurden

Sehen Sie sich das Wesentliche an, wenn Sie alles verstehen möchten ODER wenn Sie ein schnelles Kopieren + Einfügen bevorzugen, hier ist die minimierte Version:

<!-- 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">

Ersetzen Sie ___PUBLIC_DSN___ durch Ihre DSN und fügen Sie diese irgendwo im Kopf in der Nähe Ihres schließenden </head> Tags ein. Oder wenn Sie ein Hipster sind, der keine <head> und <body> Tags mehr verwendet, dann fügen Sie es einfach oben nach allen kritischen/App-Ressourcen (zB CSS) ein. Idealerweise sollte es vor jedem anderen JavaScript stehen, damit Sie Fehler von Skripts erfassen können, die nach diesem Code geladen werden.

In meinen Schnelltests gab es keine Probleme, daher sehe ich keinen Grund, dies nicht über die synchrone Standardversion zu verwenden.

Wenn jemand eine Idee für einen besseren Ansatz hat, bin ich gespannt darauf, sie zu hören.

Bearbeiten: Entschuldigung, dass ich diesen Kommentar so oft bearbeitet habe. Es ist jetzt auf einem stabilen Niveau. Es hat Spaß gemacht, es in diesen Zustand zu bringen! :smiley:

Sobald die Wachpostenbibliothek geladen ist, ist die Qualität der Fehlerberichterstattung genau die gleiche wie beim Laden der Synchronisierung? (Ich gehe davon aus, prüfe nur)

Auch in den Dokumenten, die Sie vielleicht hinzufügen möchten, können Sie Raven nicht verwenden, bis die Bibliothek geladen ist. Stellen Sie möglicherweise eine Rückruffunktion in den Optionen bereit, damit Sie den Benutzerkontext usw. festlegen können?

stimme @dalyr95 zu . eine Rückruffunktion wäre sinnvoll. vielleicht ein benutzerdefiniertes Rabenladeereignis.

Ich habe eine ähnliche Anfrage wie @dalyr95. Im Moment besteht die einzige Möglichkeit, setUserContext() aufzurufen, darin, das Loader-Snippet zu ändern, das nicht so sauber ist, wie ein Callback an das Hauptkonfigurationsobjekt übergeben zu können.

Hallo, danke für die Meldung des Problems.

Wir sind dabei, an der nächsten Hauptversion unseres SDK zu arbeiten.
Aus diesem Grund mussten wir die Arbeit an der aktuellen Version (außer Major oder Security Bugs) auf Eis legen.
Wir werden versuchen, so schnell wie möglich auf alle Berichte zurückzukommen, also haben Sie bitte etwas Geduld.

Danke für dein Verständnis,
Beifall!

Meine Lösung bestand darin, 'undefined'!=k.setup&&k.setup(); direkt nach dem Aufruf von .install() hinzuzufügen, dann fügte ich eine Funktion namens setup zu SENTRY_SDK mit meinem Post-Init-Code hinzu.

Mit dem asynchronen Loader konnte ich den Benutzerkontext und andere Informationen festlegen, indem ich ihn als zweites Argument an 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>

Ich denke, dafür gibt es schon alles, es könnte nur besser dokumentiert werden.

@danielcompton kann Benutzerinformationen nur über die Backend-API

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen