Next.js: Comment détecter et gérer les erreurs pour signaler les journaux côté serveur

Créé le 2 mai 2017  ·  74Commentaires  ·  Source: vercel/next.js

Salut,
Je suis dans la situation où nous voulons envoyer des erreurs, à la fois côté serveur et côté client, à l'outil Sentry.

Notre application utilise Express comme serveur personnalisé. Fondamentalement, nous créons une application express, appliquons quelques middlewares mais déléguons tout le vrai travail au handle 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());

Avec cette approche, next.js prend le contrôle du processus de rendu et toute erreur est détectée par next.js et la seule façon dont je dois la traiter est de remplacer le fichier de page _error.js .

Dans ce fichier _error.js , j'ai besoin d'un moyen universel de signaler les erreurs à Sentry. Actuellement, il existe deux bibliothèques ( raven pour node et raven-js pour javascript). Le problème est que je ne peux pas les importer tous les deux car raven fonctionne pour SSR mais échoue lorsque webpack construit le bundle, et raven-js échoue également en raison de la dépendance XMLHTTPRequest.

Existe-t-il un moyen d'être averti de l'erreur next.js côté serveur ?

story feature request

Commentaire le plus utile

Je dois aimer les solutions _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
}
// ¯\_(ツ)_/¯

Tous les 74 commentaires

Pour la journalisation des erreurs côté client, nous avons procédé comme suit :
https://gist.github.com/jgautheron/044b88307d934d486f59ae87c5a5a5a0

Il envoie essentiellement des erreurs au serveur, qui sont finalement imprimées à stdout et détectées par notre pilote de journalisation Docker .
Nous avons réussi à détecter les erreurs SSR avec react-guard sans qu'il soit nécessaire de remplacer les fichiers Next de base.

J'ai aussi ce problème. Je me demande : est-ce que le simple fait de renvoyer une promesse rejetée du handleRequest prochain suffirait ? C'est-à-dire en changeant le code ici pour qu'il soit :

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

Puis, en code utilisateur :

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 Pensez-vous que le changement que j'ai proposé immédiatement ci-dessus fonctionnerait ? Si oui, heureux de soumettre un PR

Acceptez de renvoyer une erreur afin que nous puissions jouer avec.
Il faut aussi relancer à renderToHTML aussi...

Je suis également dans la situation où nous voulons envoyer des erreurs, à la fois côté serveur et côté client, à un service similaire à Sentry.

