Next.js: Como detectar e lidar com erros para relatar registros no lado do servidor

Criado em 2 mai. 2017  ·  74Comentários  ·  Fonte: vercel/next.js

Oi,
Estou na situação em que queremos enviar erros, tanto do lado do servidor quanto do cliente, para a ferramenta Sentry.

Nosso aplicativo usa o Express como um servidor personalizado. Basicamente, criamos um aplicativo expresso, aplicamos alguns middlewares, mas delegamos todo o trabalho real para o 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());

Com essa abordagem, next.js assume o controle sobre o processo de renderização e qualquer erro é detectado por next.js e a única maneira que tenho de processá-lo é substituindo o arquivo de página _error.js .

Dentro daquele arquivo _error.js , preciso de uma maneira universal de relatar erros ao Sentinela. Atualmente, existem duas bibliotecas ( raven para o nó e raven-js por javascript). O problema é que não posso importar os dois porque raven funciona para SSR, mas falha quando o webpack cria o pacote, e também raven-js falha devido à dependência XMLHTTPRequest.

Existe uma maneira de ser notificado sobre o erro next.js no lado do servidor?

story feature request

Comentários muito úteis

Tenho que amar soluções _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 comentários

Para registrar erros no lado do cliente, temos feito o seguinte:
https://gist.github.com/jgautheron/044b88307d934d486f59ae87c5a5a5a0

Ele basicamente envia erros para o servidor, que são no final impressos em stdout e capturados por nosso driver de log Docker .
Detectamos erros de SSR com sucesso com react-guard sem a necessidade de substituir os arquivos principais do Next.

Eu também estou tendo esse problema. Eu me pergunto: seria suficiente retornar uma promessa rejeitada do próximo handleRequest ? Ou seja, alterar o código aqui para ser:

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

Então, no código do usuário:

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 Você acha que a mudança que propus imediatamente acima funcionaria? Em caso afirmativo, fico feliz em enviar um PR

Concorde em relançar um erro para que possamos brincar com ele.
Também precisa re-lançar em renderToHTML também ...

Também estou na situação em que queremos enviar erros, tanto do lado do servidor quanto do cliente, para um serviço semelhante ao Sentry.

Acredito que o recurso mais valioso de tais serviços / ferramentas é relatar problemas que são mais inesperados, que, em minha experiência, são erros não detectados à solta (ou seja, do lado do cliente). Infelizmente, conforme enfatizado anteriormente na questão relacionada nº 2334, os manipuladores do lado do cliente do Next.js guardam esses erros para si mesmos, sem nenhuma maneira possível de passá-los para o Sentry dessa outra ferramenta.

O que nos incomodou particularmente é o seguinte: uma página renderizada corretamente do lado do servidor é renderizada novamente como uma página de erro se uma exceção não detectada ocorrer antes da renderização do React no lado do cliente . Isso pode ser visto como um ótimo recurso, mas também uma experiência frustrante para o desenvolvedor, pois essencialmente arruína os benefícios de servir um documento já renderizado, para uma parte surpreendente de clientes.

Aqui está uma página de amostra que ilustra o 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>;

O código acima pode ser perfeitamente renderizado do lado do servidor e entregue ao cliente, apenas para se tornar um Flash de conteúdo indesejado antes que toda a página seja substituída no lado do cliente pela temida mensagem "Ocorreu um erro inesperado" na maioria dos navegadores , sem nenhuma maneira possível de relatar o erro ao Sentry (ou qualquer outro serviço / ferramenta).

Este "engolir erros" também evita qualquer alavancagem do manipulador onerror padrão, ao qual a maioria das ferramentas de relatório de erros do lado do cliente se rejeição não

Possível solução do lado do cliente

Até onde eu posso dizer, este tipo de erro do lado do cliente pré-React é engolido no bloco try / catch em Next.js ' client / index.js onde o Component prestes a ser renderizado é reatribuído ao valor de ErrorComponent (atualmente linhas 67-72 ).

