Sentry-javascript: Errores de captura y carga asincrónica

Creado en 18 dic. 2013  ·  36Comentarios  ·  Fuente: getsentry/sentry-javascript

Quiero poder cargar raven.js de forma asincrónica, pero aún así poder capturar errores mientras se carga el script. (Algo parecido a cómo Google Analytics maneja los eventos almacenándolos en una variable hasta que se carga la biblioteca).

Esto es lo que tengo hasta ahora: https://gist.github.com/karolisdzeja/8010574

Pero hacerlo de esa manera pierde parte de la información y los detalles que normalmente proporciona Raven. ¿Hay alguna forma de almacenar la información de error completa de manera similar a esto?

Feature

Comentario más útil

Aquí está el fragmento que usamos para poner en cola las llamadas a los métodos de Raven de forma transparente: https://gist.github.com/Kl0tl/ed0a9e74462a2294f4c8842f5389d8ea.

El simulacro ciertamente podría mejorarse, pero no necesitamos replicar más funcionalidades. Object.defineProperty nos permite conectarnos justo después de que Raven adhiera a la ventana, pero tal vez el evento de carga del script sea suficiente. Sería bueno tener una forma oficial de habilitar esto.

Todos 36 comentarios

+1

+1

+1

+1

Todos los que han comentado sobre esto, ¿qué hay de malo con la solución de @karolisdzeja?

En última instancia, no estoy seguro de cómo podemos agregar una función a la fuente de Raven.js que se supone que funciona cuando la fuente de Raven.js no está en la página. Creo que, en última instancia, siempre será una solución personalizada; en el mejor de los casos, podríamos agregar un "cómo" a nuestros documentos.

@benvinegar, la solución se ve bien, pero sería mejor si esto fuera oficialmente respaldado y documentado. Estoy más feliz de tener que confiar en el equipo Sentry antes que evaluar una esencia aleatoria.

En realidad, una solución mucho mejor sería algo como el código JS SDK de Twitter: https://dev.twitter.com/web/javascript/loading

Configure una cola de funciones en la carga de la página que luego se consume cuando se carga el js externo, reemplazando el objeto proxy. Y asegúrese de que todas las llamadas a la API pasen por algo como una llamada .ready () al proxy.

Esto asegura que cualquier llamada se pueda poner en cola antes de que el js se haya cargado en lugar de solo captureMessage, sin tener que hacer proxy de cada función individualmente.

Me encantaría poder cargar raven.js de forma asincrónica / diferida y no tener que preocuparme.

Otros problemas con la esencia: bloquea window.onerror e introduce algunas variables globales no contenidas.

Tampoco hace uso de la función traceKitWindowOnError con todas las funciones que usa raven.js cuando se carga.

Rehice ligeramente la esencia de arriba: https://gist.github.com/oroce/ec3786ba7eff59963842220c3ffc56b4

No hay ninguna variable con fugas. Mantuve el controlador window.onerror , pero siéntase libre de usar window.addEventListener('error', fn) .

La mayor ayuda en este momento es tener traceKitWindowOnError como función exportada de Raven. Dado que esa es la función a la que se llama cuando ocurre un error: https://github.com/getsentry/raven-js/blob/master/dist/raven.js#L2074

Sé que no tendríamos ese seguimiento de pila adecuado, pero tendríamos algo mejor que lo que tenemos ahora.

Hay otras desventajas de hacer esto:

  • Al confiar en window.onerror para detectar errores antes de que se cargue Raven, los seguimientos de pila no están disponibles para todos los navegadores

    • además de no tener un seguimiento de pila, esto afectará negativamente a la agrupación

    • esta es la razón por la que install() intenta / captura la instrumentación

  • Las trazas sintéticas no funcionarán (todas aparecerán como originadas en este código)
  • Sin recolección de migas de pan

Por lo tanto, está intercambiando un rendimiento potencialmente mejor por informes de errores de menor calidad. Si la ejecución asíncrona es importante, le recomiendo que empaque Raven con su propio código para que se sirva en conjunto.

