Next.js: Как выявлять и обрабатывать ошибки для создания отчетов о журналах на стороне сервера

Созданный на 2 мая 2017  ·  74Комментарии  ·  Источник: vercel/next.js

Привет,
Я в ситуации, когда мы хотим отправлять ошибки как на стороне сервера, так и на стороне клиента, в инструмент Sentry.

Наше приложение использует Express в качестве настраиваемого сервера. В основном мы создаем экспресс-приложение, применяем некоторое промежуточное ПО, но делегируем всю реальную работу дескриптору 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());

При таком подходе next.js берет на себя управление процессом рендеринга, и любая ошибка улавливается next.js, и единственный способ, которым я должен ее обработать, - это переопределить файл _error.js page.

В этом файле _error.js мне нужен универсальный способ сообщения об ошибках в Sentry. В настоящее время существует две библиотеки ( raven для узла и raven-js por javascript). Проблема в том, что я не могу импортировать их оба, потому что raven работает для SSR, но не работает, когда webpack создает пакет, а также raven-js не работает из-за зависимости XMLHTTPRequest.

Есть ли способ получить уведомление об ошибке next.js на стороне сервера?

story feature request

Самый полезный комментарий

Должен любить _unortodox_ solutions

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
}
// ¯\_(ツ)_/¯

Все 74 Комментарий

Для регистрации ошибок на стороне клиента мы сделали следующее:
https://gist.github.com/jgautheron/044b88307d934d486f59ae87c5a5a5a0

Он в основном отправляет ошибки на сервер, которые в конце выводятся на stdout и перехватываются нашим драйвером регистрации Docker .
Мы успешно отловили ошибки SSR с react-guard без необходимости переопределять основные файлы Next.

У меня тоже такая проблема. Интересно: будет ли достаточно просто возврата отклоненного обещания из следующего handleRequest ? То есть, изменив код здесь, чтобы он был:

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

Затем в пользовательском коде:

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 Как вы думаете, изменение, которое я предложил сразу выше,

Согласитесь повторно выдать ошибку, чтобы мы могли поиграть с ней.
Также нужно перебросить на renderToHTML тоже ...

Я также нахожусь в ситуации, когда мы хотим отправлять ошибки как на стороне сервера, так и на стороне клиента, в службу, аналогичную Sentry.

Я считаю, что наиболее ценной особенностью таких сервисов / инструментов является сообщение о самых неожиданных проблемах, которые, по моему опыту, являются неперехваченными ошибками в дикой природе (например, на стороне клиента). К сожалению, как ранее подчеркивалось в связанной проблеме № 2334, обработчики на стороне клиента Next.js хранят эти ошибки при себе, без возможности передать их Sentry или другому инструменту.

Что нас особенно сильно укусило, так это то, что правильно отрисованная на стороне сервера страница повторно отображается как страница с ошибкой, если неперехваченное исключение возникает до рендеринга React на стороне клиента . Это можно рассматривать как отличную функцию, но также как разочаровывающий опыт разработчика, поскольку он существенно сводит на нет преимущества обслуживания уже созданного документа для удивительно части клиентов.

Вот образец страницы, иллюстрирующий проблему:

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

Вышеупомянутый код может быть полностью обработан на стороне сервера и доставлен клиенту только для того, чтобы стать Flash нежелательного содержимого до того, как вся страница будет заменена на стороне клиента ужасным сообщением «Произошла непредвиденная ошибка» в большинстве браузеров. , без какого-либо возможного способа сообщить об ошибке в Sentry (или любой другой сервис / инструмент).

Это «проглатывание ошибок» также предотвращает любое использование стандартного обработчика ошибок onerror , который используется большинством клиентских средств создания отчетов об ошибках (или более современного, но не широко распространенного метода onunhandledrejection , который может быть более подходящим, учитывая асинхронный характер клиентской стороны. код).

Возможное клиентское решение

Насколько я могу судить, этот тип клиентской ошибки pre-React поглощается блоком try / catch в файле client / index.js Next.js, где Component готовится к рендерингу, переназначается на значение ErrorComponent (в настоящее время строки 67-72 ).