Caros autores e mantenedores do Next.js, por uma questão de controle do que é renderizado, o que vocês acham que seria aceitável / possível entre as seguintes idéias:

  • introduziu um gancho naquele bloco catch em client / index.js para lidar com esse tipo de erro?
  • transmitir o erro para um manipulador onerror / onunhandledrejection, se algum for detectado?
  • oferece uma opção para relançar o erro?
  • fornece uma opção para não exibir o componente de erro?
  • fornece uma opção para exibir um componente de erro personalizado?

introduzir um gancho nesse bloco catch em client / index.js para lidar com esse tipo de erro?
transmitir o erro para um manipulador onerror / onunhandledrejection, se algum for detectado?

Isso é algo sobre o qual conversamos internamente. E nós iremos abordar em breve.

Estou usando Sentry.io para relatar erros e a solução que aplicamos é:

1- Configure o raven-node no lado do servidor
2- Configure ravenjs no lado do cliente (fizemos isso em _document .

Com essas duas etapas, capturamos todas as exceções não tratadas no cliente e no servidor.

3- Crie uma página _error . Qualquer erro produzido uma vez que nextjs lida com a solicitação (não importa se do lado do cliente ou do servidor) que a página é renderizada. No método getInitialProps da página _error, relatamos o erro ao sentinela.

A maneira de decidir como carregar se raven-node ou ravenjs é resolvida importando dinamicamente dependendo se estamos no cliente const Raven = require('raven-js'); ou no lado do servidor const Raven = require('raven'); .
Observe que configuramos o webpack para não agrupar o módulo raven (o do lado do servidor), atualizando next.config.js com:

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 no lado do cliente (fizemos isso no _document.

Você pode me mostrar como configurou raven-js em seu _document.js ? Não está funcionando para mim, quando qualquer erro ocorre, nada acontece no sentinela.

Preciso enviar todos os erros manualmente na página _error.js para o sentinela?

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

Next.js ainda gera erros para console.error no servidor, desde que você não o defina como silencioso .

Com o Sentry, você pode habilitar autoBreadcrumbs para capturar essa saída e, em seguida, capturar sua própria mensagem manualmente. O título será menos descritivo, mas ainda conterá o rastreamento de pilha completo.

Implementação de exemplo:

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 é um exemplo muito artificial, adaptado de nosso código real. Eu não testei neste formulário. Me avise se quebrar.

Claro, ainda seria melhor se Next.js pudesse passar esses erros para o Express, para que possamos usar a integração Sentry / Express pronta para uso.

@tusgavomelo Desculpe, pela resposta tardia.

Temos atualização. Em nosso aplicativo temos um arquivo helper com um método responsável por obter uma instância Raven levando em consideração se estamos no lado do cliente ou do 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 é executado no lado do servidor (quando chega uma solicitação) e no lado do cliente. O que fizemos foi configurar o webpack ( next.config.js file) para evitar empacotar o pacote raven .

@acanimal você pode fornecer um exemplo _error.js ?

Estou fazendo algo como RavenInstance.captureException(err) em meu _error.js , mas não consigo ver os tipos de erros que ocorreram e onde?

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 o lugar para solicitar suporte de logger personalizado, já que quiet deve ser definido como false para evitar a próxima tela de limpeza na reconstrução do módulo e, assim, eliminar os erros da visualização, mas o único hack viável aqui requer definindo quiet como falso.

Os detalhes dos erros também estão disponíveis no fluxo stderr.
process.stderr.write = error => yourErrorLog(error);

Tenho que amar soluções _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
}
// ¯\_(ツ)_/¯

Versão compacta de como obter o Raven correto para o nó e o navegador sem a configuração do webpack personalizado. Inspirado por comentário @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

Essência

uma rápida recapitulação para quem está investigando esse 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$/));

graças ao comentário de @acanimal .

Instale raven (ignore com webpack conforme sugerido nos comentários anteriores) e raven-js , em seguida, crie um auxiliar para instanciar Raven isomórfico, por exemplo, 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;

