You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Next.js自定义服务器Express中间件Sentry错误日志配置问题

好的,我来帮你一步步解决这个问题——先从错误中间件的核心原理说起,再拆解你每个尝试的问题,最后给你适配Next.js+Express+Sentry的正确配置方案。


一、Express/Next.js 错误中间件的工作原理

Express 错误中间件的核心规则

Express 对错误处理中间件有个强制要求:必须包含4个参数:(err, req, res, next),少一个参数都不会被识别为错误中间件,而是会被当作普通的请求处理中间件。

当你在路由或其他中间件中调用next(err)时,Express会跳过所有普通中间件,直接找到第一个符合4参数格式的错误中间件执行。这是你所有尝试出问题的核心根源之一。

Next.js + Express 集成的特殊点

用Express作为Next.js的自定义服务器时,错误流转分为两种场景:

  1. Express自身的API路由/中间件抛出的错误:会正常流转到Express的错误中间件。
  2. Next.js页面渲染(比如getInitialProps、组件渲染)抛出的错误:需要通过Next.js的_error.js页面捕获,或者手动把错误传递给Express的错误中间件处理。

二、你的四个尝试为啥出问题?

我们逐个拆解:

尝试1:4参数格式正确,但位置错误

function logError(err, req, res, next) {
  Sentry.captureMessage(`${err.stack} - ${err.message}`)
  Sentry.captureException(err)
  console.log('Error reported to sentry.')
  next(err)
}

问题原因:这个中间件的格式是对的,但你大概率把它放在了Next.js路由中间件(app.get('*', handle))之前。Express的路由匹配是顺序执行的,当Next.js处理请求时抛出的错误,不会流转到它之前的错误中间件——错误中间件必须放在所有路由和普通中间件之后。

尝试2:3参数被当作普通中间件,无响应挂起

function logError(err, req, res) {
  Sentry.captureMessage(`${err.stack} - ${err.message}`)
  Sentry.captureException(err)
  console.log('Error reported to sentry.')
}

问题原因:只有3个参数,Express把它当成了普通请求中间件,只有当请求刚好匹配到这个中间件的路由时才会执行。而且你没有调用next()或返回响应,导致请求一直处于挂起状态。

尝试3:3参数+响应对象不兼容

function logError(err, req, res) {
  Sentry.captureMessage(`${err.stack} - ${err.message}`)
  Sentry.captureException(err)
  console.log('Error reported to sentry.')
  res.statusCode = 500
  res.send('Internal server error')
}

问题原因:同样是3参数的普通中间件,另外res.send is not a function是因为Next.js封装了响应对象,和Express原生响应对象的API不一致,直接调用Express的响应方法会出错。

尝试4:3参数+错误封装错误

function logError(err, req, res) {
  Sentry.captureMessage(`${err.stack} - ${err.message}`)
  Sentry.captureException(err)
  console.log('Error reported to sentry.')
  throw new Error(err)
}

问题原因:还是3参数的普通中间件,throw new Error(err)时,因为err本身是Error对象,直接传入会调用它的toString()方法,变成[object Object],所以错误栈异常。能执行只是因为请求匹配到了它的路由,不是错误正常流转过来的。


三、正确的配置方案

1. Express服务器端完整配置

在你的服务器入口文件(比如server.js)中,严格按照顺序配置:

const Sentry = require('@sentry/node');
const express = require('express');
const next = require('next');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

// 1. 初始化Sentry
Sentry.init({
  dsn: 'YOUR_SENTRY_DSN', // 替换成你的Sentry DSN
  environment: dev ? 'development' : 'production',
});

const server = express();

// 2. 先添加Sentry的请求处理中间件(必须在所有其他中间件之前)
server.use(Sentry.Handlers.requestHandler());

// 3. 你的其他Express中间件(比如解析JSON、静态文件)
server.use(express.json());
server.use(express.static('public'));

// 4. 处理Next.js的所有路由
server.get('*', (req, res) => {
  return handle(req, res);
});

// 5. 自定义错误处理中间件(必须是4参数,放在所有路由之后)
server.use((err, req, res, next) => {
  // 上报错误到Sentry
  Sentry.captureException(err);
  console.log('Error reported to Sentry:', err.message);

  // 根据环境返回响应
  if (dev) {
    // 开发环境返回详细错误信息
    res.status(500).json({
      message: err.message,
      stack: err.stack,
    });
  } else {
    // 生产环境返回友好提示
    res.status(500).send('Internal Server Error');
  }

  // 如果不需要继续传递错误,就不用调用next(err)
});

// 6. 可选:添加Sentry自带的错误处理中间件(自动处理错误并返回响应)
server.use(Sentry.Handlers.errorHandler());

// 启动服务器
app.prepare().then(() => {
  server.listen(3000, (err) => {
    if (err) throw err;
    console.log('> Ready on http://localhost:3000');
  });
});

2. Next.js页面错误的捕获

如果是页面渲染(比如getInitialProps)抛出的错误,在pages/_error.js中捕获并上报:

import * as Sentry from '@sentry/node';

function Error({ statusCode }) {
  return (
    <div style={{ padding: '2rem' }}>
      <h1>{statusCode ? `Error ${statusCode}` : 'An error occurred'}</h1>
    </div>
  );
}

Error.getInitialProps = async ({ res, err }) => {
  const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
  // 上报页面错误到Sentry
  if (err) {
    Sentry.captureException(err);
  }
  return { statusCode };
};

export default Error;

关键注意事项

  • 错误中间件位置:必须放在所有路由(包括Next.js的handle)之后,否则Express找不到它处理错误。
  • 4参数强制要求:错误处理中间件必须有err, req, res, next四个参数,缺一不可。
  • Sentry中间件顺序Sentry.Handlers.requestHandler()必须在最前面,Sentry.Handlers.errorHandler()放在最后。

内容的提问来源于stack exchange,提问作者Aral Roca

火山引擎 最新活动