Уважаемые авторы и сопровождающие Next.js, ради контроля над тем, что отрисовывается, что вы считаете приемлемым / возможным из следующих идей:

  • ввести ловушку в этом блоке catch в client / index.js для обработки такого рода ошибок?
  • передать ошибку обработчику onerror / onunhandledrejection, если таковая обнаружена?
  • предоставить возможность повторно сбросить ошибку?
  • предоставить возможность не отображать компонент ошибки?
  • предоставить возможность отображать настраиваемый компонент ошибок?

ввести ловушку в этот блок catch в client / index.js для обработки такого рода ошибок?
передать ошибку обработчику onerror / onunhandledrejection, если таковая обнаружена?

Это то, о чем мы говорили внутри компании. И мы скоро обратимся к вам.

Я использую Sentry.io, чтобы сообщать об ошибках, и мы применяем следующее решение:

1- Настроить raven-node на стороне сервера
2- Настройте ravenjs на стороне клиента (мы сделали это на _document .

С помощью этих двух шагов мы перехватываем любые необработанные исключения как на клиенте, так и на сервере.

3- Создайте страницу _error . Любая ошибка, возникающая после того, как nextjs обрабатывает запрос (независимо от того, на стороне клиента или на стороне сервера) эта страница отображается. В методе getInitialProps страницы _error мы сообщаем об ошибке часовому.

Способ решить, как загружать, если raven-node или ravenjs решается импортировать динамически, в зависимости от того, находимся ли мы на стороне клиента const Raven = require('raven-js'); или на стороне сервера const Raven = require('raven'); .
Обратите внимание, мы настроили webpack, чтобы не связывать модуль raven (серверный), обновляя next.config.js с помощью:

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- Настройте ravenjs на стороне клиента (мы сделали это в _document.

Можете ли вы показать мне, как вы настроили raven-js на своем _document.js ? У меня не работает, при любой ошибке на карауле ничего не происходит.

Нужно ли мне отправлять все ошибки вручную на странице _error.js на часовую?

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

Next.js по-прежнему выводит ошибки в console.error на сервере, пока вы не установите его в тихий режим.

С помощью Sentry вы можете включить autoBreadcrumbs для захвата этого вывода , а затем вручную захватить собственное сообщение. Название будет менее информативным, но по-прежнему будет содержать полную трассировку стека.

Пример реализации:

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

Это очень надуманный пример, адаптированный из нашего реального кода. В таком виде не тестировал. Сообщите мне, если он сломается.

Конечно, все равно было бы лучше, если бы Next.js мог передавать эти ошибки в Express, чтобы мы могли использовать интеграцию Sentry / Express из коробки.

@tusgavomelo Извините за поздний ответ.

У нас есть обновление. В нашем приложении у нас есть вспомогательный файл с методом, отвечающим за получение экземпляра Raven с учетом того, находимся мы на стороне клиента или на стороне сервера.

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

Этот код работает как на стороне сервера (при поступлении запроса), так и на стороне клиента. Что мы сделали, так это настроили webpack (файл next.config.js ), чтобы избежать связывания пакета raven .

@acanimal, может, вы можете предоставить рабочий пример? Кажется, я не получаю полную трассировку стека? или, может быть, вы можете опубликовать свой _error.js ?

Я делаю что-то вроде RavenInstance.captureException(err) в моем _error.js , но я не могу увидеть тип возникших ошибок, а также где?

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

Это похоже на место, где можно запросить поддержку настраиваемого регистратора, поскольку для quiet должно быть установлено значение false чтобы предотвратить следующую очистку экрана при перестроении модуля и, таким образом, очистить ошибки от просмотра, но для единственного работоспособного взлома здесь требуется установка quiet на false.

Подробная информация об ошибках также доступна в потоке stderr.
process.stderr.write = error => yourErrorLog(error);

Должен любить _unortodox_ solutions

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
}
// ¯\_(ツ)_/¯

Компактная версия получения правильного Raven для узла и браузера без настраиваемой конфигурации веб-пакета. На основе комментария

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

Суть

краткий обзор для всех, кто исследует эту проблему.

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

благодаря комментарию @acanimal .

Установите как raven (игнорируйте с помощью webpack, как предлагалось в предыдущих комментариях), так и raven-js , затем создайте помощник для создания экземпляра изоморфного Raven, например 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;

Затем вы можете использовать его в своем pages/_error.js и он будет работать как на стороне сервера, так и на стороне клиента.

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;

Вот мой пиар для плагина Wepback исходной карты Rollbar https://github.com/thredup/rollbar-sourcemap-webpack-plugin/pull/56 с поддержкой Next.js :)

@tusgavomelo , не могли бы вы
"process.stderr.write = error => yourErrorLog (error);"

Где мы должны написать эту строку кода, чтобы она регистрировала ошибку в консоли узла?

Это только на стороне клиента.
Rollbar.error('some error')

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

@ teekey99 У вас есть подобное решение для @sentry/browser ? Может быть, обновить с часового примера?

@sheerun До сих пор я использовал raven и raven-js . Я знаю, что они, вероятно, станут устаревшими, поскольку все новые функции теперь добавлены в @sentry/node и @sentry/browser . Дело в том, что я еще не использовал эти новые библиотеки в своих проектах, но попробую разобраться. Если у меня есть рабочий пример, я вернусь к нему.

Недавно был обновлен пример with-sentry.

@timneutkens Я вижу, но он не поддерживает ошибки на стороне сервера, поскольку Sentry инициализируется внутри приложения, где уже слишком поздно обнаруживать ошибки сервера. Правильное решение, вероятно, где-то использовало бы @sentry/node

@sheerun Я @sentry/node нетривиально. В файле readme для этого примера Sentry предлагается использовать собственный сервер, который у нас уже есть в приложении, над которым я работаю. Чтобы фиксировать исключения на нашем настраиваемом сервере Express.js, вам необходимо вставить ошибку промежуточного программного обеспечения обработчика ошибок Sentry в качестве первого промежуточного программного обеспечения для обработки ошибок . Если вы вставляете обработчик ошибок Sentry сразу после обработчика Next, ошибка уже поглотила эту точку, и Sentry ее не видит.

Я использовал @sentry/browser в getInitialProps из _error.js и, похоже, он работает как на стороне клиента, так и на стороне сервера. Я не знаю, должна ли @sentry/browser иметь поддержку на стороне сервера, но я получаю события в Sentry.

Хотя я также вызываю Sentry.init() через @sentry/node в файле ввода настраиваемого сервера Express, так что, возможно, это устанавливает какое-то глобальное состояние, которое использует SSR.

Вот суть настройки, которую я использую: https://gist.github.com/mcdougal/7bf001417c3dc4b579da224b12776691

Интересный!

Здесь определенно происходит какое-то глобальное состояние (что немного пугает и, вероятно, хрупко и нежелательно). Применение ваших изменений в _error.js :

  • __Without__ настройка Sentry на настраиваемом сервере:

    • При работе в режиме разработки или производства (то есть с моим созданным приложением) в Sentry ничего не сообщается при возникновении ошибки на стороне сервера, но я получаю некоторую ошибку ReferenceError: XMLHttpRequest is not defined в журналах сервера.

  • __После настройки__ Sentry на настраиваемом сервере:

    • Сообщается об исключениях на стороне сервера, но я также получаю странное сообщение Error: Sentry syntheticException записанное в Sentry

Хотелось бы разобраться, в чем официальное решение. Документы Next, кажется, рекомендуют использовать пользовательский компонент <App> сейчас, и это то, что делает пример «with Sentry», но это работает только на стороне клиента.

Отчеты об ошибках точно не появятся, пока приложение не будет отображено в первый раз. Далее может произойти сбой раньше, например, при рендеринге Page. Возможно, случайно это сработает в некоторых случаях после первого рендеринга приложения на стороне сервера, но наверняка это не полное решение.

Пример with-sentry у меня тоже не работает @timneutkens. Запуск проекта и его тестирование с моим настоящим DSN дает ответ 400, и ничего не доходит до часового (версия 7 API).

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

@Mcdougal у меня не работал. Уже было не очень хорошо иметь глобальную настройку на стороне сервера, которая каким-то образом всплывает до клиента, но даже с этим взломом я получал бы ответ от часового 301.

Я не понимаю, как моя автономная установка на страже могла иметь неправильную конфигурацию, так как вариантов не так много, и она работает в нескольких проектах.

Я использую @sentry/browser же, как это рекомендуется в примере with-sentry, и, похоже, он отправляет ошибки как с сервера, так и с клиента на моего часового. У меня нет специальной конфигурации, только такой же код, как в примере.

@Jauny Вы уверены, что он отправляется с сервера? Как вы это проверили? Ридми этого примера, кажется, даже предполагает, что он не будет работать на сервере.

@timrogers да, действительно, моя тестовая ошибка исходила с сервера, но на самом деле она была вызвана клиентом :(

Кажется, что в текущем примере with-sentry не обнаруживаются ошибки, возникающие в getInitialProps, а только ошибки в дереве визуализации приложения. В моем случае большинство ошибок происходит из getInitialProps.

@sheerun Можете ли вы попробовать обходной путь mcdougal, описанный выше? Он не идеален (странные синтетические ошибки часового), но у меня сложилось впечатление, что он содержит все ошибки, и хотел бы знать, не правда ли это. Если это правда, то, вероятно, необходимо обновить пример with-sentry до тех пор, пока Next.js не сможет посоветовать, как это сделать лучше (в идеале, чтобы обработчик ошибок server.js sentry не пропускался?).

Кажется, это работает с двумя основными проблемами:

  1. Вероятно, из-за того, что асинхронный код на стороне сервера без необходимости компилируется в код регенератора, трассировки стека на стороне сервера в производственной среде часто усекаются на internal / process / next_tick.js, и причина ошибки вообще не видна.
  2. Обработка ошибок не будет работать, если ошибки возникнут до того, как будет вызван следующий обработчик запроса (например, в пользовательском коде сервера)

Фактически после дальнейшего тестирования getInitialProps пользовательской ошибки по какой-то причине даже не запускается в производственной среде, когда ошибка возникает, например, внутри getInitialProps пользовательского _app ..

Да, я определенно стал вести себя странно после нескольких дней пробежки. Похоже, что основные проблемы, с которыми мы сталкиваемся, следующие:

  1. Импорт @sentry/browser для CSR и @sentry/node для SSR
  2. Поиск места для обработки ошибок, которая всегда запускается как для CSR, так и для SSR

Я думал о том, чтобы попытаться использовать ленивый импорт и глобальное состояние для решения #1 , что-то вроде

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

Возможно, можно было бы использовать динамический импорт Next, но я еще не знаком с ними. Я также не уверен в последствиях вызова require каждый раз, когда запускается код обработки ошибок. Я обновлю, если / когда попробую.

Что касается проблемы #2 , похоже, что возможности следующие:

  1. _app.componentDidCatch , который не срабатывает для SSR
  2. _error.getInitialProps , с множеством проблем
  3. ...что-то другое? Что мы можем сделать для SSR в функции обработчика сервера Next?

Мне кажется, что есть способ сделать это на сервере, внедрив обработчик ошибок Sentry до Next, но я еще не попробовал.

На данный момент Next не предоставляет никаких ловушек, которые помогли бы вам в этом. Если бы мы обнаружили, что это работает, мы могли бы добавить их 👌

Однако есть риск, что это не сработает, потому что Next поймает слишком рано.

@mcdougal Ах & #!% Я был так счастлив, когда ваш обходной путь мог оказаться достаточно хорошим, чтобы

Простите за мое полное незнание, но разве нам просто нужно позволить нам условно отключить его обработчик ошибок, чтобы обработчик ошибок nodejs sentry стал первым? Какой-то флаг в next.config.js называется disableErrorHandler?

@Enalmada Я не думаю, что вы захотите отключить обработчик ошибок Next, потому что он будет отображать красивую страницу с ошибкой. Вы просто хотите вставить перед ним другое промежуточное ПО. Думаю, это сработает, но мне нужно попробовать.

Даже с этим исправленным, я все еще не чувствую, что обработка ошибок на стороне клиента работает так хорошо, как я надеюсь :(

Вся эта проблема - позор, и она действительно мешает безопасному запуску Next в производственной среде.

К вашему сведению, я импортирую везде @sentry/node и помещаю следующее в next.config.js:

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

что может быть лучше, чем eval из @mcdougal

Вот мои дополнительные заметки о пользовательском компоненте _app и обработке ошибок:

  1. _app.js используется для ВСЕХ страниц, включая _error.js или такие страницы, как 404, поэтому вы действительно хотите быть уверенным, что при передаче ctx.err не возникает никаких ошибок ..
  2. Не забудьте вызвать Component.getInitialProps в getInitialProps приложения, так как это предотвратит вызов getInitialProps из _error.js (вызывайте его, даже если присутствует ctx.err )
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
  }
}

Пока что я считаю, что правильно настроить отчет об ошибках в next.js очень хрупко :(

спасибо @sheerun , похоже, хорошая остановка в правильном направлении. Я согласен с тем, что обработка ошибок в следующем сейчас не оптимальна, было бы здорово увидеть добавленный расширяемый модуль / промежуточное ПО, чтобы мы могли добавить в него обработку ошибок и т. Д.

Отсутствие изоморфных библиотек, таких как sentry, также усложняет задачу, потому что это означает, что мы не можем просто импортировать любую библиотеку в наши компоненты, нам нужно делать это динамически во время выполнения, чтобы всегда проверять, возникает ли ошибка на стороне сервера или браузера.

Есть ли обновление по этой проблеме? Пока что я пробовал следующее: я переместил весь наш код отслеживания в _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 }
    }