Então você pode usá-lo no seu pages/_error.js e ele funcionará tanto no servidor quanto no 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;

Aqui está o meu PR para o plugin wepback do mapa de origem Rollbar https://github.com/thredup/rollbar-sourcemap-webpack-plugin/pull/56 com suporte Next.js. :)

@tusgavomelo , Você pode explicar como usar o erro presente no stream?
"process.stderr.write = erro => yourErrorLog (erro);"

Onde devemos escrever esta linha de código, para que ela registre o erro no console do Node?

Esse é apenas o lado do cliente.
Rollbar.error('some error')

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

@ teekey99 Você tem uma solução semelhante para @sentry/browser ? Talvez atualizar com um exemplo de sentinela?

@sheerun Tenho usado raven e raven-js até agora. Estou ciente de que eles provavelmente se tornarão obsoletos, pois todos os novos recursos foram adicionados a @sentry/node e @sentry/browser . O fato é que ainda não usei essas novas bibliotecas em meus projetos, mas tentarei dar uma olhada nelas. Se eu tiver um exemplo que funcione, vou voltar com ele.

O exemplo com sentinela foi atualizado recentemente.

@timneutkens , vejo, mas não oferece suporte a erros do lado do servidor, pois o Sentry é inicializado dentro do aplicativo, onde é tarde demais para detectar erros do servidor. A solução adequada provavelmente usaria @sentry/node algum lugar

@sheerun Estou encontrando o mesmo problema. Usar @sentry/node não é trivial. O leia-me para este exemplo do Sentry sugere o uso de um servidor personalizado, que já temos no aplicativo em que estou trabalhando. Para capturar exceções em nosso servidor Express.js customizado, você precisa inserir um erro de middleware do manipulador de erros Sentry como o primeiro middleware de tratamento de erros . Se você inserir o manipulador de erros do Sentinela logo após o próximo manipulador, o erro já foi engolido por aquele ponto e o Sentinela não o vê.

Tenho usado @sentry/browser em getInitialProps de _error.js e parece estar funcionando tanto no lado do cliente quanto no lado do servidor. Não sei se @sentry/browser deve ter suporte do lado do servidor, mas estou recebendo eventos para o Sentry.

Embora eu também esteja chamando Sentry.init() via @sentry/node em um arquivo de entrada do servidor Express personalizado, então talvez isso esteja definindo algum estado global que o SSR está usando.

Aqui está um resumo da configuração que estou usando: https://gist.github.com/mcdougal/7bf001417c3dc4b579da224b12776691

Interessante!

Definitivamente, há algum tipo de estado global acontecendo aqui (o que é um pouco assustador e provavelmente frágil e indesejável). Aplicando suas alterações em _error.js :

  • __Without__ configurando o Sentry no servidor personalizado:

    • Ao executar no modo de desenvolvimento ou produção (ou seja, com meu aplicativo construído), nada é relatado ao Sentry quando ocorre um erro do lado do servidor, mas recebo algum erro ReferenceError: XMLHttpRequest is not defined nos logs do servidor

  • __Após configurar o__ Sentry no servidor personalizado:

    • Minhas exceções do lado do servidor são relatadas, mas também recebo um Error: Sentry syntheticException estranho registrado no Sentinela

Seria bom entender qual é a solução oficial. Os documentos do Next parecem recomendar o uso de um componente <App> customizado agora, e é isso que o exemplo "with Sentry" faz, mas isso só funciona para o lado do cliente.

O relatório de erros com certeza não acontecerá até a primeira vez que o aplicativo for renderizado. Next pode falhar muito antes, por exemplo, ao renderizar a página. Talvez por acaso isso funcione em alguns casos depois que o aplicativo foi renderizado no servidor pela primeira vez, mas com certeza não é uma solução completa.

O exemplo with-sentry também não funciona para mim @timneutkens. Executar o projeto e testá-lo com meu DSN real dá uma resposta 400 e nada chega ao sentinela (versão 7 da api).

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