Je pense que la caractéristique la plus précieuse de ces services/outils est de signaler les problèmes les plus inattendus, qui, selon mon expérience, sont des erreurs non détectées dans la nature (c'est-à-dire côté client). Malheureusement, comme souligné précédemment dans le problème connexe n° 2334, les gestionnaires côté client de Next.js gardent ces erreurs pour eux-mêmes, sans aucun moyen possible de les transmettre à Sentry d'un tel autre outil.

Ce qui nous a particulièrement marqués, c'est ceci : une page correctement rendue côté serveur est restituée en tant que page d'erreur si une exception non interceptée se produit avant le rendu React côté client . Cela peut être considéré comme une fonctionnalité intéressante, mais aussi comme une expérience de développeur frustrante, car cela ruine essentiellement les avantages de servir un document déjà rendu, sur une partie étonnamment de clients.

Voici un exemple de page qui illustre le problème :

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

Le code ci-dessus peut être parfaitement rendu côté serveur et livré au client, pour devenir un Flash Of Unwanted Content avant que toute la page ne soit remplacée côté client par le redoutable message « Une erreur inattendue s'est produite » sur la plupart des navigateurs. , sans aucun moyen possible de signaler l'erreur à Sentry (ou à tout autre service/outil).

Cette " absorption d' erreurs" empêche également tout effet de levier du gestionnaire d' onunhandledrejection , qui peut être plus approprié compte tenu de la nature asynchrone du côté client code).

Solution possible côté client

Pour autant que je sache, ce type d'erreur côté client pré-React est avalé dans le bloc try / catch dans le client/index.js de Next.js où le Component sur le point d'être rendu est réaffecté à la valeur de ErrorComponent (actuellement lignes 67-72 ).

Chers auteurs et mainteneurs de Next.js, dans un souci de contrôle de ce qui est rendu, que pensez-vous serait acceptable/possible parmi les idées suivantes :

  • introduire un crochet dans ce bloc catch dans client/index.js pour gérer ce type d'erreur ?
  • transmettre l'erreur à un gestionnaire onerror/onunhandledrejection, le cas échéant ?
  • fournir une option pour renvoyer l'erreur ?
  • fournir une option pour ne pas afficher le composant d'erreur ?
  • fournir une option pour afficher un composant d'erreur personnalisé ?

introduire un crochet dans ce bloc catch dans client/index.js pour gérer ce type d'erreur ?
transmettre l'erreur à un gestionnaire onerror/onunhandledrejection, le cas échéant ?

C'est quelque chose dont nous avons parlé en interne. Et nous aborderons bientôt.

J'utilise Sentry.io pour signaler des erreurs et la solution que nous appliquons est :

1- Configurer raven-node côté serveur
2- Configurer ravenjs côté client (nous l'avons fait sur _document .

Avec ces deux étapes, nous interceptons toutes les exceptions non gérées à la fois sur le client et le serveur.

3- Créez une page _error . Toute erreur produite une fois que nextjs a traité la demande (que ce soit côté client ou côté serveur) cette page est rendue. Dans la méthode getInitialProps de la page _error, nous rapportons l'erreur à sentinelle.

La façon de décider comment charger si raven-node ou ravenjs est résolu en important dynamiquement selon si nous sommes dans le client const Raven = require('raven-js'); ou côté serveur const Raven = require('raven'); .
Notez que nous avons configuré webpack pour ne pas regrouper le module raven (le côté serveur) mettant next.config.js jour

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- Configurer ravenjs côté client (nous l'avons fait sur _document.

Pouvez-vous me montrer comment avez-vous configuré raven-js sur votre _document.js ? Cela ne fonctionne pas pour moi, lorsqu'une erreur se produit, rien ne se passe sur sentinelle.

Dois-je envoyer manuellement toutes les erreurs à la page _error.js à sentinelle ?

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

Next.js renvoie toujours des erreurs à console.error sur le serveur tant que vous ne le définissez pas sur quiet .

Avec Sentry, vous pouvez activer autoBreadcrumbs pour capturer cette sortie , puis capturer votre propre message manuellement. Le titre sera moins descriptif, mais il contiendra toujours la trace de la pile complète.

Exemple de mise en œuvre :

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

Ceci est un exemple très artificiel adapté de notre code actuel. Je ne l'ai pas testé sous cette forme. Faites-moi savoir s'il casse.

Bien sûr, il serait toujours préférable que Next.js puisse transmettre ces erreurs à Express, afin que nous puissions utiliser l'intégration Sentry/Express prête à l'emploi.

@tusgavomelo Désolé, pour la réponse tardive.

Nous avons la mise à jour. Dans notre application, nous avons un fichier d'aide avec une méthode chargée d'obtenir une instance Raven en tenant compte du fait que nous sommes côté client ou côté serveur.

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

Ce code s'exécute à la fois côté serveur (lorsqu'une demande arrive) et côté client. Ce que nous avons fait est de configurer webpack (fichier next.config.js ) pour éviter de regrouper le package raven .

@acanimal pouvez-vous peut-être fournir un exemple de travail ? Il semble que je n'obtienne pas la trace complète de la pile ? ou peut-être pouvez-vous poster votre _error.js ?

Je fais quelque chose comme RavenInstance.captureException(err) dans mon _error.js , mais je ne vois pas le type d'erreurs qui se sont produites ainsi que où ?

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

Cela semble être l'endroit pour demander une prise en charge personnalisée des enregistreurs puisque quiet doit être défini sur false pour empêcher l'écran d'effacement suivant lors de la reconstruction du module et ainsi de purger les erreurs de la vue, mais le seul hack réalisable ici nécessite en définissant quiet sur faux.

Les détails des erreurs sont également disponibles sur le flux stderr.
process.stderr.write = error => yourErrorLog(error);

Je dois aimer les solutions _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
}
// ¯\_(ツ)_/¯

Version compacte de la façon d'obtenir le Raven correct pour le nœud et le navigateur sans configuration Webpack personnalisée. Inspiré du commentaire 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

Essentiel

un récapitulatif rapide pour tous ceux qui enquêtent sur ce problème.

// 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$/));

merci au commentaire de

Installez à la fois raven (ignorez avec webpack comme suggéré dans les commentaires précédents) et raven-js , puis créez un assistant pour instancier Raven isomorphe, par exemple 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;

Ensuite, vous pouvez l'utiliser dans votre pages/_error.js et cela fonctionnera à la fois côté serveur et côté client.

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;

Voici mon PR pour le plugin Rollbar sourcemap wepback https://github.com/thredup/rollbar-sourcemap-webpack-plugin/pull/56 avec le support Next.js :)

@tusgavomelo , Pouvez-vous expliquer comment utiliser l'erreur présente sur le flux ?
"process.stderr.write = error => yourErrorLog(error);"

Où devons-nous écrire cette ligne de code, pour qu'elle enregistre l'erreur dans la console Node ?

C'est seulement côté client.
Rollbar.error('some error')

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

@ teekey99 Avez-vous une solution similaire pour @sentry/browser ? Peut-être mettre à jour l'exemple avec-sentry ?

@sheerun J'ai utilisé raven et raven-js jusqu'à présent. Je suis conscient que ceux-ci deviendront probablement obsolètes car toutes les nouvelles fonctionnalités sont désormais ajoutées à @sentry/node et @sentry/browser . Le fait est que je n'ai pas encore utilisé ces nouvelles bibliothèques sur mes projets, mais je vais essayer d'y réfléchir. Si j'ai un exemple fonctionnel, je le reprendrai.

L'exemple with-sentry a été récemment mis à jour.

@timneutkens Je vois mais il ne prend pas en charge les erreurs côté serveur car Sentry est initialisé dans l'application où il est trop tard pour détecter les erreurs de serveur. Une bonne solution utiliserait probablement @sentry/node quelque part

@sheerun, je rencontre le même problème. Utiliser @sentry/node n'est pas anodin. Le fichier readme de cet exemple Sentry suggère d'utiliser un serveur personnalisé, que nous avons déjà dans l'application sur laquelle je travaille. Pour capturer les exceptions dans notre serveur Express.js personnalisé, vous devez insérer une erreur middleware du gestionnaire d'erreurs Sentry comme premier middleware de gestion des erreurs . Si vous insérez le gestionnaire d'erreurs de Sentry juste après le gestionnaire Next, l'erreur a déjà été avalée à ce stade et Sentry ne la voit pas.

J'ai utilisé @sentry/browser dans le getInitialProps de _error.js et cela semble fonctionner à la fois côté client et côté serveur. Je ne sais pas si @sentry/browser est censé avoir un support côté serveur, mais je reçois des événements pour Sentry.

Bien que j'appelle également Sentry.init() via @sentry/node dans le fichier d'entrée d'un serveur Express personnalisé, cela définit donc peut-être un état global utilisé par le SSR.

Voici un aperçu de la configuration que j'utilise : https://gist.github.com/mcdougal/7bf001417c3dc4b579da224b12776691

Intéressant!

Il y a certainement une sorte d'état global en cours ici (ce qui est un peu effrayant, et probablement fragile et indésirable). Appliquer vos modifications dans _error.js :

  • __Sans__ configurer Sentry dans le serveur personnalisé :

    • Lors de l'exécution en mode développement ou en production (c'est-à-dire avec mon application créée), rien n'est signalé à Sentry lorsqu'une erreur côté serveur se produit, mais j'obtiens une erreur ReferenceError: XMLHttpRequest is not defined dans les journaux du serveur

  • __Après avoir configuré__ Sentry dans le serveur personnalisé :

    • Mes exceptions côté serveur sont signalées, mais je reçois également un étrange Error: Sentry syntheticException enregistré dans Sentry

