Next.js: Cómo detectar y manejar errores para informar registros en el lado del servidor

Creado en 2 may. 2017  ·  74Comentarios  ·  Fuente: vercel/next.js

Hola,
Estoy en una situación en la que queremos enviar errores, tanto del lado del servidor como del cliente, a la herramienta Sentry.

Nuestra aplicación utiliza Express como servidor personalizado. Básicamente creamos una aplicación rápida, aplicamos algunos middlewares pero delegamos todo el trabajo real al identificador next.js:

  const app = nextJs({ dev: process.env.NODE_ENV !== 'production' });
  const handler = routes.getRequestHandler(app);
  const expressApp = express();

  ...
  ...

  expressApp.use(morgan('combined', { stream: logger.stream }));
  expressApp.use(statsdMiddleware);

  // Add security
  expressApp.use(helmet());

  // Sentry handler
  expressApp.use(sentry.requestHandler());

  // Load locale and translation messages
  expressApp.use(i18n);

  // Next.js handler
  expressApp.use(handler);

  // Sentry error handler.
  // MUST be placed before any other express error handler !!!
  expressApp.use(sentry.errorHandler());

Con este enfoque, next.js toma el control sobre el proceso de renderizado y cualquier error es detectado por next.js y la única forma que tengo de procesarlo es anulando el archivo de página _error.js .

Dentro de ese archivo _error.js necesito una forma universal de informar errores a Sentry. Actualmente hay dos bibliotecas ( raven para nodo y raven-js por javascript). El problema es que no puedo importar ambos porque raven funciona para SSR pero falla cuando el paquete web crea el paquete, y también raven-js falla debido a la dependencia XMLHTTPRequest.

¿Hay alguna manera de que se me pueda notificar el error de next.js en el lado del servidor?

story feature request

Comentario más útil

Tengo que amar las soluciones _unortodox_

function installErrorHandler(app) {
  const _renderErrorToHTML = app.renderErrorToHTML.bind(app)
  const errorHandler = rollbar.errorHandler()

  app.renderErrorToHTML = (err, req, res, pathname, query) => {
    if (err) {
      errorHandler(err, req, res, () => {})
    }

    return _renderErrorToHTML(err, req, res, pathname, query)
  }

  return app
}
// ¯\_(ツ)_/¯

Todos 74 comentarios

Para registrar errores en el lado del cliente, hemos estado haciendo lo siguiente:
https://gist.github.com/jgautheron/044b88307d934d486f59ae87c5a5a5a0

Básicamente, envía errores al servidor, que al final se imprimen en stdout y son detectados por nuestro controlador de registro Docker .
Hemos detectado correctamente los errores de SSR con react-guard sin necesidad de anular los archivos siguientes principales.

También tengo este problema. Me pregunto: ¿sería suficiente simplemente devolver una promesa rechazada del siguiente handleRequest ? Es decir, cambiando el código aquí para que sea:

handleRequest (req, res, parsedUrl) {
  // .....snip....

   return this.run(req, res, parsedUrl)
     .catch((err) => {
       if (!this.quiet) console.error(err)
       res.statusCode = 500
       res.end(STATUS_CODES[500])

       // rethrow error to create new, rejected promise 
       throw err;
     })
}

Luego, en el código de usuario:

const app = nextJs({ dev: process.env.NODE_ENV !== 'production' });
const nextJsHandler = app.getRequestHandler();
const expressApp = express();

app.prepare().then(() => {
   // invoke express middlewares
   // ...

   // time to run next
   expressApp.use(function(req, res, next) {
     nextJsHandler(req, res).catch(e => {
       // use rejected promise to forward error to next express middleware
       next(e)
     })
   });

   // Use standard express error middlewares to handle the error from next
   // this makes it easy to log to sentry etc.
})

@arunoda @rauchg ¿Crees que el cambio que propuse inmediatamente arriba funcionaría? Si es así, feliz de enviar un PR

Acepta volver a lanzar un error para que podamos jugar con él.
También es necesario volver a lanzar en renderToHTML también ...

También estoy en la situación en la que queremos enviar errores, tanto del lado del servidor como del cliente, a un servicio similar a Sentry.

Creo que la característica más valiosa de tales servicios / herramientas es informar los problemas más inesperados, que en mi experiencia son errores no detectados en la naturaleza (es decir, del lado del cliente). Desafortunadamente, como se destacó anteriormente en el problema relacionado # 2334, los controladores del lado del cliente de Next.js se guardan estos errores para sí mismos, sin ninguna forma posible de pasarlos a Sentry de dicha otra herramienta.

Lo que nos ha molestado especialmente es esto: una página renderizada correctamente en el lado del servidor se vuelve a representar como una página de error si se produce una excepción no detectada antes de la representación de React en el lado del cliente . Esto puede verse como una gran característica, pero también como una experiencia frustrante para el desarrollador, ya que esencialmente arruina los beneficios de entregar un documento ya renderizado, en una parte sorprendente de los clientes.

Aquí hay una página de muestra que ilustra el problema:

import React from 'react';

// Works well in Node 8, but crashes in Chrome<56, Firefox<48, Edge<15, Safari<10, any IE…
const whoops = 'Phew, I made it to the client-side…'.padEnd(80);

export default () => <pre>{whoops}</pre>;

El código anterior se puede representar perfectamente en el lado del servidor y entregarse al cliente, solo para convertirse en un Flash de contenido no deseado antes de que toda la página sea reemplazada en el lado del cliente por el temido mensaje "Se ha producido un error inesperado" en la mayoría de los navegadores. , sin ninguna forma posible de informar el error a Sentry (o cualquier otro servicio / herramienta).

Este “tragar error” evita además la influencia de la norma onerror manipulador, que la mayoría de errores del lado del cliente herramientas de reporte gancho en (o el más moderno, pero no se ha generalizado, onunhandledrejection , que puede ser más adecuado dada la naturaleza asíncrona del lado del cliente código).

