You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Next.js(TypeScript) Monorepo中实现Pino客户端与服务端日志?

Next.js (TypeScript) Monorepo 中配置 Pino 实现全端日志记录

核心方案概述

在Monorepo架构下,我们可以将日志配置抽为共享包,分别实现服务端直接输出到Graylog和客户端日志通过API路由转发到Graylog的逻辑,同时保证TypeScript类型一致性。


1. Monorepo 目录结构规划

首先将日志模块独立为共享包,方便主应用及其他子包复用:

your-monorepo/
├── apps/
│   └── next-app/          # Next.js 主应用
│       ├── app/
│       │   ├── api/
│       │   │   └── log/route.ts  # 客户端日志转发API
│       │   └── ...
│       └── package.json
├── packages/
│   └── logging/           # 共享日志包
│       ├── src/
│       │   ├── server.ts  # 服务端日志配置
│       │   ├── client.ts  # 客户端日志配置
│       │   ├── graylogStream.ts  # 你的Graylog流实现
│       │   └── index.ts   # 统一导出入口
│       ├── package.json
│       └── tsconfig.json
└── package.json

2. 服务端 Pino 配置优化(共享包)

基于你提供的代码,补充TypeScript类型支持及Next.js服务端场景适配:

packages/logging/src/graylogStream.ts

保留你的现有实现,确保它符合Pino的Stream接口:

import type { Stream } from 'pino';

class GraylogStream implements Stream {
  private host: string;
  private port: number;

  constructor(host: string, port: number) {
    this.host = host;
    this.port = port;
    // 初始化Graylog连接逻辑
  }

  write(chunk: string | Buffer): boolean {
    // 实现将日志发送到Graylog的逻辑
    console.log('Sending to Graylog:', chunk.toString());
    return true;
  }
}

export default GraylogStream;

packages/logging/src/server.ts

import pino from 'pino';
import GraylogStream from './graylogStream';

// 从环境变量读取配置,TS类型断言
const graylogHost = process.env.GRAYLOG_HOST as string;
const graylogPort = parseInt(process.env.GRAYLOG_PORT || '12201', 10);
const logLevel = process.env.LOG_LEVEL || 'info';

export const serverLogger = pino(
  {
    level: logLevel,
    serializers: {
      err: pino.stdSerializers.err,
      req: pino.stdSerializers.req,
      res: pino.stdSerializers.res,
      // 自定义序列化Next.js的Request对象(Server Components/Middleware)
      nextReq: (req: Request) => ({
        method: req.method,
        url: req.url,
        headers: Object.fromEntries(req.headers.entries()),
      }),
    },
    // 服务端日志添加环境标识
    base: { env: process.env.NODE_ENV, service: 'next-app' },
  },
  new GraylogStream(graylogHost, graylogPort)
);

3. 客户端 Pino 配置(共享包)

客户端无法直接连接Graylog(跨域限制),因此通过Next.js API路由转发日志:

packages/logging/src/client.ts

import pino from 'pino/browser';

// 客户端日志发送到API路由
const sendLogToServer = async (logEntry: string) => {
  try {
    await fetch('/api/log', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: logEntry,
      keepalive: true, // 页面卸载时仍能发送日志
    });
  } catch (err) {
    // 降级处理:控制台输出失败的日志
    console.error('Failed to send client log:', err);
  }
};

// 创建客户端Pino实例
export const clientLogger = pino({
  level: process.env.NEXT_PUBLIC_LOG_LEVEL || 'info',
  // 客户端日志添加标识
  base: { env: process.env.NODE_ENV, service: 'next-app-client' },
  // 自定义传输:发送到API路由
  transport: {
    target: 'pino-browser',
    options: {
      write: sendLogToServer,
    },
  },
  // 禁用客户端默认的控制台输出(可选,根据需求调整)
  browser: {
    write: () => {},
  },
});

4. 共享包导出入口

packages/logging/src/index.ts

export { serverLogger } from './server';
export { clientLogger } from './client';

5. Next.js 主应用配置与使用

5.1 客户端日志转发API路由

创建apps/next-app/app/api/log/route.ts:

import { serverLogger } from '@your-monorepo/logging';
import { NextRequest } from 'next/server';

export async function POST(req: NextRequest) {
  try {
    const logEntry = await req.json();
    // 将客户端日志转发到Graylog
    serverLogger.write(logEntry);
    return new Response(null, { status: 204 });
  } catch (err) {
    serverLogger.error(err, 'Failed to process client log');
    return new Response('Error', { status: 500 });
  }
}

5.2 服务端场景使用示例

  • API路由:
import { serverLogger } from '@your-monorepo/logging';
import { NextRequest, NextResponse } from 'next/server';

export async function GET(req: NextRequest) {
  serverLogger.info({ req: req }, 'API route called');
  try {
    // 业务逻辑
    return NextResponse.json({ data: 'success' });
  } catch (err) {
    serverLogger.error({ err, req }, 'API route failed');
    return NextResponse.json({ error: 'failed' }, { status: 500 });
  }
}
  • Server Components:
import { serverLogger } from '@your-monorepo/logging';

export default async function ServerComponent() {
  serverLogger.info('Server Component rendered');
  // 业务逻辑
  return <div>Server Component</div>;
}
  • Middleware:
import { serverLogger } from '@your-monorepo/logging';
import { NextRequest, NextResponse } from 'next/server';

export function middleware(req: NextRequest) {
  serverLogger.info({ nextReq: req }, 'Middleware triggered');
  return NextResponse.next();
}

5.3 客户端场景使用示例

在Client Components中使用:

'use client';

import { clientLogger } from '@your-monorepo/logging';

export default function ClientComponent() {
  const handleClick = () => {
    clientLogger.info('Button clicked');
    // 业务逻辑
  };

  return <button onClick={handleClick}>Click Me</button>;
}

6. 关键注意事项

  • 环境变量:客户端使用的日志级别需要加NEXT_PUBLIC_前缀,确保能被Next.js暴露到客户端。
  • 日志批量发送:客户端可以优化sendLogToServer,实现批量发送日志,减少API请求次数。
  • TypeScript路径配置:在Monorepo的根tsconfig.json中配置路径别名,方便引用共享包:
    {
      "compilerOptions": {
        "paths": {
          "@your-monorepo/logging": ["./packages/logging/src"]
        }
      }
    }
    
  • 日志级别一致性:尽量保持客户端和服务端的日志级别一致,避免日志遗漏或冗余。

内容的提问来源于stack exchange,提问作者Kavindu Wimalasooriya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 15:45:22