в сочетании с добавлением next.config.js из @sheerun и инициализацией часового в server.js тоже if (!isServer) { config.resolve.alias['@sentry/node$'] = '@sentry/browser' }
похоже, что это отслеживает все ошибки на стороне клиента, но на стороне сервера он, кажется, отслеживает только первую ошибку, которая происходит после перезапуска сервера. Однако более поздние ошибки на сервере не отслеживаются. При таком подходе у меня в журнале нет SyntheticErrors, а есть только реальные ошибки.

Тем не менее, мне это кажется довольно взломанным, и, поскольку отслеживание на стороне сервера работает только в первый раз, его все еще нельзя использовать.

Я также добавил эту часть из примера 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)
        // }
    }

но я не совсем уверен, нужно ли это

В моем случае с работает без проблем. Также вы не должны вводить часового в
конструктор _app.js, но полностью вне этого класса

В среду, 21 ноября 2018 г., в 14:53 abraxxas [email protected] написал:

Есть ли обновление по этой проблеме? До сих пор я пробовал следующее: Я
переместил весь наш код отслеживания в _app.js

`
конструктор (аргументы: любой) {
супер (аргументы)
Sentry.init ({
dsn: 'бла',
среда: 'местный',
})
Sentry.configureScope (scope => {
scope.setTag ('errorOrigin', isServer? 'SSR': 'Клиент')
})
}

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 }

}

`