O @mcdougal não funcionou para mim. Já não era muito bom ter uma configuração global do lado do servidor que de alguma forma borbulha para o cliente, mas mesmo com esse hack eu receberia 301 respostas de sentinela.

Não vejo como minha configuração auto-hospedada de sentinela pode ter uma configuração incorreta, pois não há muitas opções e está sendo executado em vários projetos.

Estou usando @sentry/browser da mesma maneira que é recomendado no exemplo com-sentinela, e parece estar enviando erros do servidor e do cliente para minha sentinela. Não tenho nenhuma configuração especial, apenas o mesmo código do exemplo.

@Jauny Tem certeza de que está enviando do servidor? Como você testou isso? O readme do exemplo até parece sugerir que não funcionará no servidor.

@timrogers sim, de fato, meu mal, presumi que um erro testado estava vindo do servidor, mas na verdade foi disparado do cliente :(

Parece que o exemplo atual com-sentry não detecta erros lançados em getInitialProps, apenas alguns na árvore de renderização do aplicativo. No meu caso, a maioria dos erros são de getInitialProps.

@sheerun Você pode tentar a solução alternativa mcdougal acima? Não é perfeito (erros estranhos de sentinela sintética), mas tenho a impressão de que recebe todos os erros e gostaria de saber se isso não é verdade. Se for verdade, o exemplo with-sentry provavelmente precisa ser atualizado com isso até que Next.js possa aconselhar sobre como fazê-lo melhor (de preferência fazendo com que o manipulador de erros sentry server.js não seja ignorado?).

Parece que funciona com dois problemas principais:

  1. Provavelmente porque o código assíncrono do lado do servidor é compilado desnecessariamente em código regenerador, os stacktraces do lado do servidor na produção são frequentemente truncados em internal / process / next_tick.js e nenhuma causa de erro é visível.
  2. O tratamento de erros não funcionará se os erros forem lançados antes que o próximo manipulador de solicitação seja chamado (por exemplo, no código de servidor personalizado)

Na verdade, após mais testes, getInitialProps de custom Error, por algum motivo, nem mesmo dispara na produção quando ocorre um erro, por exemplo, dentro de getInitialProps de _app customizado.

Sim, definitivamente tenho tido um comportamento estranho depois de tentar fazer isso por alguns dias. Parece que os principais problemas que enfrentamos são:

  1. Importando @sentry/browser para CSR e @sentry/node para SSR
  2. Encontrar um local para colocar o tratamento de erros que sempre é acionado para CSR e SSR

Tenho pensado em usar as importações preguiçosas e o 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);

Talvez as importações dinâmicas do Next pudessem ser usadas, mas ainda não estou familiarizado com elas. Também não tenho certeza das repercussões de chamar require toda vez que o código de tratamento de erros é acionado. Vou atualizar se / quando eu tentar.

Quanto ao problema #2 , parece que as possibilidades são:

  1. _app.componentDidCatch , que não dispara para SSR
  2. _error.getInitialProps , que tem uma infinidade de problemas
  3. ...algo mais? Há algo que possamos fazer para SSR na função de manipulador de servidor Next?

Meu pressentimento é que haverá uma maneira de fazer isso no servidor, injetando o manipulador de erros do Sentry antes do Next, mas ainda não tentei.

No momento, o Next não fornece nenhum gancho para ajudá-lo a fazer isso. Se descobrirmos que isso funciona, podemos adicioná-los 👌

Há um risco de que isso não funcione porque o Next pega muito cedo.

@mcdougal Ah & #!% Fiquei tão feliz quando sua solução alternativa pode ter sido boa o suficiente para verificar a cobertura de cliente / servidor de sentinela necessária para nos colocar em produção.

Perdoe minha total ignorância, mas precisamos apenas em seguida nos permitir desabilitar seu manipulador de erros condicionalmente para que o manipulador de erros sentinela nodejs se torne o primeiro? Algum sinalizador em next.config.js chamado "disableErrorHandler"?