@benvinegar, tienes toda la razón. En aplicaciones que no son públicas (también conocidas como Google no llegará a las páginas), la forma clásica (bloqueo) de cuervo está completamente bien, pero tan pronto como tenga un sitio público donde los puntos de información de la página de Google sean importantes, debemos optimizar cómo cargamos el código de terceros (este es el precio que estamos dispuestos a pagar a favor de ux, velocidad y una mejor posición en los resultados de búsqueda).

Además, incluir raven en nuestro paquete es una solución, pero tan pronto como empiece a optimizar su código de interfaz con optimizaciones por encima de la mitad, como dividir su paquete en varios utilizando herramientas como factor-bundle o incluir varios paquetes para ganar más velocidad, la solución anterior puede ser mejor en mi opinión, pero estoy abierto a sugerencias.

Todos tienen compensaciones, por lo que sería genial si pudiéramos documentar todas las estrategias disponibles, así que dependiendo de cada aplicación web específica, aplicaremos diferentes estrategias.
Con la estrategia asíncrona, deberíamos cargar el script después del evento onload , en lugar de cargar solo asíncrono, para evitar bloquear la representación del evento 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');

Creo que probablemente solo documentaremos un fragmento de código asincrónico, como los proporcionados anteriormente, pero mencionamos que viene con compensaciones.

Solo un comentario más. Esas compensaciones pueden parecer aceptables, pero trato con muchos tickets de soporte de usuarios sobre errores de baja fidelidad que están experimentando y que se cree (incorrectamente) que se derivan de Raven.js. Mi temor es que si animo a las personas a usar el enfoque asíncrono, cada vez más personas me preguntan "por qué no hay seguimiento de pila" y otras quejas cuando es porque este enfoque es de menor fidelidad. Estoy dispuesto a aceptar eso, pero es una píldora difícil de tragar. 😓

@benvinegar Entiendo totalmente de dónde vienes, sé lo que estoy haciendo, así que no espero stacktrace cuando no debería haberlo. Para los recién llegados, los menos experimentados o simplemente los usuarios con diferentes casos de uso, puede ser líder seguro.

@oroce : sí, esto no es 100% una preocupación para las personas en este hilo, sino para las personas que podrían seguir esta estrategia sin comprender las advertencias correctamente (por ejemplo, simplemente copiar / pegar).

Mantendré este problema abierto, con un plan para agregar el fragmento a los documentos Install , y pondré un montón de advertencias por todas partes.

Gracias nuevamente por su participación aquí / por convencerme de hacer esto.

Aquí está el fragmento que usamos para poner en cola las llamadas a los métodos de Raven de forma transparente: https://gist.github.com/Kl0tl/ed0a9e74462a2294f4c8842f5389d8ea.

El simulacro ciertamente podría mejorarse, pero no necesitamos replicar más funcionalidades. Object.defineProperty nos permite conectarnos justo después de que Raven adhiera a la ventana, pero tal vez el evento de carga del script sea suficiente. Sería bueno tener una forma oficial de habilitar esto.

Hola chicos, me pregunto si hay algo de malo en la forma en que Raygun lo hace de forma asincrónica.
No estoy seguro, pero parece manejar bien los casos extremos. Aunque podría estar equivocado :)

@ Kl0tl muy agradable, gracias

Esto es muy simple usando una importación dinámica . Todavía en stage3 pero compatible con webpack.

Simplemente usamos la promesa como una cola. Una vez completado, todas las devoluciones de llamada se ejecutan en orden.

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

De manera similar a @zanona , también preferiría tener un código de seguimiento simple como Raygun o Google Analytics . A continuación, se muestra un ejemplo de 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 ¿Es posible algo como esto con Raven.js? ¿Quizás en el futuro?

@kireerik definitivamente se implementará (probablemente como una documentación de cómo hacerlo), pero ahora no puedo darle una fecha estimada precisa.

@kamilogorek Suena genial (no me gusta la solución alternativa). ¡No hay problema!

Para cualquier persona interesada, he presentado una idea general de otra forma de cargar ravenjs de forma asincrónica:
https://gist.github.com/MaxMilton/e2338b02b7381fc7bef2ccd96f434201

Se basa en el código de @oroce, pero las diferencias clave son que uso una etiqueta <script async src'='..."> regular en el encabezado del documento para un mejor rendimiento (los navegadores pueden programar la obtención del recurso antes) + No me molesto en envolverlo un IIFE y otros pequeños ajustes.