в сочетании с дополнением next.config.js от @sheerun
https://github.com/sheerun и инициализация часового в server.js тоже, если
(! isServer) {config.resolve.alias ['@ sentry / node $'] = '@ sentry / browser'}
это, кажется, отслеживает все ошибки на стороне клиента, но на стороне сервера
он отслеживает только первую ошибку, которая возникает после перезапуска
сервер. Однако более поздние ошибки на сервере не отслеживаются. С этим
подход у меня нет никаких SyntheticErrors в журнале, а только реальные ошибки.

Тем не менее, мне это кажется довольно взломанным, и поскольку отслеживание на стороне сервера
работает только в первый раз, он все еще непригоден для использования.

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/zeit/next.js/issues/1852#issuecomment-440668980 или отключить звук
нить
https://github.com/notifications/unsubscribe-auth/AAR2DeIhoOj6PdWRA2VqiEZyrO5Jui8vks5uxVrHgaJpZM4NOQlp
.

Я уже пробовал вытащить его, и все такое же поведение. @sheerun не могли бы вы опубликовать минимальную суть вашей установки? Я попытался настроить его с помощью предоставленных вами фрагментов, но не могу заставить его работать. Все это кажется слишком сложным для того, что я ожидал, будет довольно простой задачей :( Вы также инициализируете часовую на сервере или только в _app.js вне класса?

Я бы обновил официальный пример часового, но я боюсь, что он будет отклонен как "слишком сложный". Я могу попробовать опубликовать что-нибудь, хотя, когда найду время ..

@sheerun Даже попытка обновления до официального примера была бы очень ценной. Я чувствую, что это будет объединено, если это действительно минимальная сложность, необходимая для работы sentry ssr без SyntheticErrors или записи только первой ошибки сервера, которая происходит. Затем мы можем перейти оттуда, чтобы выяснить, как сделать его лучше, или настаивать на улучшении ядра nextjs или сторожевой изоморфной поддержке.

Итак, теперь, когда у нас есть обязательно сложный рабочий пример, каковы следующие шаги для улучшения ситуации:

Единственное, что нужно в next.js, - это возможность добавить пользовательское промежуточное ПО в next.config.js и что-то вроде next.browser.js для универсальной (изоморфной) конфигурации плагина (next.config.js используется, среди прочего, для конфигурации веб-пакетов, что означает, что другие вещи, определенные в этом файле, не могут использоваться в коде приложения, потому что это вызовет циклическую зависимость).

Для next.browser.js next.js может определять конфигурацию, такую ​​как декоратор, для компонентов приложения или документа. Таким образом, я мог полностью реализовать интеграцию с часовым в виде плагина.

РЕДАКТИРОВАТЬ: я не знаю, есть ли на это билет, но я думаю, что @timneutkens уже распаковывает следующий сервер в отдельные пакеты. Я думаю, что лучший интерфейс плагина будет примерно таким:

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

Предложение такого API я реализовал в # 6922

Он позволяет добавлять декораторы к пользовательскому коду сервера, потому что контракт изменяется на тот, который не вызывает автоматически .listen() поэтому он позволяет next.js дополнительно украсить его перед созданием экземпляра.

У меня были большие проблемы с использованием примера with-sentry , поэтому я начал PR для более простого примера. У него нет всех наворотов, но он работает для меня.

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

Попробуйте это в пользовательском _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>
    );
  }
}