Il serait bon de comprendre quelle est la solution officielle. La documentation de Next semble recommander d'utiliser un composant <App> maintenant, et c'est ce que fait l'exemple "with Sentry", mais cela ne fonctionne que pour le côté client.

Les rapports d'erreurs ne se produiront certainement pas tant que l'application ne sera pas rendue pour la première fois. Next peut échouer bien avant, par exemple lors du rendu de Page. Peut-être que par hasard, cela fonctionne dans certains cas après que l'application a été rendue côté serveur pour la première fois, mais ce n'est certainement pas une solution complète.

L'exemple with-sentry ne fonctionne pas pour moi non plus @timneutkens. Exécuter le projet et le tester avec mon DSN réel donne une réponse de 400 et rien ne parvient à sentinelle (version 7 de l'api).

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

Le @mcdougal n'a pas fonctionné pour moi. Ce n'était déjà pas très bien d'avoir une configuration globale côté serveur qui remonte d'une manière ou d'une autre jusqu'au client, mais même avec ce hack, j'obtiendrais des réponses de sentinelle 301.

Je ne vois pas comment ma configuration auto-hébergée Sentry pourrait avoir une mauvaise configuration, car il n'y a pas beaucoup d'options et son fonctionnement sur plusieurs projets.

J'utilise @sentry/browser de la même manière que cela est recommandé dans l'exemple with-sentry, et il semble envoyer des erreurs à la fois du serveur et du client à ma sentinelle. Je n'ai pas de configuration spéciale, seulement le même code que l'exemple.