@Enalmada Não creio que você queira desabilitar o próximo manipulador de erros porque será ele que renderizará uma bela página de erro. Você apenas deseja inserir outro middleware antes dele. Acho que vai funcionar, mas preciso tentar.

Mesmo com isso corrigido, ainda não acho que o tratamento de erros do lado do cliente funcione tão bem quanto eu esperava :(

Todo esse problema é uma pena e é realmente um bloqueador para a execução segura do Next em produção.

Para sua informação, eu importo em todos os lugares @sentry/node e coloco o seguinte em next.config.js:

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

que pode ser melhor do que eval de @mcdougal

Aqui estão minhas observações extras sobre o componente _app personalizado e tratamento de erros:

  1. _app.js é usado para TODAS as páginas, incluindo _error.js ou páginas como 404, então você realmente deseja ter certeza de que nenhum erro será gerado quando ctx.err for passado para ele.
  2. Não se esqueça de chamar Component.getInitialProps em getInitialProps do app, pois isso evitará que getInitialProps de _error.js seja chamado (chame-o mesmo se ctx.err estiver 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
  }
}

Até agora, acho que configurar adequadamente o relatório de erros em next.js é um procedimento muito frágil :(

obrigado @sheerun que parece uma boa parada na direção certa. Eu concordo que o tratamento de erros no próximo não é o ideal agora, será ótimo ver algum módulo extensível / middleware adicionado para que possamos adicionar o tratamento de erros nele etc.

A falta de bibliotecas isomórficas como sentinela também está complicando as coisas, porque isso significa que não podemos simplesmente importar nenhuma das bibliotecas em nossos componentes, precisamos fazer isso dinamicamente em tempo de execução para sempre verificar se o erro é gerado no servidor ou no navegador.

Existe uma atualização para este problema? O que tentei até agora é o seguinte: movi todo o nosso código de acompanhamento para o _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 }
    }

juntamente com a adição next.config.js de @sheerun e a inicialização do sentinela no server.js também if (!isServer) { config.resolve.alias['@sentry/node$'] = '@sentry/browser' }
isso parece rastrear todos os erros no lado do cliente, mas no lado do servidor, parece rastrear apenas o primeiro erro que ocorre após a reinicialização do servidor. No entanto, erros posteriores no servidor não são rastreados. Com essa abordagem, não tenho nenhum SyntheticErrors no log, mas apenas erros reais.

Ainda assim, isso parece bastante hacky para mim e, uma vez que o rastreamento do lado do servidor está funcionando apenas na primeira vez, ainda não é utilizável.

Eu também adicionei esta parte do exemplo 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)
        // }
    }

mas não estou totalmente certo se isso é necessário

No meu caso funciona sem problemas. também você não deve iniciar o sentinela
construtor _app.js, mas inteiramente fora desta classe

Na quarta-feira, 21 de novembro de 2018 às 14h53 abraxxas [email protected] escreveu:

Existe uma atualização para este problema? O que tentei até agora é o seguinte:
movemos todo o nosso código de rastreamento para o _app.js

`
construtor (args: any) {
super (args)
Sentry.init ({
dsn: 'blah',
ambiente: 'local',
})
Sentry.configureScope (scope => {
scope.setTag ('errorOrigin', isServer? 'SSR': 'Cliente')
})
}

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 }

}

`

juntamente com a adição next.config.js de @sheerun
https://github.com/sheerun e inicializando sentry no server.js também se
(! isServer) {config.resolve.alias ['@ sentry / node $'] = '@ sentry / browser'}
isso parece rastrear todos os erros do lado do cliente, mas do lado do servidor
parece apenas rastrear o primeiro erro que acontece após uma reinicialização do
servidor. No entanto, erros posteriores no servidor não são rastreados. Com isso
abordagem Eu não tenho nenhum SyntheticErrors no log, mas apenas erros reais.