И это в пользовательском _app.js :

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

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

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

Помогает мне.

Tbh я очень запутался в том, где сейчас находятся правильные места для отлова ошибок для next.js. Есть _app.tsx, _error.tsx и _document.tsx есть некоторые комментарии, в которых говорится, что некоторые вещи должны быть пойманы в _error.tsx (например: https://github.com/zeit/next.js/pull/5727/files # r235981700), но в текущих примерах используется либо _app.tsx, либо _app и _document.

Я попробовал оба способа сейчас, и кажется, что некоторые ошибки, возникающие во время ssr, мне не отлавливаются. Какие места теперь являются правильными для проверки на наличие ошибок? Для меня наиболее логично проверить componentDidCatch _app и getInitialProps _error, поскольку этот обслуживается в случае ошибок. Меня немного смущает вся часть process.on в _document

Может ли кто-нибудь с более глубокими знаниями разрешить этот вопрос?

@abraxxas process.on in _document.js перехватывает ошибки, возникающие на сервере. Подведем итоги:

  • _document.js используется _ только на стороне сервера_ и используется для изменения разметки исходного документа, отображаемого на стороне сервера.
  • _app.js используется только на стороне клиента и используется для инициализации страниц.

Следовательно, когда на сервере происходит ошибка, он должен выдать ошибку Sentry через process.on а затем клиент отобразит страницу ошибки по умолчанию или _error.js .

Надеюсь, это поможет: https://leerob.io/blog/configuring-sentry-for-nextjs-apps/

_app.js не только на стороне клиента, однако componentDidCatch

@timneutkens Хорошо, это немного морщит мне мозг. Я думаю, что документы обновились с тех пор, как я последний раз смотрел. Не могли бы вы более подробно объяснить, почему _app.js не является клиентским?

@timneutkens не могли бы вы объяснить, где и почему вы

@timneutkens Хорошо, это немного морщит мне мозг. Я думаю, что документы обновились с тех пор, как я последний раз смотрел. Не могли бы вы более подробно объяснить, почему _app.js не является клиентским?

Привет, Лироб!
Я только что разместил вопрос по запросу на слияние вашего примера:
https://github.com/zeit/next.js/pull/7360#issuecomment -514318899

Добавляя к этому вопросу ... Я также попытался выдать ошибку в render () на стороне сервера (я запустил состояние с помощью raiseErrorInRender: true, поэтому первый рендер, то есть на стороне сервера, уже выдает ошибку) и эта ошибка также не была зафиксирована Sentry.

Вы проверили это в своем приложении, и действительно ли эти серверные ошибки фиксируются? Я использую Next 9.

Если это действительно так, и фиксируются только ошибки на стороне клиента, также не было бы причин переопределять файл _document.js.

Заранее благодарю за любую помощь!

@timneutkens Хорошо, это немного морщит мне мозг. Я думаю, что документы обновились с тех пор, как я последний раз смотрел. Не могли бы вы более подробно объяснить, почему _app.js не является клиентским?

Отвечая на ваш вопрос, _app определяет компонент приложения для инициализации страниц. Мы бы переопределили его в таких случаях, как необходимость в постоянном макете между изменениями страницы (все страницы используют один и тот же _app) или для использования redux. Таким образом, даже первая визуализация, которая происходит на стороне сервера, будет отображать содержимое _app (это не только на стороне клиента).

Добавляя к этому вопросу ... Я также попытался выдать ошибку в render () на стороне сервера (я запустил состояние с помощью raiseErrorInRender: true, поэтому первый рендер, то есть на стороне сервера, уже выдает ошибку) и эта ошибка также не была зафиксирована Sentry.

Удалось ли вам каким-либо другим способом заставить эту ошибку появиться в карауле? Я попытался захватить его в getInitialProps _error, но он также ничего не показывает в часовом. В настоящее время я не нашел ни одного надежного способа фиксировать ошибки на сервере, некоторые из них (в основном, если они связаны с ошибками API) появляются, но мне не удалось получить простую ошибку со страницы errortest, чтобы появиться в часовом

Добавляя к этому вопросу ... Я также попытался выдать ошибку в render () на стороне сервера (я запустил состояние с помощью raiseErrorInRender: true, поэтому первый рендер, то есть на стороне сервера, уже выдает ошибку) и эта ошибка также не была зафиксирована Sentry.

Удалось ли вам каким-либо другим способом заставить эту ошибку появиться в карауле? Я попытался захватить его в getInitialProps _error, но он также ничего не показывает в часовом. В настоящее время я не нашел ни одного надежного способа фиксировать ошибки на сервере, некоторые из них (в основном, если они связаны с ошибками API) появляются, но мне не удалось получить простую ошибку со страницы errortest, чтобы появиться в часовом

К сожалению нет. Решение, которое, как мне кажется, я собираюсь реализовать, - использовать этот пример (без переопределенного файла _document.js) в качестве моего шаблона для захвата ошибок, возникающих на стороне клиента, поскольку это те, которые у меня нет контроля и возможности знать о том, если только пользователи не сообщают потом мне. На стороне сервера, я думаю, я просто перенаправлю любую строку журнала прямо в файл журнала. Это определенно не лучшее решение, поскольку я хотел, чтобы все ошибки были в одном месте, но, делая это таким образом, я, по крайней мере, буду иметь информацию о любой ошибке, которая может возникнуть в приложении.

Что вы думаете об этом? Спасибо!

Добавляя к этому вопросу ... Я также попытался выдать ошибку в render () на стороне сервера (я запустил состояние с помощью raiseErrorInRender: true, поэтому первый рендер, то есть на стороне сервера, уже выдает ошибку) и эта ошибка также не была зафиксирована Sentry.

Удалось ли вам каким-либо другим способом заставить эту ошибку появиться в карауле? Я попытался захватить его в getInitialProps _error, но он также ничего не показывает в часовом. В настоящее время я не нашел ни одного надежного способа фиксировать ошибки на сервере, некоторые из них (в основном, если они связаны с ошибками API) появляются, но мне не удалось получить простую ошибку со страницы errortest, чтобы появиться в часовом

К сожалению нет. Решение, которое, как мне кажется, я собираюсь реализовать, - использовать этот пример (без переопределенного файла _document.js) в качестве моего шаблона для захвата ошибок, возникающих на стороне клиента, поскольку это те, которые у меня нет контроля и возможности знать о том, если только пользователи не сообщают потом мне. На стороне сервера, я думаю, я просто перенаправлю любую строку журнала прямо в файл журнала. Это определенно не лучшее решение, поскольку я хотел, чтобы все ошибки были в одном месте, но, делая это таким образом, я, по крайней мере, буду иметь информацию о любой ошибке, которая может возникнуть в приложении.

Что вы думаете об этом? Спасибо!

Это именно то, что мы делаем сейчас, это немного неуклюже, но лучшее, что мы могли придумать. Но поскольку некоторые ошибки на стороне сервера обнаруживаются в часовом для нас, должно быть что-то происходит либо с часовым, либо с next.js, я думаю. Я пробовал использовать как простой, так и более сложный пример, и оба они ведут себя одинаково, поэтому я, по крайней мере, в некоторой степени уверен, что это поведение не связано с нашей настройкой.

Людей в этой теме могут заинтересовать https://github.com/zeit/next.js/pull/8684 и связанные с ними ошибки. В нем есть 12 различных тестов необработанных исключений, с которыми вы можете поиграть, чтобы понять, какие исключения обрабатывает Next.js за вас, а какие нет.

Есть новости по этому поводу?

Мое решение - использовать redux сохранить ошибку выборки данных узла в state 。Затем в componentDidMount _app.js сделайте что-нибудь (предупреждение для пользователя или сообщение об ошибке req в бэкэнд).
Код находится там next-antd-scaffold_server-error .

========> состояние

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;

=======> действие

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

Должен любить _unortodox_ solutions

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
}
// ¯\_(ツ)_/¯

