Next.js: 如何捕获和处理错误以在服务器端报告日志

创建于 2017-05-02  ·  74评论  ·  资料来源: vercel/next.js

你好,
我的情况是我们想要将服务器端和客户端的错误发送到 Sentry 工具。

我们的应用程序使用 Express 作为自定义服务器。 基本上我们创建一个 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页面文件。

在那个_error.js文件中,我需要一种通用的方式来向 Sentry 报告错误。 目前有两个库(用于节点的raven和用于 javascript 的raven-js )。 问题是我不能同时导入它们,因为raven适用于 SSR 但在 webpack 构建包时失败,而且raven-js由于 XMLHTTPRequest 依赖而失败。

有什么方法可以通知我服务器端的 next.js 错误?

story feature request

最有用的评论

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

所有74条评论

为了在客户端记录错误,我们一直在做以下事情:
https://gist.github.com/jgautheron/044b88307d934d486f59ae87c5a5a5a0

它基本上将错误发送到服务器,最后打印到stdout并由我们的Docker日志驱动程序捕获。
我们使用react-guard成功捕获了 SSR 错误,无需覆盖核心 Next 文件。

我也有这个问题。 我想知道:简单地从 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你认为我上面提出的改变会奏效吗? 如果是这样,很高兴提交 PR

同意重新抛出一个错误,以便我们可以解决它。
也需要重新抛出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>;

上面的代码可以完美地在服务器端渲染并交付给客户端,只是在整个页面在客户端被大多数浏览器上可怕的“发生意外错误”消息替换之前变成了不需要的内容没有任何可能的方式向 Sentry (或任何其他服务/工具)