@Jauny Êtes-vous sûr qu'il envoie depuis le serveur ? Comment as-tu testé ça ? Le fichier readme de l'exemple semble même suggérer que cela ne fonctionnera pas sur le serveur.

@timrogers oui en effet mon mauvais, j'ai supposé qu'une erreur testée provenait du serveur mais a en fait été déclenchée par le client :(

Il semble que l'exemple actuel avec-sentry n'attrape pas les erreurs générées dans getInitialProps, seulement celles dans l'arbre de rendu de l'application. Dans mon cas, la plupart des erreurs proviennent de getInitialProps.

@sheerun Pouvez-vous essayer la solution de contournement mcdougal ci-dessus ? Ce n'est pas parfait (étranges erreurs de sentinelle synthétique) mais j'ai l'impression qu'il y a toutes les erreurs et j'aimerais savoir si ce n'est pas vrai. Si c'est vrai, l'exemple with-sentry doit probablement être mis à jour avec cela jusqu'à ce que Next.js puisse vous conseiller sur la meilleure façon de le faire (idéalement en faisant en sorte que le gestionnaire d'erreurs de sentinelle server.js ne soit pas ignoré ?).

Il semble que cela fonctionne avec deux problèmes majeurs :

  1. Probablement parce que le code côté serveur asynchrone est inutilement compilé dans le code du régénérateur, les traces de pile côté serveur en production sont souvent tronquées sur internal/process/next_tick.js et aucune cause d'erreur n'est visible du tout.
  2. La gestion des erreurs ne fonctionnera pas si des erreurs sont générées avant l'appel du prochain gestionnaire de requêtes (par exemple, dans le code de serveur personnalisé)

En fait, après avoir testé davantage getInitialProps de l'erreur personnalisée pour une raison quelconque, il ne se déclenche même pas en production lorsqu'une erreur se produit, par exemple dans le getInitialProps de _app personnalisé.

Oui, j'ai définitivement eu un comportement étrange après avoir couru avec ma tentative pendant quelques jours. Il semble que les principaux problèmes auxquels nous sommes confrontés sont les suivants :

  1. Importation de @sentry/browser pour CSR et @sentry/node pour SSR
  2. Trouver un endroit pour mettre la gestion des erreurs qui est toujours déclenchée à la fois pour le CSR et le SSR

J'ai pensé à essayer d'utiliser les importations paresseuses et l'état global pour résoudre #1 , quelque chose comme

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

Peut-être que les importations dynamiques de Next pourraient être utilisées, mais je ne les connais pas encore très bien. Je ne suis pas non plus sûr des répercussions de l'appel de require chaque fois que le code de gestion des erreurs est déclenché. Je mettrai à jour si/quand j'essaierai.

En ce qui concerne le problème #2 , il semble que les possibilités soient :

  1. _app.componentDidCatch , qui ne se déclenche pas pour le SSR
  2. _error.getInitialProps , qui a une multitude de problèmes
  3. ...autre chose? Y a-t-il quelque chose que nous puissions faire pour le SSR dans la fonction de gestionnaire de serveur suivant ?

Mon intuition est qu'il y aura un moyen de le faire sur le serveur en injectant le gestionnaire d'erreurs de Sentry avant celui de Next, mais je ne l'ai pas encore essayé.

Pour le moment, Next ne fournit aucun crochet pour vous aider à le faire. Si nous trouvons que cela fonctionne, nous pourrions les ajouter

Il y a un risque que cela ne fonctionne pas car Next attrape trop tôt, cependant.

@mcdougal Ah &#!% J'étais si heureux lorsque votre solution de contournement aurait pu être assez bonne pour vérifier la couverture client/serveur sentinelle requise pour nous mettre en production.