Этот метод нельзя применить к ошибке 404, потому что аргумент err будет иметь значение NULL при ошибке 404.

Я решил это для клиента узла Elastic APM (https://www.npmjs.com/package/elastic-apm-node)

Для всех, кому интересно:

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

Затем в _error.js render func вы можете вручную вызвать captureError:

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

}

Всем привет! Мы только что запустили библиотеку NPM для архитектуры в стиле Express в Next.js без добавления сервера Express. Это может быть полезно для проблем с обработкой ошибок сервера! Проверьте это, если вам интересно. https://github.com/oslabs-beta/connext-js

Кому-нибудь удалось перехватить (и обработать) исключения, возникающие в getServerSideProps ?

Кому-нибудь удалось перехватить (и обработать) исключения, возникающие в getServerSideProps ?

Непонятно, можно ли подумать, что фреймворк предоставит идиоматический способ регистрации ошибок как клиента, так и сервера 🤷‍♂️

Кому-нибудь удалось перехватить (и обработать) исключения, возникающие в getServerSideProps ?

@stephankaag

да, у меня сработало что-то вроде этого:

сначала создайте промежуточное ПО для обработки отчетов о сбоях. Я заключил Sentry в класс CrashReporter потому что простой возврат Sentry не сработает (т.е. 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;

Затем, конечно, загрузите промежуточное ПО в свое приложение до того, как будет установлен обработчик запросов Next.js:

// server.js

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

...

app.use(crashReporterMiddleware);

...

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

тогда куда бы вы ни позвонили 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 }
  }
}
Была ли эта страница полезной?
0 / 5 - 0 рейтинги