Ainda assim, isso parece bastante hacky para mim e, uma vez que o rastreamento do lado do servidor é
funcionando apenas na primeira vez, ainda não é utilizável.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/zeit/next.js/issues/1852#issuecomment-440668980 ou mudo
o segmento
https://github.com/notifications/unsubscribe-auth/AAR2DeIhoOj6PdWRA2VqiEZyrO5Jui8vks5uxVrHgaJpZM4NOQlp
.

Já tentei mudar isso e ainda o mesmo comportamento. @sheerun, você poderia postar um

Eu atualizaria o exemplo oficial de sentinela, mas temo que seja rejeitado como "muito complexo". Posso tentar postar algo quando encontrar tempo ..

@sheerun Mesmo uma tentativa de atualizar para o exemplo oficial seria de grande valor. Acho que seria mesclado se realmente tivesse a complexidade mínima necessária para fazer o SSR do Sentry funcionar sem SyntheticErrors ou apenas registrar o primeiro erro de servidor que ocorrer. Então podemos ir de lá para descobrir maneiras de torná-lo melhor ou empurrar para melhorias do núcleo do nextjs ou suporte isomórfico sentinela.

Portanto, agora que temos um exemplo de trabalho necessariamente complexo, quais são os próximos passos para melhorar a situação:

A única coisa necessária em next.js é a capacidade de adicionar middleware personalizado em next.config.js e algo como next.browser.js para configuração de plug-in universal (isomórfico) (next.config.js é usado entre outros para configuração de webpack, o que significa que outras coisas definidas neste arquivo não podem ser usadas no código do aplicativo, porque causaria dependência circular).

Para next.browser.js, next.js poderia definir a configuração como decorador para componentes de aplicativos ou documentos. Dessa forma, eu poderia implementar a integração sentry inteiramente como um plugin.

EDIT: Eu não sei se há ticket para isso, mas acho que @timneutkens já está extraindo o próximo servidor em pacotes separados. Acho que a melhor interface de plug-in seria algo como:

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

Implementei a proposta de tal API em # 6922

Ele permite adicionar decoradores ao código do servidor personalizado porque o contrato é alterado para um que não chame .listen() automaticamente, então permite que next.js o decore antes de instanciar

Eu estava tendo muitos problemas para usar o exemplo with-sentry , então abri um PR para um exemplo muito mais simples. Não tem todos os sinos e assobios, mas tem funcionado para mim.

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

Experimente isso de forma personalizada _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>
    );
  }
}

E isso personalizado _app.js :

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

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

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

Isso me ajuda.