Pardonnez mon ignorance totale, mais avons-nous juste besoin de nous autoriser ensuite à désactiver son gestionnaire d'erreurs de manière conditionnelle afin que le gestionnaire d' erreurs de la sentinelle nodejs devienne le premier? Un indicateur dans next.config.js appelé « disableErrorHandler » ?

@Enalmada Je ne pense pas que vous voudriez désactiver le gestionnaire d'erreurs suivant car ce sera celui qui rendra une belle page d'erreur. Vous voulez juste insérer d'autres middleware avant cela. Je pense que cela fonctionnera, mais je dois l'essayer.

Même avec cela corrigé, je n'ai toujours pas l'impression que la gestion des erreurs côté client fonctionne aussi bien que je l'espère :(

Tout ce problème est une honte et c'est vraiment un obstacle à l'exécution sûre de Next en production.

Pour info, j'importe partout @sentry/node et je mets ce qui suit dans next.config.js :

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

qui peut être meilleur que eval de @mcdougal

Voici mes notes supplémentaires sur le composant _app personnalisé et la gestion des erreurs :

  1. _app.js est utilisé pour TOUTES les pages, y compris _error.js ou des pages comme 404, donc vous voulez vraiment être sûr qu'aucune erreur n'est renvoyée lorsque ctx.err est transmis.
  2. N'oubliez pas d'appeler Component.getInitialProps dans getInitialProps de l'application car cela empêchera getInitialProps de _error.js d'être appelé (appelez-le même si ctx.err est présent)
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
  }
}

Jusqu'à présent, je trouve que la configuration correcte du rapport d'erreurs dans next.js est une procédure très fragile :(

merci @sheerun qui ressemble à un bon arrêt dans la bonne direction. Je suis d'accord pour dire que la gestion des erreurs dans Next n'est pas optimale pour le moment, ce sera formidable de voir un module/intergiciel extensible ajouté afin que nous puissions ajouter la gestion des erreurs dessus, etc.

Le manque de bibliothèques isomorphes telles que sentinelle rend également les choses compliquées, car cela signifie que nous ne pouvons pas importer simplement l'une ou l'autre bibliothèque dans nos composants, nous devons le faire dynamiquement au moment de l'exécution pour toujours vérifier si l'erreur est déclenchée côté serveur ou navigateur.

Y a-t-il une mise à jour de ce problème ? Ce que j'ai essayé jusqu'à présent est le suivant : j'ai déplacé tout notre code de suivi dans le _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 }
    }

couplé avec l'ajout next.config.js de @sheerun et l'initialisation de sentinelle dans le server.js aussi if (!isServer) { config.resolve.alias['@sentry/node$'] = '@sentry/browser' }
cela semble suivre toutes les erreurs côté client, mais côté serveur, il ne semble suivre que la première erreur qui se produit après un redémarrage du serveur. Les erreurs ultérieures sur le serveur ne sont cependant pas suivies. Avec cette approche, je n'ai pas d'erreurs synthétiques dans le journal, mais seulement de vraies erreurs.

Cela me semble quand même assez compliqué et comme le suivi côté serveur ne fonctionne que la première fois, il n'est toujours pas utilisable.

J'ai également ajouté cette partie de l'exemple 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)
        // }
    }

mais je ne suis pas tout à fait sûr que cela soit nécessaire

Dans mon cas avec fonctionne sans problèmes. aussi vous ne devriez pas init sentinelle dans
constructeur _app.js mais entièrement en dehors de cette classe

Le mercredi 21 novembre 2018 à 14 h 53, abraxxas [email protected] a écrit :

Y a-t-il une mise à jour de ce problème ? Ce que j'ai essayé jusqu'à présent est le suivant: je
déplacé tout notre code de suivi dans le _app.js

`
constructeur (arguments : tout) {
super(arguments)
Sentinelle.init({
dsn: 'blabla',
environnement : 'local',
})
Sentry.configureScope(portée => {
scope.setTag('errorOrigin', isServer ? 'SSR' : 'Client')
})
}

static async getInitialProps({ Composant, routeur, ctx } : tout) {
laissez 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 }

}

`

couplé avec l'ajout next.config.js de @sheerun
https://github.com/sheerun et initialiser la sentinelle dans le server.js aussi si
(!isServer) { config.resolve.alias['@sentry/node$'] = '@sentry/browser' }
cela semble suivre toutes les erreurs côté client, mais côté serveur
il ne semble suivre que la première erreur qui se produit après un redémarrage du
serveur. Les erreurs ultérieures sur le serveur ne sont cependant pas suivies. Avec ça
approche, je n'ai pas d'erreurs synthétiques dans le journal, mais seulement de vraies erreurs.