Posible solución del lado del cliente

Por lo que puedo decir, este tipo de error del lado del cliente pre-React se ingiere en el bloque try / catch en el cliente / index.js de Next.js donde el Component punto de ser renderizado se reasigna al valor de ErrorComponent (actualmente líneas 67-72 ).

Estimados autores y mantenedores de Next.js, en aras del control de lo que se representa, ¿qué crees que sería aceptable / posible entre las siguientes ideas:

  • ¿Introducir un gancho en ese bloque catch en client / index.js para manejar este tipo de error?
  • transmitir el error a un controlador de rechazo onerror / onunhandled, si se detecta alguno?
  • proporcionar una opción para volver a lanzar el error?
  • proporcionar una opción para no mostrar el componente de error?
  • proporcionar una opción para mostrar un componente de error personalizado?

¿Introducir un gancho en ese bloque de captura en client / index.js para manejar este tipo de error?
transmitir el error a un controlador de rechazo onerror / onunhandled, si se detecta alguno?

Esto es algo de lo que hemos hablado internamente. Y nos dirigiremos pronto.

Estoy usando Sentry.io para informar errores y la solución que aplicamos es:

1- Configurar raven-node en el lado del servidor
2- Configure ravenjs en el lado del cliente (lo hicimos en _document .

Con estos dos pasos detectamos cualquier excepción no controlada tanto en el cliente como en el servidor.

3- Crea una página _error . Cualquier error producido una vez que nextjs maneja la solicitud (sin importar si es del lado del cliente o del servidor) esa página se representa. En el método getInitialProps de la página _error informamos el error al centinela.

La forma de decidir cómo cargar si raven-node o ravenjs se resuelve importando dinámicamente dependiendo si estamos en el cliente const Raven = require('raven-js'); o en el lado del servidor const Raven = require('raven'); .
Tenga en cuenta que hemos configurado el paquete web para no agrupar el módulo raven (el lado del servidor uno) actualizando next.config.js con:

const webpack = require('webpack');

module.exports = {
  // Do not show the X-Powered-By header in the responses
  poweredByHeader: false,
  webpack: (config) => {
    config.plugins.push(new webpack.IgnorePlugin(/^raven$/));
    return config;
  },
};

@acanimal

2- Configure ravenjs en el lado del cliente (lo hicimos en _document.

¿Puedes mostrarme cómo configuraste raven-js en tu _document.js ? No funciona para mí, cuando ocurre algún error, no pasa nada en el centinela.

¿Tengo que enviar todos los errores manualmente a la página _error.js al centinela?

// _document constructor
constructor(props) {
    super(props);
    Raven
      .config('...')
      .install();
}

Next.js todavía genera errores en console.error en el servidor siempre que no lo configure en silencio .

Con Sentry, puede habilitar autoBreadcrumbs para capturar esta salida y luego capturar su propio mensaje manualmente. El título será menos descriptivo, pero aún contendrá el seguimiento de la pila completa.

Implementación de ejemplo:

const express = require('express');
const nextjs = require('next');
const Raven = require('raven');

const dev = process.env.NODE_ENV !== 'production';

// Must configure Raven before doing anything else with it
if (!dev) {
  Raven.config('__DSN__', {
    autoBreadcrumbs: true,
    captureUnhandledRejections: true,
  }).install();
}

const app = nextjs({ dev });
const handle = app.getRequestHandler();

const captureMessage = (req, res) => () => {
  if (res.statusCode > 200) {
    Raven.captureMessage(`Next.js Server Side Error: ${res.statusCode}`, {
      req,
      res,
    });
  }
};

app
  .prepare()
  .then(() => {
    const server = express();

    if (!dev) {
      server.use((req, res, next) => {
        res.on('close', captureMessage(req, res));
        res.on('finish', captureMessage(req, res));
        next();
      });
    }

    [...]

    server.get('/', (req, res) => {
      return app.render(req, res, '/home', req.query)
    })

    server.get('*', (req, res) => {
      return handle(req, res)
    })

    server.listen('3000', (err) => {
      if (err) throw err
      console.log(`> Ready on http://localhost:${port}`)
    })
  })
  .catch(ex => {
    console.error(ex.stack);
    process.exit(1);
  });

Este es un ejemplo muy elaborado adaptado de nuestro código real. No lo he probado de esta forma. Avísame si se rompe.

Por supuesto, sería mejor si Next.js pudiera pasar esos errores a Express, para que podamos usar la integración Sentry / Express lista para usar.

@tusgavomelo Lo siento, por la respuesta tardía.

Tenemos actualización. En nuestra aplicación tenemos un archivo de ayuda con un método responsable de obtener una instancia de Raven teniendo en cuenta si estamos del lado del cliente o del servidor.

let clientInstance;
let serverInstance;

const getRavenInstance = (key, config) => {
  const clientSide = typeof window !== 'undefined';

  if (clientSide) {
    if (!clientInstance) {
      const Raven = require('raven-js');  // eslint-disable-line global-require
      Raven.config(key, config).install();
      clientInstance = Raven;
    }

    return clientInstance;
  }

  if (!serverInstance) {
    // NOTE: raven (for node) is not bundled by webpack (see rules in next.config.js).
    const RavenNode = require('raven'); // eslint-disable-line global-require
    RavenNode.config(key, config).install();
    serverInstance = RavenNode;
  }
  return serverInstance;
};

Este código se ejecuta tanto en el lado del servidor (cuando llega una solicitud) como en el lado del cliente. Lo que hemos hecho es configurar el paquete web ( next.config.js archivo) para evitar agrupar el paquete raven .

@acanimal , ¿puede proporcionar un ejemplo de trabajo? ¿Parece que no obtengo el seguimiento de pila completo? ¿O quizás puedes publicar tu _error.js ?

Estoy haciendo algo como RavenInstance.captureException(err) en mi _error.js , pero no puedo ver el tipo de errores que ocurrieron ni dónde.

export default class Error extends React.Component {
  static getInitialProps({ res, err }) {
    const RavenInstance = getRavenInstance('__SENTRY__')
    if (!(err instanceof Error)) {
      err = new Error(err && err.message)
    }
    RavenInstance.captureException(err)
    // const statusCode = res ? res.statusCode : err ? err.statusCode : null;

    return { }
  }

  render() {
    return (
      <div>
        <p>An error occurred on server</p>
      </div>
    )
  }
}

Este parece ser el lugar para solicitar soporte de registrador personalizado, ya que quiet debe configurarse en false para evitar que se borre la siguiente pantalla en la reconstrucción del módulo y, por lo tanto, se eliminen los errores de la vista, pero el único truco viable aquí requiere estableciendo quiet en falso.

Los detalles de los errores también están disponibles en la secuencia stderr.
process.stderr.write = error => yourErrorLog(error);

Tengo que amar las soluciones _unortodox_

function installErrorHandler(app) {
  const _renderErrorToHTML = app.renderErrorToHTML.bind(app)
  const errorHandler = rollbar.errorHandler()

  app.renderErrorToHTML = (err, req, res, pathname, query) => {
    if (err) {
      errorHandler(err, req, res, () => {})
    }

    return _renderErrorToHTML(err, req, res, pathname, query)
  }

  return app
}
// ¯\_(ツ)_/¯

Versión compacta de cómo obtener Raven correcto para el nodo y el navegador sin la configuración personalizada del paquete web. Inspirado en el comentario de @acanimal

// package.json
"browser": {
    "raven": "raven-js"
}

// getRaven.js
const Raven = require('raven')

if (process.env.NODE_ENV === 'production') {
  Raven.config('YOUR_SENTRY_DSN').install()
}

module.exports = Raven

Esencia

un resumen rápido para cualquiera que esté investigando este problema.

// pages/_error.js
import Raven from 'raven';
...
static async getInitialProps({ store, err, isServer }) {
   if (isServer && err) {
      // https://github.com/zeit/next.js/issues/1852
      // eslint-disable-next-line global-require
      const Raven = require('raven');

      Raven.captureException(err);
    }
    ...
// next.config.js
config.plugins.push(new webpack.IgnorePlugin(/^raven$/));

gracias al comentario de @acanimal .

Instale raven (ignore con el paquete web como se sugiere en los comentarios anteriores) y raven-js , luego cree un ayudante para instanciar Raven isomorfo, por ejemplo, lib/raven.js

import Raven from 'raven-js';

// https://gist.github.com/impressiver/5092952
const clientIgnores = {
  ignoreErrors: [
    'top.GLOBALS',
    'originalCreateNotification',
    'canvas.contentDocument',
    'MyApp_RemoveAllHighlights',
    'http://tt.epicplay.com',
    "Can't find variable: ZiteReader",
    'jigsaw is not defined',
    'ComboSearch is not defined',
    'http://loading.retry.widdit.com/',
    'atomicFindClose',
    'fb_xd_fragment',
    'bmi_SafeAddOnload',
    'EBCallBackMessageReceived',
    'conduitPage',
    'Script error.',
  ],
  ignoreUrls: [
    // Facebook flakiness
    /graph\.facebook\.com/i,
    // Facebook blocked
    /connect\.facebook\.net\/en_US\/all\.js/i,
    // Woopra flakiness
    /eatdifferent\.com\.woopra-ns\.com/i,
    /static\.woopra\.com\/js\/woopra\.js/i,
    // Chrome extensions
    /extensions\//i,
    /^chrome:\/\//i,
    // Other plugins
    /127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb
    /webappstoolbarba\.texthelp\.com\//i,
    /metrics\.itunes\.apple\.com\.edgesuite\.net\//i,
  ],
};

const options = {
  autoBreadcrumbs: true,
  captureUnhandledRejections: true,
};

let IsomorphicRaven = null;

if (process.browser === true) {
  IsomorphicRaven = Raven;
  IsomorphicRaven.config(SENTRY_PUBLIC_DSN, {
    ...clientIgnores,
    ...options,
  }).install();
} else {
  // https://arunoda.me/blog/ssr-and-server-only-modules
  IsomorphicRaven = eval("require('raven')");
  IsomorphicRaven.config(
    SENTRY_DSN,
    options,
  ).install();
}

export default IsomorphicRaven;

Luego puede usarlo en su pages/_error.js y funcionará tanto en el lado del servidor como en el del cliente.

import NextError from 'next/error';
import IsomorphicRaven from 'lib/raven';

class MyError extends NextError {
  static getInitialProps = async (context) => {
    if (context.err) {
      IsomorphicRaven.captureException(context.err);
    }
    const errorInitialProps = await NextError.getInitialProps(context);
    return errorInitialProps;
  };
}

export default MyError;

Aquí está mi PR para el plugin de wepback del mapa de origen de Rollbar https://github.com/thredup/rollbar-sourcemap-webpack-plugin/pull/56 con soporte de Next.js :)

@tusgavomelo , ¿puede explicarnos cómo hacer uso del error presente en la transmisión?
"process.stderr.write = error => yourErrorLog (error);"

¿Dónde deberíamos escribir esta línea de código para que registre el error en la consola del nodo?

Eso es solo del lado del cliente.
Rollbar.error('some error')

https://docs.rollbar.com/docs/javascript

@ teekey99 ¿Tiene una solución similar para @sentry/browser ? ¿Quizás actualizar con el ejemplo de centinela?

@sheerun He estado usando raven y raven-js hasta ahora. Soy consciente de que esos probablemente quedarán obsoletos ya que todas las funciones nuevas ahora se agregan a @sentry/node y @sentry/browser . El caso es que aún no he usado estas nuevas bibliotecas en mis proyectos, pero intentaré investigarlas. Si tengo un ejemplo funcional, volveré con él.

El ejemplo with-sentry se actualizó recientemente.

Veo @timneutkens , pero no admite errores del lado del servidor, ya que Sentry se inicializa dentro de la aplicación, donde es demasiado tarde para detectar errores del servidor. La solución adecuada probablemente usaría @sentry/node algún lugar

@sheerun Estoy @sentry/node no es trivial. El archivo Léame de este ejemplo de Sentry sugiere usar un servidor personalizado, que ya tenemos en la aplicación en la que estoy trabajando. Para capturar excepciones en nuestro servidor Express.js personalizado, debe insertar un error de middleware del controlador de errores Sentry como el primer middleware de manejo de errores . Si inserta el controlador de errores de Sentry inmediatamente después del controlador Siguiente, el error ya ha sido absorbido por ese punto y Sentry no lo ve.

He estado usando @sentry/browser en getInitialProps de _error.js y parece estar funcionando tanto en el lado del cliente como en el del servidor. No sé si se supone que @sentry/browser tiene soporte del lado del servidor, pero estoy enviando eventos a Sentry.

Aunque también estoy llamando a Sentry.init() través de @sentry/node en el archivo de entrada de un servidor Express personalizado, entonces quizás eso esté configurando algún estado global que el SSR está usando.

Aquí hay una idea general de la configuración que estoy usando: https://gist.github.com/mcdougal/7bf001417c3dc4b579da224b12776691

¡Interesante!

Definitivamente hay algún tipo de estado global sucediendo aquí (que da un poco de miedo, y probablemente frágil e indeseable). Aplicando sus cambios en _error.js :

  • __Sin__ configurar Sentry en el servidor personalizado:

    • Cuando se ejecuta en modo de desarrollo o producción (es decir, con mi aplicación construida), no se informa nada a Sentry cuando se produce un error del lado del servidor, pero obtengo un error ReferenceError: XMLHttpRequest is not defined en los registros del servidor

  • __Después de configurar__ Sentry en el servidor personalizado:

    • Se informan mis excepciones del lado del servidor, pero también obtengo un Error: Sentry syntheticException extraño registrado en Sentry

Sería bueno comprender cuál es la solución oficial. Los documentos de Next parecen recomendar el uso de un componente <App> ahora, y eso es lo que hace el ejemplo "con Sentry", pero esto solo funciona para el lado del cliente.

Los informes de errores con seguridad no sucederán hasta que se procese la aplicación por primera vez. Next puede fallar mucho antes, por ejemplo, al renderizar la página. Tal vez por casualidad funcione en algunos casos después de que la aplicación se haya procesado en el lado del servidor por primera vez, pero seguro que no es una solución completa.

El ejemplo with-sentry tampoco me funciona @timneutkens. Ejecutar el proyecto y probarlo con mi DSN real da una respuesta de 400 y nada llega a centinela (versión 7 de la API).

{"error":"Bad data reconstructing object (JSONDecodeError, Expecting value: line 1 column 1 (char 0))"}

El @mcdougal no me funcionó. Ya no era muy bueno tener una configuración global del lado del servidor que de alguna manera llegara al cliente, pero incluso con ese truco obtendría respuestas del centinela 301.

No veo cómo mi configuración autohospedada de centinela podría tener una configuración incorrecta, ya que no hay muchas opciones y se está ejecutando en varios proyectos.

Estoy usando @sentry/browser de la misma manera que se recomienda en el ejemplo with-sentry, y parece estar enviando errores tanto desde el servidor como desde el cliente a mi centinela. No tengo ninguna configuración especial, solo el mismo código que en el ejemplo.

@Jauny ¿Estás seguro de que se está enviando desde el servidor? ¿Cómo lo has probado? El archivo Léame del ejemplo incluso parece sugerir que no funcionará en el servidor.

@timrogers sí, de hecho, mi error, asumí que un error probado provenía del servidor, pero en realidad se activó desde el cliente :(

Parece que el ejemplo actual with-sentry no detecta errores arrojados en getInitialProps, solo en el árbol de renderizado de la aplicación. En mi caso, la mayoría de los errores son de getInitialProps.

@sheerun ¿Puedes probar la solución de mcdougal anterior? No es perfecto (extraños errores de centinela sintéticos) pero tengo la impresión de que recibe todos los errores y me gustaría saber si eso no es cierto. Si es cierto, el ejemplo with-sentry probablemente deba actualizarse con eso hasta que Next.js pueda asesorar sobre cómo hacerlo mejor (idealmente para que no se omita el controlador de errores centinela server.js).

Parece que funciona con dos problemas principales:

  1. Probablemente porque el código asincrónico del lado del servidor se compila innecesariamente en el código del regenerador, los seguimientos de pila del lado del servidor en producción a menudo se truncan en internal / process / next_tick.js y no se ve ninguna causa de error.
  2. El manejo de errores no funcionará si los errores se lanzan antes de que se llame al siguiente controlador de solicitud (por ejemplo, en el código de servidor personalizado)

En realidad, después de más pruebas, getInitialProps de custom Error, por alguna razón, ni siquiera se activa en producción cuando ocurre un error, por ejemplo, dentro de getInitialProps de custom _app.

Sí, definitivamente he tenido un comportamiento extraño después de ejecutar mi intento durante unos días. Parece que los principales problemas que enfrentamos son:

  1. Importando @sentry/browser para CSR y @sentry/node para SSR
  2. Encontrar un lugar para colocar el manejo de errores que siempre se activa tanto para CSR como para SSR

He estado pensando en intentar usar importaciones diferidas y estado global para resolver #1 , algo como

const sentryInitialized = false;

# Inside some trigger point
const Sentry = ssr ? eval(`require('@sentry/node')`) : eval(`require('@sentry/browser')`);
if (!sentryInitialized) {
  Sentry.init({dsn: SENTRY_DSN});
}
Sentry.captureException(err);

Quizás se puedan usar las importaciones dinámicas de Next, pero todavía no estoy tan familiarizado con ellas. Tampoco estoy seguro de las repercusiones de llamar a require cada vez que se activa el código de manejo de errores. Actualizaré si / cuando le dé una oportunidad a esto.

En cuanto al problema #2 , parece que las posibilidades son:

  1. _app.componentDidCatch , que no se activa para SSR
  2. _error.getInitialProps , que tiene una multitud de problemas
  3. ...¿algo más? ¿Hay algo que podamos hacer por SSR en la función del controlador del servidor Siguiente?

Mi intuición es que habrá una manera de hacer esto en el servidor inyectando el controlador de errores de Sentry antes que el de Next, pero aún no lo he probado.

Por el momento, Next no proporciona ningún gancho para ayudarlo a hacer eso. Si encontramos que esto funciona, podríamos agregarlos 👌

Sin embargo, existe el riesgo de que esto no funcione porque Next se detecta demasiado pronto.

@mcdougal Ah & #!% Estaba tan feliz cuando su solución podría haber sido lo suficientemente buena como para marcar la cobertura de cliente / servidor centinela requerida para ponernos en producción.

Disculpe mi completa ignorancia, pero ¿necesitamos lo siguiente para permitirnos deshabilitar su controlador de errores condicionalmente para que el controlador de errores centinela de nodejs se convierta en el primero? ¿Alguna bandera en next.config.js llamada "disableErrorHandler"?

@Enalmada No creo que quieras deshabilitar el controlador de errores Siguiente porque será el que genere una buena página de error. Solo desea insertar otro middleware antes. Creo que funcionará, pero tengo que intentarlo.

Incluso con eso arreglado, todavía no siento que el manejo de errores del lado del cliente funcione tan bien como espero :(

Todo este problema es una pena y realmente bloquea la ejecución segura de Next en producción.

Para su información, importo en todas partes @sentry/node y pongo lo siguiente en next.config.js:

        if (!isServer) {
          config.resolve.alias['@sentry/node$'] = '@sentry/browser'
        }

que puede ser mejor que eval de @mcdougal

Aquí están mis notas adicionales sobre el componente _app personalizado y el manejo de errores:

  1. _app.js se usa para TODAS las páginas, incluidas _error.js o páginas como 404, por lo que realmente desea asegurarse de que no se produzca ningún error cuando se le pase ctx.err .
  2. No olvide llamar a Component.getInitialProps en getInitialProps de la aplicación, ya que evitará que se llame a getInitialProps de _error.js (llámelo incluso si ctx.err está presente)
class MyApp extends App {
  static async getInitialProps (appContext) {
    const { Component, ctx } = appContext
    if (ctx.err) {
      if (Component.getInitialProps) {
        pageProps = await Component.getInitialProps(ctx)
      }
      return { error: true, pageProps }
    }
    // here code that can throw an error, and then:
    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx)
    }
    return { pageProps }
  }
  render() {
    if (this.props.error) return super.render()
    // rest of code that can throw an error
  }
}

Hasta ahora encuentro que configurar correctamente el informe de errores en next.js es un procedimiento muy frágil :(

gracias @sheerun que parece una buena parada en la dirección correcta. Estoy de acuerdo en que el manejo de errores en el siguiente no es óptimo en este momento, será genial ver que se agreguen algunos módulos extensibles / middleware para que podamos agregar el manejo de errores, etc.

La falta de bibliotecas isomórficas como sentry también complica las cosas, porque eso significa que no podemos importar simplemente ninguna de las bibliotecas en nuestros componentes, debemos hacerlo de forma dinámica en tiempo de ejecución para verificar siempre si el error se genera en el servidor o en el navegador.

¿Hay alguna actualización de este problema? Lo que intenté hasta ahora es lo siguiente: moví todo nuestro código de seguimiento a _app.js

constructor(args: any) {
        super(args)
        Sentry.init({
            dsn: 'blah',
            environment: 'local',
        })
        Sentry.configureScope(scope => {
            scope.setTag('errorOrigin', isServer ? 'SSR' : 'Client')
        })
    }
    static async getInitialProps({ Component, router, ctx }: any) {
        let pageProps = {}

        try {
            if (Component.getInitialProps) {
                pageProps = await Component.getInitialProps(ctx)
            }
        } catch (error) {
            // console.log('we caught an error')
            console.log(error)
            Sentry.captureException(error)
            throw error
        }

        return { pageProps }
    }

junto con la adición next.config.js de @sheerun e inicializando sentry en server.js también if (!isServer) { config.resolve.alias['@sentry/node$'] = '@sentry/browser' }
esto parece rastrear todos los errores en el lado del cliente, pero en el lado del servidor solo parece rastrear el primer error que ocurre después de reiniciar el servidor. Sin embargo, los errores posteriores en el servidor no se rastrean. Con este enfoque, no tengo ningún SyntheticErrors en el registro, sino solo errores reales.

Aún así, esto me parece bastante complicado y, dado que el seguimiento del lado del servidor solo funciona la primera vez, todavía no se puede usar.

También agregué esta parte del ejemplo with-Sentry

    componentDidCatch(error: any, errorInfo: any) {
        // if (process.env.FIAAS_NAMESPACE !== undefined) {
        Sentry.configureScope(scope => {
            Object.keys(errorInfo).forEach(key => {
                scope.setExtra(key, errorInfo[key])
            })
        })
        Sentry.captureException(error)
        console.log('componentDidCatch')

        // This is needed to render errors correctly in development / production
        super.componentDidCatch(error, errorInfo)
        // }
    }

pero no estoy totalmente seguro de si esto es necesario

En mi caso funciona sin problemas. Además, no deberías ser un centinela inicial en
_app.js constructor pero fuera de esta clase por completo

El miércoles 21 de noviembre de 2018 a las 2:53 p. M. Abraxxas [email protected] escribió:

¿Hay alguna actualización de este problema? Lo que intenté hasta ahora es lo siguiente:
movió todo nuestro código de seguimiento a _app.js

'
constructor (args: any) {
super (argumentos)
Sentry.init ({
dsn: 'bla',
entorno: 'local',
})
Sentry.configureScope (alcance => {
scope.setTag ('errorOrigin', isServer? 'SSR': 'Cliente')
})
}

static async getInitialProps ({Component, router, ctx}: any) {
dejar pageProps = {}

try {
    if (Component.getInitialProps) {
        pageProps = await Component.getInitialProps(ctx)
    }
} catch (error) {
    // console.log('we caught an error')
    console.log(error)
    Sentry.captureException(error)
    throw error
}

return { pageProps }

}

'

junto con la adición next.config.js de @sheerun
https://github.com/sheerun e inicializando sentry en server.js también si
(! isServer) {config.resolve.alias ['@ sentry / node $'] = '@ sentry / browser'}
esto parece rastrear todos los errores en el lado del cliente, pero en el lado del servidor
solo parece rastrear el primer error que ocurre después de un reinicio del
servidor. Sin embargo, los errores posteriores en el servidor no se rastrean. Con este
enfoque No tengo ningún SyntheticErrors en el registro, sino solo errores reales.

Aún así, esto me parece bastante hack y dado que el seguimiento del lado del servidor es
sólo funciona la primera vez que todavía no se puede utilizar.

-
Recibes esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/zeit/next.js/issues/1852#issuecomment-440668980 , o silenciar
la amenaza
https://github.com/notifications/unsubscribe-auth/AAR2DeIhoOj6PdWRA2VqiEZyrO5Jui8vks5uxVrHgaJpZM4NOQlp
.

Intenté moverlo ya y todavía el mismo comportamiento. @sheerun, ¿ podrías publicar un

Actualizaría el ejemplo oficial de centinela, pero me temo que será rechazado por ser "demasiado complejo". Sin embargo, puedo intentar publicar algo cuando tenga tiempo.

@sheerun Incluso un intento de actualizar el ejemplo oficial sería de gran valor. Siento que se fusionaría si realmente es la complejidad mínima necesaria para que Sentry SSR funcione sin SyntheticErrors o solo registre el primer error del servidor que ocurre. Luego, podemos ir desde allí para encontrar formas de mejorarlo o impulsar las mejoras principales de nextjs o el soporte isomórfico de centinela.

Entonces, ahora que tenemos un ejemplo de trabajo necesariamente complejo, ¿cuáles son los próximos pasos para mejorar la situación?

Lo único que se necesita en next.js es la capacidad de agregar middleware personalizado en next.config.js y algo como next.browser.js para la configuración de complementos universales (isomórficos) (next.config.js se usa, entre otros, para la configuración del paquete web, lo que significa que otras cosas definidas en este archivo no se pueden usar en el código de la aplicación, porque causaría una dependencia circular).

Para next.browser.js, next.js podría definir una configuración como decorador para componentes de aplicación o documento. De esta manera, podría implementar la integración centinela por completo como un complemento.

EDITAR: No sé si hay un boleto para esto, pero creo que @timneutkens ya está extrayendo el próximo servidor en paquetes separados. Creo que la mejor interfaz de complemento sería algo como:

module.exports = {
  server: server => {
    server.use(Sentry.Handlers.errorHandler())
  }
}

Implementé la propuesta de dicha API en el n. ° 6922

Permite agregar decoradores al código de servidor personalizado porque el contrato se cambia a uno que no llama automáticamente a .listen() por lo que permite que next.js lo decore más antes de crear una instancia

Estaba teniendo muchos problemas al usar el ejemplo with-sentry , así que abrí un PR para un ejemplo mucho más simple. No tiene todas las comodidades, pero me ha funcionado.

https://github.com/zeit/next.js/pull/7119

Pruebe esto en _document.js :

import React from "react";
import Document, {
  Html,
  Head,
  Main,
  NextScript,
} from "next/document";
import { NodeClient } from "@sentry/node";

const { default: getConfig } = require("next/config");
const { publicRuntimeConfig: { sentryDSN } } = getConfig();

let sentry = null;
if (sentryDSN) {
  sentry = new NodeClient({ dsn: sentryDSN });
}

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const initialProps = await Document.getInitialProps(ctx);
    if (ctx.err && sentry) sentry.captureException(ctx.err);
    return { ...initialProps };
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

Y esto en _app.js :

import * as Sentry from "@sentry/browser";

const { default: getConfig } = require("next/config");
const { publicRuntimeConfig: { sentryDSN } } = getConfig();

if (sentryDSN) {
  Sentry.init({ dsn: sentryDSN });
}

Me ayuda.

Tbh, estoy muy confundido sobre cuáles son los lugares correctos para detectar errores para next.js en este momento. Hay _app.tsx, _error.tsx y _document.tsx, hay algunos comentarios que dicen que algunas cosas deberían capturarse en _error.tsx (como: https://github.com/zeit/next.js/pull/5727/files # r235981700) pero los ejemplos actuales usan _app.tsx o _app y _document.

Intenté en ambos sentidos ahora y parece que algunos errores que ocurren durante SSR no me están detectando. ¿Qué lugares son ahora los correctos para comprobar si hay errores? El más lógico para mí parece verificar el componentDidCatch de _app y el getInitialProps del _error, ya que este se está sirviendo en casos de errores. Estoy un poco confundido acerca de toda la parte process.on en el documento _

¿Podría alguien con un conocimiento más profundo resolver esta pregunta?

@abraxxas process.on in _document.js está detectando errores que ocurren en el servidor. Como resumen,

  • _document.js es _sólo en el lado del servidor_ y se usa para cambiar el marcado inicial del documento renderizado en el lado del servidor.
  • _app.js es _sólo del lado del cliente_ y se usa para inicializar páginas.

Por lo tanto, cuando ocurre un error en el servidor, debería arrojar un error a Sentry a través de process.on y luego el cliente mostrará la página de error predeterminada o _error.js .

Con suerte, esto ayudará: https://leerob.io/blog/configuring-sentry-for-nextjs-apps/

_app.js no es solo del lado del cliente, sin embargo componentDidCatch es

@timneutkens Está bien, eso arruga un poco mi cerebro. Creo que los documentos se han actualizado desde la última vez que miré. ¿Podría explicar con más detalle cómo _app.js no es solo del lado del cliente?

@timneutkens, ¿ podrías explicar dónde y por qué detectarías errores? Parece haber tantas opiniones.

@timneutkens Está bien, eso arruga un poco mi cerebro. Creo que los documentos se han actualizado desde la última vez que miré. ¿Podría explicar con más detalle cómo _app.js no es solo del lado del cliente?

¡Hola leerob!
Acabo de publicar una pregunta sobre la solicitud de fusión de su ejemplo:
https://github.com/zeit/next.js/pull/7360#issuecomment -514318899

Agregando a esa pregunta ... también intenté lanzar un error en render () en el lado del servidor (comencé el estado con raiseErrorInRender: true, por lo que el primer render, que es del lado del servidor, ya arrojaría un error), y ese error tampoco fue capturado por Sentry.

¿Ha probado eso en su aplicación y realmente se están capturando esos errores del lado del servidor? Estoy usando Next 9.

Si ese es realmente el caso, y solo se capturan los errores del lado del cliente, tampoco habría razón para anular el archivo _document.js.

¡Gracias de antemano por cualquier ayuda!

@timneutkens Está bien, eso arruga un poco mi cerebro. Creo que los documentos se han actualizado desde la última vez que miré. ¿Podría explicar con más detalle cómo _app.js no es solo del lado del cliente?

Respondiendo a su pregunta, _app es donde se define un componente de la aplicación para inicializar páginas. Lo anularíamos en casos como la necesidad de un diseño persistente entre cambios de página (todas las páginas utilizan la misma _app), o para hacer uso de redux. Entonces, incluso el primer renderizado, que ocurre en el lado del servidor, renderizará el contenido de _app (no es solo del lado del cliente).

Agregando a esa pregunta ... también intenté lanzar un error en render () en el lado del servidor (comencé el estado con raiseErrorInRender: true, por lo que el primer render, que es del lado del servidor, ya arrojaría un error), y ese error tampoco fue capturado por Sentry.

¿Ha logrado de alguna otra manera que ese error aparezca en centinela? Intenté capturarlo en getInitialProps de _error pero eso tampoco muestra nada en sentry. Actualmente no he encontrado una forma confiable de capturar errores en el servidor, algunos de ellos (principalmente si están conectados a fallas de API) aparecen pero no he logrado obtener un error simple de la página de prueba de errores para que aparezcan en Sentry

Agregando a esa pregunta ... también intenté lanzar un error en render () en el lado del servidor (comencé el estado con raiseErrorInRender: true, por lo que el primer render, que es del lado del servidor, ya arrojaría un error), y ese error tampoco fue capturado por Sentry.

¿Ha logrado de alguna otra manera que ese error aparezca en centinela? Intenté capturarlo en getInitialProps de _error pero eso tampoco muestra nada en sentry. Actualmente no he encontrado una forma confiable de capturar errores en el servidor, algunos de ellos (principalmente si están conectados a fallas de API) aparecen pero no he logrado obtener un error simple de la página de prueba de errores para que aparezcan en Sentry

Lamentablemente no. La solución que creo que voy a implementar es usar el ejemplo (menos el archivo _document.js anulado) como mi plantilla para capturar errores que ocurren en el lado del cliente, ya que esos son los que no tengo control y forma de saber, a menos que los usuarios me informen. En el lado del servidor, creo que simplemente redirigiré cualquier línea de registro directamente a un archivo de registro. Definitivamente, esa no es la mejor solución, ya que quería tener todos los errores en el mismo lugar, pero al hacerlo de esa manera, al menos tendré información sobre cualquier error que pueda ocurrir en la aplicación.

¿Qué piensas de eso? ¡Gracias!

Agregando a esa pregunta ... también intenté lanzar un error en render () en el lado del servidor (comencé el estado con raiseErrorInRender: true, por lo que el primer render, que es del lado del servidor, ya arrojaría un error), y ese error tampoco fue capturado por Sentry.

¿Ha logrado de alguna otra manera que ese error aparezca en centinela? Intenté capturarlo en getInitialProps de _error pero eso tampoco muestra nada en sentry. Actualmente no he encontrado una forma confiable de capturar errores en el servidor, algunos de ellos (principalmente si están conectados a fallas de API) aparecen pero no he logrado obtener un error simple de la página de prueba de errores para que aparezcan en Sentry

Lamentablemente no. La solución que creo que voy a implementar es usar el ejemplo (menos el archivo _document.js anulado) como mi plantilla para capturar errores que ocurren en el lado del cliente, ya que esos son los que no tengo control y forma de saber, a menos que los usuarios me informen. En el lado del servidor, creo que simplemente redirigiré cualquier línea de registro directamente a un archivo de registro. Definitivamente, esa no es la mejor solución, ya que quería tener todos los errores en el mismo lugar, pero al hacerlo de esa manera, al menos tendré información sobre cualquier error que pueda ocurrir en la aplicación.

¿Qué piensas de eso? ¡Gracias!

Eso es exactamente lo que estamos haciendo en este momento, es un poco torpe, pero es lo mejor que pudimos encontrar. Pero dado que algunos errores del lado del servidor aparecen en sentry para nosotros, debe haber algo sucediendo con sentry o next.js, creo. Probé tanto con el ejemplo simple como con el más complejo y ambos se comportan de la misma manera, así que estoy al menos algo seguro de que este comportamiento no está relacionado con nuestra configuración.

Las personas en este hilo podrían estar interesadas en https://github.com/zeit/next.js/pull/8684 y errores relacionados. Tiene 12 pruebas diferentes de excepciones no controladas con las que puede jugar para comprender qué excepciones Next.js maneja por usted y qué no.

¿Alguna noticia sobre este tema?

Mi solución es usar redux guardar el error de recuperación de datos de nodo en state Luego, en componentDidMount de _app.js hacer algo (alerta para el usuario o post error req al backend).
El código está allí next-antd-scaffold_server-error .

========> estado

import {
  SERVER_ERROR,
  CLEAR_SERVER_ERROR
} from '../../constants/ActionTypes';

const initialState = {
  errorType: []
};

const serverError = (state = initialState, { type, payload }) => {
  switch (type) {
    case SERVER_ERROR: {
      const { errorType } = state;
      errorType.includes(payload) ? null : errorType.push(payload);
      return {
        ...state,
        errorType
      };
    }
    case CLEAR_SERVER_ERROR: {
      return initialState;
    }
    default:
      return state;
  }
};

export default serverError;

=======> acción

import {
  SERVER_ERROR
} from '../../constants/ActionTypes';

export default () => next => action => {
  if (!process.browser && action.type.includes('FAIL')) {
    next({
      type: SERVER_ERROR,
      payload: action.type 
    });
  }
  return next(action);
};

=======> _app.js

...
componentDidMount() {
    const { store: { getState, dispatch } } = this.props;
    const { errorType } = getState().serverError;
    if (errorType.length > 0) {
      Promise.all(
        errorType.map(type => message.error(`Node Error, Code:${type}`))
      );
      dispatch(clearServerError());
    }
  }
...

Tengo que amar las soluciones _unortodox_

function installErrorHandler(app) {
  const _renderErrorToHTML = app.renderErrorToHTML.bind(app)
  const errorHandler = rollbar.errorHandler()

  app.renderErrorToHTML = (err, req, res, pathname, query) => {
    if (err) {
      errorHandler(err, req, res, () => {})
    }

    return _renderErrorToHTML(err, req, res, pathname, query)
  }

  return app
}
// ¯\_(ツ)_/¯

Este método no se puede aplicar al error 404 porque el argumento err será nulo cuando se produzca un error 404.

Resolví esto para el cliente de nodo Elastic APM (https://www.npmjs.com/package/elastic-apm-node)

Para cualquier interesado:

En next.config.js :

webpack: (config, { isServer, webpack }) => {
      if (!isServer) {
        config.node = {
          dgram: 'empty',
          fs: 'empty',
          net: 'empty',
          tls: 'empty',
          child_process: 'empty',
        };

        // ignore apm (might use in nextjs code but dont want it in client bundles)
        config.plugins.push(
          new webpack.IgnorePlugin(/^(elastic-apm-node)$/),
        );
      }

      return config;
} 

Luego, en _error.js render func puede llamar captureError manualmente:

function Error({ statusCode, message, err }) {

const serverSide = typeof window === 'undefined';

  // only run this on server side as APM only works on server
  if (serverSide) {
    // my apm instance (imports elastic-apm-node and returns  captureError)
    const { captureError } = require('../src/apm'); 

    if (err) {
      captureError(err);
    } else {
      captureError(`Message: ${message}, Status Code: ${statusCode}`);
    }
  }

}

¡Oigan todos! Acabamos de lanzar una biblioteca NPM para la arquitectura de estilo Express en Next.js sin agregar un servidor Express. ¡Podría ser útil para los desafíos de manejo de errores de su servidor! Compruébalo si estás interesado. https://github.com/oslabs-beta/connext-js

¿Alguien tuvo éxito en la captura (y manejo) de las excepciones que ocurren en getServerSideProps ?

¿Alguien tuvo éxito en la captura (y manejo) de las excepciones que ocurren en getServerSideProps ?

No está claro, pensaría que un marco proporcionaría una forma idiomática de registrar errores tanto en el cliente como en el servidor 🤷‍♂️

¿Alguien tuvo éxito en la captura (y manejo) de las excepciones que ocurren en getServerSideProps ?

@stephankaag

sí, algo como esto funcionó para mí:

primero cree un middleware para manejar los informes de fallas. Envolví Sentry dentro de la clase CrashReporter porque simplemente devolver Sentry no funcionará (es decir, req.getCrashReporter = () => Sentry ).

// crash-reporter.js

const Sentry = require("@sentry/node");

class CrashReporter {
  constructor(){
    Sentry.init({ dsn: process.env.SENTRY_DSN });
  }
  captureException(ex){
    return Sentry.captureException(ex);
  }
}

function crashReporterMiddleware(req, res, next) {
  req.getCrashReporter = () => new CrashReporter();
  next();
}

module.exports = crashReporterMiddleware;

a continuación, por supuesto, cargue el middleware en su aplicación antes de que se configure el controlador de solicitudes Next.js:

// server.js

const crashReporterMiddleware = require("./middleware/crash-reporter")

...

app.use(crashReporterMiddleware);

...

setHandler((req, res) => {
  return handle(req, res);
});

luego, donde sea que llames getServerSideProps :

// _error.js

export async function getServerSideProps({req, res, err}) {
  const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
  const crashReporter = req.getCrashReporter();
  const eventId = crashReporter.captureException(err);
  req.session.eventId = eventId;
  return {
    props: { statusCode, eventId }
  }
}
¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

renatorib picture renatorib  ·  3Comentarios

rauchg picture rauchg  ·  3Comentarios

havefive picture havefive  ·  3Comentarios

swrdfish picture swrdfish  ·  3Comentarios

pie6k picture pie6k  ·  3Comentarios