这种“错误吞没”还阻止了标准onerror处理程序的任何杠杆作用,大多数客户端错误报告工具都挂上了(或更现代但不普遍的

可能的客户端解决方案

据我所知,这种类型的 pre-React 客户端错误在 Next.js 的client/index.js中的try / catch块中被吞噬了,其中Component将要渲染的ErrorComponent (当前第67-72行)。

亲爱的 Next.js 作者和维护者,为了控制呈现的内容,您认为以下想法中哪些是可以接受/可能的:

  • 在 client/index.js 的catch块中引入一个钩子来处理这种错误?
  • 将错误传输到 onerror/onunhandledrejection 处理程序(如果检测到)?
  • 提供重新抛出错误的选项?
  • 提供不显示错误组件的选项?
  • 提供显示自定义错误组件的选项?

在 client/index.js 的 catch 块中引入一个钩子来处理这种错误?
将错误传输到 onerror/onunhandledrejection 处理程序(如果检测到)?

这是我们内部讨论过的。 我们很快就会解决。

我正在使用 Sentry.io 报告错误,我们应用的解决方案是:

1- 在服务器端配置 raven-node
2- 在客户端配置 ravenjs(我们在_document

通过这两个步骤,我们可以在客户端和服务器上捕获任何未处理的异常。

3- 创建一个_error页面。 一旦 nextjs 处理页面呈现的请求(无论是客户端还是服务器端)产生的任何错误。 在_error 页面的getInitialProps方法中,我们将错误报告给哨兵。

如果 raven-node 或 ravenjs 解决动态导入,则决定如何加载的方式取决于我们是在客户端const Raven = require('raven-js');还是服务器端const Raven = require('raven');
请注意,我们已将 webpack 配置为不捆绑更新next.config.jsraven模块(服务器端模块):

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.

你能告诉我你是如何在_document.js上配置 raven-js 的吗? 它对我不起作用,当发生任何错误时,哨兵没有任何反应。

我是否需要将_error.js页面上的所有错误手动发送给哨兵?

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

Next.js 仍然会在服务器上向console.error输出错误,只要您不将其设置为quiet

使用 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以防止模块重建时的下一个清除屏幕,从而从视图中清除错误,但这里唯一可行的 hack 需要将quiet为 false。

错误详细信息也可在 stderr 流中找到。
process.stderr.write = error => yourErrorLog(error);

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

如何在没有自定义 webpack 配置的情况下为节点和浏览器获取正确的 Raven 的紧凑版本。 灵感来自@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

要旨

对调查此问题的任何人的快速回顾。

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

这是我对 Rollbar sourcemap wepback 插件 https://github.com/thredup/rollbar-sourcemap-webpack-plugin/pull/56 的 PR,支持 Next.js :)

@tusgavomelo ,您能否详细说明如何利用流中存在的错误?
“process.stderr.write = error => yourErrorLog(error);”

我们应该在哪里编写这行代码,以便将错误记录到 Node 控制台?

那只是客户端。
Rollbar.error('some error')

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

@teekey99你对@sentry/browser有类似的解决方案吗? 也许更新哨兵示例?

@sheerun 到目前为止,我一直在使用ravenraven-js 。 我知道这些可能会被弃用,因为所有新功能现在都添加到@sentry/node@sentry/browser 。 问题是我还没有在我的项目中使用这些新库,但我会尝试研究它。 如果我有一个工作示例,我会回来的。

with-sentry 示例最近已更新。

@timneutkens我看到了,但它不支持服务器端错误,因为 Sentry 是在 App 内部初始化的,在那里捕获服务器错误为时已晚。 正确的解决方案可能会@sentry/node某处使用

@sheerun我遇到了同样的问题。 使用@sentry/node并非易事。 这个 Sentry 示例的自述文件建议使用自定义服务器,我们在我正在处理的应用程序中已经有了它。 要在我们的自定义 Express.js 服务器中捕获异常,您需要插入 Sentry 错误处理程序中间件错误作为第一个错误处理中间件。 如果您在 Next 处理程序之后直接插入 Sentry 的错误处理程序,则该错误已被该点吞并而 Sentry 看不到它。

我一直在@sentry/browsergetInitialProps中使用_error.js ,它似乎在客户端和服务器端都有效。 我不知道@sentry/browser是否应该有服务器端支持,但我正在向 Sentry 发送事件。

虽然我也在自定义 Express 服务器的入口文件中通过@sentry/node调用Sentry.init() ,所以也许这正在设置 SSR 正在使用的一些全局状态。

这是我正在使用的设置要点: https :

有趣的!

这里肯定存在某种全局状态(这有点可怕,而且可能很脆弱和不受欢迎)。 在_error.js应用您的更改:

  • __Without__ 在自定义服务器中配置 Sentry:

    • 在开发模式或生产模式下运行(即构建我的应用程序)时,发生服务器端错误时不会向 Sentry 报告任何内容,但我在服务器日志中收到一些ReferenceError: XMLHttpRequest is not defined错误

  • __在自定义服务器中配置__哨兵后:

    • 我的服务器端异常被报告,但我也在 Sentry 中记录了一个奇怪的Error: Sentry syntheticException

最好了解官方解决方案是什么。 Next 的文档现在似乎建议使用自定义<App>组件,这就是“with Sentry”示例所做的,但这仅适用于客户端。

在第一次呈现应用程序之前,肯定不会发生错误报告。 Next 可能会在例如渲染页面之前失败。 也许在应用程序第一次在服务器端呈现后,它碰巧在某些情况下工作,但肯定它不是一个完整的解决方案。

@timneutkens 的 with-sentry 示例对我也不起作用。 运行该项目并使用我的实际 DSN 对其进行测试会给出 400 响应,但哨兵(api 的第 7 版)没有任何反应。

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

@mcdougal对我不起作用。 拥有一个以某种方式冒泡到客户端的全局服务器端设置已经不是很好,但即使有这种黑客,我也会得到哨兵 301 响应。

我不知道我的哨兵自托管设置如何配置错误,因为选项不多并且它在多个项目上运行。

我使用@sentry/browser的方式与 with-sentry 示例中推荐的方式相同,它似乎从服务器和客户端向我的哨兵发送错误。 我没有任何特殊的配置,只有与示例相同的代码。

@Jauny你确定它是从服务器发送的吗? 你是如何测试的? 该示例的自述文件甚至似乎表明它在服务器上不起作用。

@timrogers是的,确实是我的错,我假设一个经过测试的错误来自服务器,但实际上是从客户端触发的 :(

似乎当前的带有哨兵的例子没有捕获 getInitialProps 中抛出的错误,只是应用程序渲染树中的错误。在我的情况下,大多数错误来自 getInitialProps。

@sheerun你能试试上面的mcdougal 解决方法吗? 它并不完美(奇怪的合成哨兵错误),但我的印象是它会出现所有错误,并想知道这是否属实。 如果是真的,那么 with-sentry 示例可能需要更新,直到 Next.js 可以建议如何做得更好(理想情况下,不要跳过 server.js sentry 错误处理程序?)。

似乎它有点适用于两个主要问题:

  1. 可能是因为异步服务器端代码被不必要地编译成再生器代码,生产中的服务器端堆栈跟踪经常在 internal/process/next_tick.js 上被截断,并且根本看不到错误原因。
  2. 如果在调用下一个请求处理程序之前抛出错误(例如在自定义服务器代码中),则错误处理将不起作用

实际上,在进一步测试了自定义错误的 getInitialProps 之后,由于某种原因,当错误发生在生产环境中时,例如在自定义 _app 的 getInitialProps 中,它甚至没有在生产中触发。

是的,在尝试跑步几天后,我肯定会出现一些奇怪的行为。 我们面临的主要问题似乎是:

  1. 为 CSR 导入@sentry/browser ,为 SSR 导入@sentry/node
  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. ……还有什么? 我们可以在 Next 服务器处理程序函数中为 SSR 做些什么吗?

我的直觉是,可以通过在 Next 之前注入 Sentry 的错误处理程序来在服务器上执行此操作,但我还没有尝试过。

目前,Next 没有提供任何钩子来帮助你做到这一点。 如果我们发现这有效,我们可以添加它们👌

但是,这存在不起作用的风险,因为 Next 捕获得太早了。

@mcdougal Ah &#!% 当您的解决方法可能足以检查使我们投入生产所需的哨兵客户端/服务器覆盖范围时,我感到非常高兴。

请原谅我完全无知,但我们是否只需要 next 允许我们有条件地禁用其错误处理程序,以便nodejs sentry 错误处理程序成为第一个? next.config.js 中的一些标志称为“disableErrorHandler”?

@Enalmada我认为您不想禁用 Next 错误处理程序,因为它会呈现一个不错的错误页面。 您只想在它之前插入其他中间件。 我认为这会奏效,但我需要尝试一下。

即使修复了这个问题,我仍然觉得客户端错误处理的效果不如我希望的 :(

这整个问题是一种耻辱,它确实是在生产中安全运行 Next 的障碍。

仅供参考,我到处导入@sentry/node并将以下内容放入 next.config.js:

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

这可能比@mcdougal 的eval更好

以下是我关于自定义 _app 组件和错误处理的额外说明:

  1. _app.js用于所有页面,包括_error.js或类似 404 的页面,因此您确实希望确保在将ctx.err传递给它时不会引发错误。
  2. 不要忘记在应用程序的 getInitialProps 中调用 Component.getInitialProps,因为它会阻止调用_error.js getInitialProps(即使存在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 ,这看起来像是朝着正确方向的一个很好的停留。 我确实同意 next 中的错误处理现在不是最佳的,很高兴看到添加了一些可扩展的模块/中间件,以便我们可以在其上添加错误处理等。

缺少诸如 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 }
    }

再加上来自@sheerun的 next.config.js 添加以及在 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 构造函数,但完全在这个类之外

在星期三,2018年11月21日在下午2时53 abraxxas [email protected]写道:

这个问题有更新吗? 到目前为止我尝试的是以下内容:我
将我们所有的跟踪代码移动到 _app.js

`
构造函数(参数:任何){
超级(参数)
哨兵.init({
dsn: '等等',
环境:'本地',
})
Sentry.configureScope(范围 => {
scope.setTag('errorOrigin', isServer ? 'SSR' : 'Client')
})
}

静态异步 getInitialProps({ Component, router, ctx }: any) {
让 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 }

}

`

加上来自@sheerun 的 next.config.js 添加
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即使尝试更新到官方示例也很有价值。 我觉得它会被合并,如果它真的是让哨兵 ssr 在没有 SyntheticErrors 的情况下工作所需的最低复杂性,或者只记录发生的第一个服务器错误。 然后我们可以从那里找出改进它或推动 nextjs 核心改进或哨兵同构支持的方法。

因此,既然我们有了一个必然复杂的工作示例,接下来要改进这种情况的步骤是什么:

在 next.js 中唯一需要的是能够在 next.config.js 和类似 next.browser.js 的通用(同构)插件配置中添加自定义中间件(next.config.js 用于 webpack 配置,这意味着此文件中定义的其他内容不能在应用程序代码中使用,因为它会导致循环依赖)。

对于 next.browser.js next.js 可以为 App 或 Document 组件定义像装饰器这样的配置。 这样我就可以完全作为插件来实现哨兵集成。

编辑:我不知道是否有票,但我认为@timneutkens已经将下一个服务器提取到单独的包中。 我认为最好的插件界面应该是这样的:

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

我已经在 #6922 中实现了这种 API 的提议

它允许向自定义服务器代码添加装饰器,因为合同更改为不自动调用.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 期间发生的一些错误没有被我发现。 现在哪些地方是检查错误的正确位置? 对我来说最合乎逻辑的似乎是检查 _app 的 componentDidCatch 和 _error 的 getInitialProps,因为在出​​现错误时会提供这个服务。 我对 _document 中的整个process.on部分有点困惑

有更深入知识的人可以解决这个问题吗?

@abraxxas process.on中的_document.js正在捕获服务器上发生的错误。 作为回顾,

  • _document.js仅用于_服务器端_,用于更改初始服务器端呈现的文档标记。
  • _app.js是 _client-side only_ 用于初始化页面。

因此,当服务器上发生错误时,它应该通过process.on向 Sentry 抛出错误,然后客户端将呈现默认错误页面或_error.js

希望这会有所帮助: https :

_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 组件以初始化页面的地方。 如果需要在页面更改之间保持布局(所有页面使用相同的 _app)或使用 redux,我们会覆盖它。 因此,即使是在服务器端发生的第一次渲染,也会渲染 _app 的内容(不仅仅是客户端)。

添加到那个问题......我还尝试在服务器端的 render() 中抛出一个错误(我用 raiseErrorInRender: true 开始状态,所以第一个渲染,即服务器端,已经抛出一个错误),并且Sentry 也没有捕捉到该错误。

您是否以任何其他方式设法使该错误显示在哨兵中? 我尝试在 _error 的 getInitialProps 中捕获它,但在哨兵中也没有显示任何内容。 目前我还没有找到一种可靠的方法来捕获服务器上的错误,其中一些(主要是如果它们连接到 api 故障)它们会出现,但我没有设法从 errortestpage 中得到一个简单的错误以显示在哨兵中

添加到那个问题......我还尝试在服务器端的 render() 中抛出一个错误(我用 raiseErrorInRender: true 开始状态,所以第一个渲染,即服务器端,已经抛出一个错误),并且Sentry 也没有捕捉到该错误。

您是否以任何其他方式设法使该错误显示在哨兵中? 我尝试在 _error 的 getInitialProps 中捕获它,但在哨兵中也没有显示任何内容。 目前我还没有找到一种可靠的方法来捕获服务器上的错误,其中一些(主要是如果它们连接到 api 故障)它们会出现,但我没有设法从 errortestpage 中得到一个简单的错误以显示在哨兵中

不幸的是没有。 我想我要实现的解决方案是使用示例(减去覆盖的 _document.js 文件)作为我的模板来捕获客户端发生的错误,因为这些是我无法控制的错误知道,除非用户向我报告。 在服务器端,我想我只是将任何日志行直接重定向到日志文件。 这绝对不是最好的解决方案,因为我希望将所有错误都放在同一个地方,但是这样做我至少会获得有关应用程序上可能发生的任何错误的信息。

你对那个怎么想的? 谢谢!

添加到那个问题......我还尝试在服务器端的 render() 中抛出一个错误(我用 raiseErrorInRender: true 开始状态,所以第一个渲染,即服务器端,已经抛出一个错误),并且Sentry 也没有捕捉到该错误。

您是否以任何其他方式设法使该错误显示在哨兵中? 我尝试在 _error 的 getInitialProps 中捕获它,但在哨兵中也没有显示任何内容。 目前我还没有找到一种可靠的方法来捕获服务器上的错误,其中一些(主要是如果它们连接到 api 故障)它们会出现,但我没有设法从 errortestpage 中得到一个简单的错误以显示在哨兵中

不幸的是没有。 我想我要实现的解决方案是使用示例(减去覆盖的 _document.js 文件)作为我的模板来捕获客户端发生的错误,因为这些是我无法控制的错误知道,除非用户向我报告。 在服务器端,我想我只是将任何日志行直接重定向到日志文件。 这绝对不是最好的解决方案,因为我希望将所有错误都放在同一个地方,但是这样做我至少会获得有关应用程序上可能发生的任何错误的信息。

你对那个怎么想的? 谢谢!

这正是我们现在正在做的事情,它有点笨重,但我们能想出最好的。 但是由于我们在哨兵中出现了一些服务器端错误,我认为哨兵或 next.js 肯定会发生一些事情。 我尝试了简单和更复杂的示例,它们的行为都相同,所以我至少有点确信这种行为与我们的设置无关

此线程中的人可能对https://github.com/zeit/next.js/pull/8684和相关错误感兴趣。 它有 12 种不同的未处理异常测试,您可以使用这些测试来了解 Next.js 为您处理的异常和不处理的异常。

有关于这个问题的消息吗?

我的解决方案是使用redux将节点获取数据错误保存在state 。然后在componentDidMount_app.js做一些事情(提醒用户或发布错误到后端)。
代码在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_解决方案

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 参数将在 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渲染函数中,您可以手动调用 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}`);
    }
  }

}

大家好! 我们刚刚在 Next.js 中为 Express 风格的架构推出了一个 NPM 库,而没有添加 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 等级