Pourtant, cela me semble assez difficile et puisque le suivi côté serveur est
ne fonctionnant que la première fois, il est toujours inutilisable.

-
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/zeit/next.js/issues/1852#issuecomment-440668980 , ou couper le son
le fil
https://github.com/notifications/unsubscribe-auth/AAR2DeIhoOj6PdWRA2VqiEZyrO5Jui8vks5uxVrHgaJpZM4NOQlp
.

J'ai déjà essayé de le déplacer et toujours le même comportement. @sheerun pourriez-vous peut-être publier un

Je mettrais à jour l'exemple officiel de sentinelle mais j'ai peur qu'il soit rejeté comme "trop ​​complexe", je peux essayer de poster quelque chose quand je trouverai le temps..

@sheerun Même une tentative de mise à jour vers l'exemple officiel serait d'une grande valeur. Je pense qu'il serait fusionné s'il s'agissait vraiment de la complexité minimale nécessaire pour que Sentry ssr fonctionne sans SyntheticErrors ou n'enregistre que la première erreur de serveur qui se produit. Ensuite, nous pouvons partir de là pour trouver des moyens de l'améliorer ou de pousser les améliorations de base de nextjs ou le support isomorphique de sentinelle.

Alors maintenant que nous avons un exemple de travail nécessairement complexe, quelles sont les prochaines étapes pour améliorer la situation :

La seule chose dont on a besoin dans next.js est la possibilité d'ajouter un middleware personnalisé dans next.config.js et quelque chose comme next.browser.js pour la configuration du plugin universel (isomorphe) (next.config.js est utilisé entre autres pour la configuration du webpack, ce qui signifie que d'autres éléments définis dans ce fichier ne peuvent pas être utilisés dans le code de l'application, car cela entraînerait une dépendance circulaire).

Pour next.browser.js, next.js pourrait définir une configuration comme un décorateur pour les composants App ou Document. De cette façon, j'ai pu implémenter l'intégration de sentinelle entièrement en tant que plugin.

EDIT: Je ne sais pas s'il existe un ticket pour cela, mais je pense que @timneutkens extrait déjà le prochain serveur dans des packages séparés. Je pense que la meilleure interface de plugin serait quelque chose comme :

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

J'ai implémenté la proposition d'une telle API dans #6922

Il permet d'ajouter des décorateurs au code de serveur personnalisé car le contrat est remplacé par un contrat qui n'appelle pas automatiquement .listen() ce qui permet à next.js de le décorer davantage avant l'instanciation

J'avais beaucoup de mal à utiliser l'exemple with-sentry , alors j'ai ouvert un PR pour un exemple beaucoup plus simple. Il n'a pas toutes les cloches et les sifflets, mais ça a fonctionné pour moi.

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

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

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

Ça m'aide.

Tbh, je suis très confus quant aux bons endroits pour détecter les erreurs pour next.js en ce moment. Il y a _app.tsx, _error.tsx et _document.tsx il y a des commentaires disant que certaines choses devraient être attrapées dans _error.tsx (comme : https://github.com/zeit/next.js/pull/5727/files #r235981700) mais les exemples actuels utilisent soit _app.tsx soit _app et _document.

J'ai essayé les deux manières maintenant et il semble que certaines erreurs qui se produisent pendant ssr ne soient pas détectées pour moi. Quels endroits sont maintenant les bons pour vérifier les erreurs ? Le plus logique pour moi semble vérifier dans le componentDidCatch de _app et le getInitialProps du _error car celui-ci est servi en cas d'erreurs. Je suis un peu confus à propos de toute la partie process.on dans le _document

Quelqu'un avec des connaissances plus approfondies pourrait-il résoudre cette question?

@abraxxas process.on dans _document.js détecte les erreurs qui se produisent sur le serveur. En guise de récapitulatif,

  • _document.js est _côté serveur uniquement_ et est utilisé pour modifier le balisage initial du document rendu côté serveur.
  • _app.js est _côté client uniquement_ et est utilisé pour initialiser les pages.

Par conséquent, lorsqu'une erreur se produit sur le serveur, elle doit envoyer une erreur à Sentry via process.on , puis le client affichera la page d'erreur par défaut ou _error.js .

Espérons que cela vous aidera : https://leerob.io/blog/configuring-sentry-for-nextjs-apps/

_app.js n'est pas uniquement côté client, mais componentDidCatch est

@timneutkens D'accord, ça me été mise à jour depuis la dernière fois que j'ai regardé. Pourriez-vous expliquer plus en détail comment _app.js n'est pas côté client ?

@timneutkens pourriez-vous peut-être expliquer où et pourquoi vous attraperiez des erreurs ? Il semble y avoir tellement d'opinions.

@timneutkens D'accord, ça me été mise à jour depuis la dernière fois que j'ai regardé. Pourriez-vous expliquer plus en détail comment _app.js n'est pas côté client ?

Salut leerob !
Je viens de poster une question sur la demande de fusion de votre exemple :
https://github.com/zeit/next.js/pull/7360#issuecomment -514318899

Ajout à cette question... J'ai également essayé de lancer une erreur dans render() côté serveur (j'ai commencé l'état avec raiseErrorInRender: true, donc le premier rendu, c'est-à-dire côté serveur, générerait déjà une erreur), et cette erreur n'a pas non plus été capturée par Sentry.