Tbh, estou muito confuso sobre quais são os locais corretos para detectar erros para next.js agora. Existem _app.tsx, _error.tsx e _document.tsx, existem alguns comentários dizendo que algumas coisas devem ser capturadas em _error.tsx (como: https://github.com/zeit/next.js/pull/5727/files # r235981700), mas os exemplos atuais usam _app.tsx ou _app e _document.

Eu tentei das duas maneiras agora e parece que alguns erros que acontecem durante o ssr não estão sendo detectados para mim. Quais lugares agora são os corretos para verificar se há erros? O mais lógico para mim parece verificar no componentDidCatch de _app e em getInitialProps de _error já que este está sendo servido em casos de erros. Estou um pouco confuso sobre toda a parte process.on do _documento

Alguém com um conhecimento mais profundo poderia resolver esta questão?

@abraxxas process.on em _document.js está capturando erros que acontecem no servidor. Recapitulando,

  • _document.js é _apenas do lado do servidor_ e é usado para alterar a marcação inicial do documento renderizado do lado do servidor.
  • _app.js é _apenas do lado do cliente_ e é usado para inicializar páginas.

Portanto, quando um erro acontece no servidor, ele deve lançar um erro para o Sentry via process.on e então o cliente irá renderizar a página de erro padrão ou _error.js .

Espero que isso ajude: https://leerob.io/blog/configuring-sentry-for-nextjs-apps/

_app.js não é apenas do lado do cliente, no entanto componentDidCatch é

@timneutkens Ok, isso enruga meu cérebro um pouco. Acho que os documentos foram atualizados desde a última vez que olhei. Você poderia explicar como _app.js não está do lado do cliente apenas com mais detalhes?

@timneutkens você poderia explicar onde e por que você detectaria erros? Parece haver tantas opiniões.

@timneutkens Ok, isso enruga meu cérebro um pouco. Acho que os documentos foram atualizados desde a última vez que olhei. Você poderia explicar como _app.js não está do lado do cliente apenas com mais detalhes?

Hello Leerob!
Acabei de postar uma pergunta sobre a solicitação de mesclagem do seu exemplo:
https://github.com/zeit/next.js/pull/7360#issuecomment -514318899

Adicionando a essa pergunta ... Eu também tentei lançar um erro em render () no lado do servidor (eu iniciei o estado com raiseErrorInRender: true, então a primeira renderização, que é do lado do servidor, já geraria um erro), e aquele erro também não foi capturado pelo Sentry.

Você testou isso em seu aplicativo e esses erros do lado do servidor estão realmente sendo capturados? Estou usando o Next 9.

Se esse for realmente o caso, e apenas os erros do lado do cliente estiverem sendo capturados, também não haveria razão para substituir o arquivo _document.js.

Agradecemos antecipadamente por qualquer ajuda!

@timneutkens Ok, isso enruga meu cérebro um pouco. Acho que os documentos foram atualizados desde a última vez que olhei. Você poderia explicar como _app.js não está do lado do cliente apenas com mais detalhes?

Respondendo à sua pergunta, _app é onde um componente de aplicativo é definido para inicializar as páginas. Nós o substituiríamos em casos como a necessidade de um layout persistente entre as mudanças de página (todas as páginas utilizam o mesmo _app) ou para fazer uso de redux. Portanto, mesmo a primeira renderização, que ocorre no lado do servidor, renderizará o conteúdo de _app (não é apenas do lado do cliente).

Adicionando a essa pergunta ... Eu também tentei lançar um erro em render () no lado do servidor (eu iniciei o estado com raiseErrorInRender: true, então a primeira renderização, que é do lado do servidor, já geraria um erro), e aquele erro também não foi capturado pelo Sentry.

Você conseguiu de alguma outra forma fazer com que aquele erro aparecesse na sentinela? Tentei capturá-lo no getInitialProps de _error, mas isso também não mostra nada no sentry. Atualmente não encontrei uma maneira confiável de capturar erros no servidor, alguns deles (principalmente se eles estão conectados a falhas de API) eles aparecem, mas eu não consegui obter um erro simples da página errortest para aparecer no sentinela

Adicionando a essa pergunta ... Eu também tentei lançar um erro em render () no lado do servidor (eu iniciei o estado com raiseErrorInRender: true, então a primeira renderização, que é do lado do servidor, já geraria um erro), e aquele erro também não foi capturado pelo Sentry.

Você conseguiu de alguma outra forma fazer com que aquele erro aparecesse na sentinela? Tentei capturá-lo no getInitialProps de _error, mas isso também não mostra nada no sentry. Atualmente não encontrei uma maneira confiável de capturar erros no servidor, alguns deles (principalmente se eles estão conectados a falhas de API) eles aparecem, mas eu não consegui obter um erro simples da página errortest para aparecer no sentinela

Infelizmente não. A solução que acho que vou implementar é usar o exemplo (sem o arquivo _document.js substituído) como meu modelo para capturar erros que ocorrem no lado do cliente, uma vez que são aqueles que eu não tenho controle e como saber sobre, a menos que os usuários relatem então a mim. No lado do servidor, acho que redirecionarei qualquer linha de log diretamente para um arquivo de log. Essa definitivamente não é a melhor solução, já que eu queria ter todos os erros no mesmo lugar, mas assim fazendo, terei pelo menos informações sobre qualquer erro que possa ocorrer no aplicativo.

O que você acha daquilo? Obrigado!

Adicionando a essa pergunta ... Eu também tentei lançar um erro em render () no lado do servidor (eu iniciei o estado com raiseErrorInRender: true, então a primeira renderização, que é do lado do servidor, já geraria um erro), e aquele erro também não foi capturado pelo Sentry.

Você conseguiu de alguma outra forma fazer com que aquele erro aparecesse na sentinela? Tentei capturá-lo no getInitialProps de _error, mas isso também não mostra nada no sentry. Atualmente não encontrei uma maneira confiável de capturar erros no servidor, alguns deles (principalmente se eles estão conectados a falhas de API) eles aparecem, mas eu não consegui obter um erro simples da página errortest para aparecer no sentinela

Infelizmente não. A solução que acho que vou implementar é usar o exemplo (sem o arquivo _document.js substituído) como meu modelo para capturar erros que ocorrem no lado do cliente, uma vez que são aqueles que eu não tenho controle e como saber sobre, a menos que os usuários relatem então a mim. No lado do servidor, acho que redirecionarei qualquer linha de log diretamente para um arquivo de log. Essa definitivamente não é a melhor solução, já que eu queria ter todos os erros no mesmo lugar, mas assim fazendo, terei pelo menos informações sobre qualquer erro que possa ocorrer no aplicativo.

O que você acha daquilo? Obrigado!

Isso é exatamente o que estamos fazendo agora, é um pouco desajeitado, mas o melhor que pudemos sugerir. Mas, uma vez que alguns erros do lado do servidor aparecem no sentinela para nós, deve haver algo acontecendo com o sentinela ou com o next.js, eu acho. Eu tentei com o exemplo simples e com o exemplo mais complexo e ambos se comportam da mesma forma, então estou pelo menos um pouco confiante de que este comportamento não está relacionado à nossa configuração

As pessoas neste tópico podem estar interessadas em https://github.com/zeit/next.js/pull/8684 e bugs relacionados. Ele tem 12 testes diferentes de exceções não tratadas com os quais você pode brincar para entender quais exceções o Next.js trata para você e quais não.

Alguma notícia sobre este assunto?

Minha solução é usar redux salvar o erro de busca de dados do nó em state 。Então em componentDidMount de _app.js faça algo (alertar para o usuário ou postar erro req para backend).
O código está lá em 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;

=======> ação

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

Tenho que amar soluções _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 não pode ser aplicado ao erro 404 porque o argumento err será nulo quando ocorrer o erro 404.

Resolvi isso para o cliente do nó Elastic APM (https://www.npmjs.com/package/elastic-apm-node)

Para qualquer pessoa interessada:

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

Então, em _error.js render func, você pode chamar 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}`);
    }
  }

}

Olá a todos! Acabamos de lançar uma biblioteca NPM para arquitetura de estilo Express em Next.js sem adicionar um servidor Express. Pode ser útil para os desafios de tratamento de erros do servidor! Verifique se você está interessado. https://github.com/oslabs-beta/connext-js

Alguém teve sucesso em capturar (e manipular) exceções que ocorrem em getServerSideProps ?

Alguém teve sucesso em capturar (e manipular) exceções que ocorrem em getServerSideProps ?

Não está claro, você pensaria que um framework forneceria uma maneira idiomática de registrar erros de cliente e servidor 🤷‍♂️

Alguém teve sucesso em capturar (e manipular) exceções que ocorrem em getServerSideProps ?

@stephankaag

sim, algo assim funcionou para mim:

primeiro crie um middleware para lidar com o relatório de travamento. Eu envolvi Sentry dentro da classe CrashReporter porque simplesmente retornar Sentry não funcionará (ou seja, 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;

em seguida, é claro, carregue o middleware em seu aplicativo antes que o manipulador de solicitação Next.js seja definido:

// server.js

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

...

app.use(crashReporterMiddleware);

...

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

então, onde quer que você chame 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 }
  }
}
Esta página foi útil?
0 / 5 - 0 avaliações