@MaxMilton ¡ Bonito! He creado mi propio sabor basado en el tuyo:

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

También tengo alguna pregunta:

  • Es necesario definir el crossorigin atributo en el script etiqueta?
  • ¿Es suficiente pasar solo el objeto de error en lugar de la otra solución ?

¿Qué piensas? ¿Cuál es la opinión del autor (@kamilogorek) sobre esto?

@kireerik cuando pones crossorigin="anonymous" en scripts, te permite capturar completamente los errores (de ese script externo) con el evento window.onerror . También evita que el navegador envíe credenciales con la solicitud de recuperación, que suele ser lo que desea con los recursos de terceros. MDN referencia 1 , MDN referencia 2 .

Puede simplemente pasar el error y funcionará la mayor parte del tiempo. La advertencia es que los navegadores antiguos (por ejemplo, Firefox antes de la versión 31) no pasan las propiedades columnNo o Error Object al evento window.onerror . Entonces, si desea una compatibilidad realmente buena, debe hacer un poco más. Referencia de MDN .

EDITAR: Consejo adicional: resulta que cuando pones crossorigin sin ningún valor, se trata de la misma manera que crossorigin="anonymous" .

Para su información, he actualizado mi esencia anterior a algo que está mucho más listo para la producción:

  • muchos comentarios para explicar lo que está pasando realmente
  • gran limpieza + uso de nombres de variables descriptivas (siempre una buena ventaja: guiño :)
  • envolver en IIFE para no contaminar el espacio de nombres global
  • corregir parámetros incorrectos pasados ​​al elemento de matriz de error

Vea la esencia si quiere entender todo O si prefiere copiar y pegar rápidamente, aquí está la versión reducida:

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

Reemplace ___PUBLIC_DSN___ con su DSN y péguelo en algún lugar de la cabeza cerca de su etiqueta de cierre </head> . O si eres un hipster que ya no usa las etiquetas <head> y <body> pégalo cerca de la parte superior después de cualquier recurso crítico / de la aplicación (por ejemplo, CSS). Idealmente, debería estar antes que cualquier otro JavaScript para que pueda capturar errores de los scripts cargados después de este código.

En mis pruebas rápidas no ha habido ningún problema, por lo que no veo ninguna razón para no usar esto en la versión sincrónica predeterminada.

Si alguien tiene una idea para un mejor enfoque, me encantaría escucharla.

Editar: Lo siento por editar este comentario tantas veces. Ahora está en un nivel estable. ¡Fue divertido llegar a este estado! : smiley:

Una vez que se carga la biblioteca centinela, ¿la calidad del informe de errores es exactamente la misma que la de cargarla sincronizada? (Supongo que sí, solo comprobando)

Además, en los documentos que desee agregar, no puede usar Raven hasta que se cargue la biblioteca, tal vez proporcione una función de devolución de llamada en las opciones para que pueda configurar el contexto del usuario, etc.

de acuerdo con @ dalyr95 . una función de devolución de llamada sería útil. tal vez un evento de carga cuervo personalizado.

Tengo una solicitud similar a @ dalyr95. En este momento, la única forma de llamar a setUserContext() es modificar el fragmento del cargador, que no es tan limpio como poder pasar una devolución de llamada en el objeto de configuración principal.

Hola, gracias por informar del problema.

Estamos trabajando en la próxima versión principal de nuestro SDK.
Por eso, tuvimos que suspender el trabajo en la versión actual (excepto errores importantes o de seguridad).
Intentaremos volver a todos los informes lo antes posible, así que tenga paciencia.

Gracias por su comprensión,
¡Salud!

Mi solución fue agregar 'undefined'!=k.setup&&k.setup(); inmediatamente después de la llamada a .install() , luego agregué una función llamada setup a SENTRY_SDK con mi código post init.

Con el cargador asíncrono, pude configurar el contexto del usuario y otra información pasándola como un segundo argumento a 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>

Creo que ya existe todo para esto, simplemente podría estar mejor documentado.

¿@danielcompton solo puede obtener información del usuario a través de la API de backend?

¿Fue útil esta página
0 / 5 - 0 calificaciones