Avez-vous testé cela dans votre application et ces erreurs côté serveur sont-elles vraiment capturées ? J'utilise Next 9.

Si tel est vraiment le cas et que seules les erreurs côté client sont capturées, il n'y aurait également aucune raison de remplacer le fichier _document.js.

Merci d'avance pour votre aide!

@timneutkens D'accord, ça me été mise à jour depuis la dernière fois que j'ai regardé. Pourriez-vous expliquer plus en détail comment _app.js n'est pas côté client ?

Pour répondre à votre question, _app est l'endroit où un composant App est défini pour initialiser les pages. Nous le remplacerions dans des cas tels que le besoin d'une mise en page persistante entre les changements de page (toutes les pages utilisent le même _app), ou pour utiliser redux. Ainsi, même le premier rendu, qui se produit côté serveur, rendra le contenu de _app (ce n'est pas uniquement côté client).

Ajout à cette question... J'ai également essayé de lancer une erreur dans render() côté serveur (j'ai commencé l'état avec raiseErrorInRender: true, donc le premier rendu, c'est-à-dire côté serveur, générerait déjà une erreur), et cette erreur n'a pas non plus été capturée par Sentry.

Avez-vous réussi d'une autre manière à faire apparaître cette erreur en sentinelle ? J'ai essayé de le capturer dans getInitialProps de _error mais cela ne montre rien non plus dans sentinelle. Actuellement, je n'ai pas trouvé de moyen fiable de capturer les erreurs sur le serveur, certaines d'entre elles (surtout si elles sont connectées à des échecs d'API) elles s'affichent mais je n'ai pas réussi à obtenir une simple erreur de la page errortest pour apparaître dans sentinelle

Ajout à cette question... J'ai également essayé de lancer une erreur dans render() côté serveur (j'ai commencé l'état avec raiseErrorInRender: true, donc le premier rendu, c'est-à-dire côté serveur, générerait déjà une erreur), et cette erreur n'a pas non plus été capturée par Sentry.

Avez-vous réussi d'une autre manière à faire apparaître cette erreur en sentinelle ? J'ai essayé de le capturer dans getInitialProps de _error mais cela ne montre rien non plus dans sentinelle. Actuellement, je n'ai pas trouvé de moyen fiable de capturer les erreurs sur le serveur, certaines d'entre elles (surtout si elles sont connectées à des échecs d'API) elles s'affichent mais je n'ai pas réussi à obtenir une simple erreur de la page errortest pour apparaître dans sentinelle

Malheureusement non. La solution que je pense que je vais mettre en œuvre consiste à utiliser l'exemple (moins le fichier _document.js remplacé) comme modèle pour capturer les erreurs qui se produisent du côté client, car ce sont celles-là que je n'ai aucun contrôle ni moyen de savoir, à moins que les utilisateurs ne me le signalent. Côté serveur, je pense que je vais simplement rediriger n'importe quelle ligne de journal directement vers un fichier journal. Ce n'est certainement pas la meilleure solution car je voulais avoir toutes les erreurs au même endroit, mais en procédant ainsi, j'aurai au moins des informations sur toute erreur pouvant survenir sur l'application.

Que penses-tu de cela? Merci!

Ajout à cette question... J'ai également essayé de lancer une erreur dans render() côté serveur (j'ai commencé l'état avec raiseErrorInRender: true, donc le premier rendu, c'est-à-dire côté serveur, générerait déjà une erreur), et cette erreur n'a pas non plus été capturée par Sentry.

