NextJs 的 middleware 无法使用 winston 进行日志处理

时之世 发布于 2025-03-25 374 次阅读 预计阅读时间: 5 分钟 最后更新于 2025-03-25 995 字 无~


AI 摘要

在 Next.js 的 middleware.ts 中使用 winston 时会遇到因为 Edge Runtime 不支持某些 Node.js API 导致的错误,如 `process.nextTick`。 Edge Runtime 是轻量级的、无服务器环境,不兼容 Node.js 的核心 API,因此使用依赖这些 API 的库 (如 winston) 会失败。为了解决这个问题,可以考虑以下方案: 1. **使用轻量级日志库**:使用 console 方法替代 winston,以支持边缘运行时的环境。 2. **将日志逻辑移到服务端**:创建 API 路由来处理日志,避免在 Middleware 中直接使用不兼容的库。 3. **禁用边缘运行时**:通过配置 `next.config.js` 强制使用 Node.js 运行时,但不推荐因为会失去边缘运行时的性能优势。 推荐的最佳方案根据需求而定:如果只需要简单的日志记录,使用方案 1;如果需要复杂日志,使用方案 2;避免使用方案 3 。通过这些方法,确保 Middleware 在边缘运行时中正常工作,同时满足日志记录的需求。

在 Next.js 的 middleware.ts 中使用 winston 时出现以下错误:

⨯ Error: A Node.js API is used (process.nextTick) which is not supported in the Edge Runtime.

这是因为 Next.js Middleware 默认运行在 Edge Runtime(边缘运行时) 中,而边缘运行时是一个轻量级的、无服务器化的环境,专为快速和可扩展的执行设计。它对某些 Node.js API 和功能有严格的限制。


为什么会发生这个错误?

  1. Edge Runtime 的限制
  • 边缘运行时被设计为轻量且跨平台 (例如 Vercel 的全球 CDN),因此不支持一些 Node.js 的功能。
  • 比如,process.nextTick 是 Node.js 的核心 API,但在边缘运行时不被支持。
  • winston 内部依赖了这些不受支持的 Node.js API,因此无法在边缘运行时中正常工作。
  1. Middleware 的执行环境
  • Next.js Middleware 默认在边缘网络上运行,这意味着它在边缘运行时中执行。
  • 如果你尝试在 Middleware 中使用依赖于 Node.js 的库 (如 winston),就会触发类似的错误。
  1. 你的代码问题
  • middleware.ts 中,你使用了 winston 来记录日志。虽然 winston 在 Node.js 环境中运行良好,但它并不兼容边缘运行时。

如何解决这个问题?

为了避免这个错误,你需要避免在边缘运行时中使用依赖于 Node.js 的库或 API 。以下是几种解决方案:


方案 1:使用与边缘运行时兼容的轻量级日志库

可以使用简单的日志工具 (如 console 方法) 来替代 winston,因为 console.logconsole.error 是边缘运行时支持的。

示例代码:

const logger = {
    info: (message: string, metadata?: any) => {
        console.log(`${new Date().toISOString()} [INFO] ${message}`, metadata);
    },
    error: (message: string, metadata?: any) => {
        console.error(`${new Date().toISOString()} [ERROR] ${message}`, metadata);
    },
};

export default logger;

然后用这个轻量级的日志工具替换你的 logger.info 调用:

logger.info(`Request from IP: ${clientIp}, URL: ${req.url}`);

这种方式简单高效,适合基本的日志需求。


方案 2:将日志逻辑移到服务端

如果你需要更复杂的日志功能 (比如写入文件),可以将日志逻辑移到服务端的 API 路由或其他后端服务中,而不是放在 Middleware 中。

  1. 创建一个 API 路由 (如 pages/api/log.ts) 来处理日志。
  2. 在 Middleware 中调用这个 API 路由来记录日志。

API 路由 (pages/api/log.ts)

import { createLogger, format, transports } from 'winston';

const logger = createLogger({
    format: format.combine(
        format.timestamp(),
        format.json()
    ),
    transports: [
        new transports.File({ filename: 'combined.log', level: 'info' }),
        new transports.File({ filename: 'error.log', level: 'error' }),
    ],
});

export default async function handler(req, res) {
    const { message, metadata } = req.body;
    logger.info(message, metadata);
    res.status(200).json({ success: true });
}

Middleware (middleware.ts)

import { NextResponse } from 'next/server';

export async function middleware(req) {
    const clientIp = req.ip;
    const logData = {
        clientIp,
        reqUrl: req.url,
    };

    // 将日志数据发送到 API 路由
    await fetch('http://your-domain/api/log', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message: 'Request logged', metadata: logData }),
    });

    return NextResponse.next();
}

这种方式将复杂的日志逻辑移到了服务端,避免了边缘运行时的限制。


方案 3:禁用边缘运行时

如果你必须在 Middleware 中使用 winston 或其他依赖 Node.js 的库,可以通过配置 next.config.js 来禁用边缘运行时,强制使用 Node.js 运行时。

module.exports = {
    experimental: {
        runtime: 'nodejs', // 使用 Node.js 运行时,而不是边缘运行时
    },
};

不过,这种做法不推荐,因为它会失去边缘运行时的性能优势,并可能导致更高的成本和较慢的执行速度。


推荐方案

最佳方案取决于你的需求:

  • 如果只需要简单的日志记录,使用 方案 1console 方法) 。
  • 如果需要复杂日志功能 (如写入文件),使用 方案 2(将日志逻辑移到服务端) 。
  • 避免使用 方案 3,除非你没有其他选择,因为它会牺牲边缘运行时的性能优势。

通过以上方法,可以确保 Middleware 在边缘运行时中正常工作,同时满足日志记录的需求。