Avez-vous réussi d'une autre manière à faire apparaître cette erreur en sentinelle ? J'ai essayé de le capturer dans getInitialProps de _error mais cela ne montre rien non plus dans sentinelle. Actuellement, je n'ai pas trouvé de moyen fiable de capturer les erreurs sur le serveur, certaines d'entre elles (surtout si elles sont connectées à des échecs d'API) elles s'affichent mais je n'ai pas réussi à obtenir une simple erreur de la page errortest pour apparaître dans sentinelle

Malheureusement non. La solution que je pense que je vais mettre en œuvre consiste à utiliser l'exemple (moins le fichier _document.js remplacé) comme modèle pour capturer les erreurs qui se produisent du côté client, car ce sont celles-là que je n'ai aucun contrôle ni moyen de savoir, à moins que les utilisateurs ne me le signalent. Côté serveur, je pense que je vais simplement rediriger n'importe quelle ligne de journal directement vers un fichier journal. Ce n'est certainement pas la meilleure solution car je voulais avoir toutes les erreurs au même endroit, mais en procédant ainsi, j'aurai au moins des informations sur toute erreur pouvant survenir sur l'application.

Que penses-tu de cela? Merci!

C'est exactement ce que nous faisons en ce moment, c'est un peu maladroit mais le meilleur que nous puissions trouver. Mais comme certaines erreurs côté serveur apparaissent dans sentinelle pour nous, il doit y avoir quelque chose qui se passe avec sentinelle ou next.js, je pense. J'ai essayé à la fois avec l'exemple simple et le plus complexe et les deux se comportent de la même manière, donc je suis au moins assez confiant que ce comportement n'est pas lié à notre configuration

Les membres de ce fil pourraient être intéressés par https://github.com/zeit/next.js/pull/8684 et les bogues associés. Il propose 12 tests différents d'exceptions non gérées avec lesquels vous pouvez jouer pour comprendre quelles exceptions Next.js gère pour vous et ce qu'il ne gère pas.

Des nouvelles concernant ce problème ?

Ma solution est d'utiliser redux enregistrer l'erreur de données de récupération de nœud dans state 。Puis dans le componentDidMount de _app.js faire quelque chose (alerte pour l'utilisateur ou post error req au backend).
Le code est là suivant-antd-scaffold_server-error .

========> état

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;

=======> action

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

Je dois aimer les solutions _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
}
// ¯\_(ツ)_/¯

Cette méthode ne peut pas être appliquée à l'erreur 404 car l'argument err sera nul en cas d'erreur 404.

J'ai résolu ce problème pour le client de nœud Elastic APM (https://www.npmjs.com/package/elastic-apm-node)

Pour toute personne intéressée :

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

Ensuite, dans _error.js render func, vous pouvez appeler captureError manuellement :

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

}

Salut à tous! Nous venons de lancer une bibliothèque NPM pour l'architecture de style Express dans Next.js sans ajouter de serveur Express. Cela pourrait être utile pour vos problèmes de gestion des erreurs de serveur ! Vérifiez-le si vous êtes intéressé. https://github.com/oslabs-beta/connext-js

Quelqu'un a réussi à attraper (et gérer) les exceptions qui se produisent dans getServerSideProps ?

Quelqu'un a réussi à attraper (et gérer) les exceptions qui se produisent dans getServerSideProps ?

Ce n'est pas clair, on pourrait penser qu'un cadre fournirait un moyen idiomatique de consigner les erreurs à la fois du client et du serveur 🤷‍♂️

Quelqu'un a réussi à attraper (et gérer) les exceptions qui se produisent dans getServerSideProps ?

@stephankaag

oui, quelque chose comme ça a fonctionné pour moi:

créez d'abord un middleware pour gérer le rapport de plantage. J'ai encapsulé Sentry dans la classe CrashReporter car le simple renvoi de Sentry ne fonctionnera pas (c'est- req.getCrashReporter = () => Sentry dire

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

Ensuite, bien sûr, chargez le middleware dans votre application avant que le gestionnaire de requêtes Next.js ne soit défini :

// server.js

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

...

app.use(crashReporterMiddleware);

...

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

alors partout où vous appelez 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 }
  }
}
Cette page vous a été utile?
0 / 5 - 0 notes