☰
Next.js 分布式追踪实战:基于 Highlight 的 W3C Trace Context 全链路接入与源码解析
2026/9/25 3:12:02 网站建设 项目流程
  • 可观测性
  • 后端

【免费下载链接】highlight

highlight.io: The open source, full-stack monitoring platform. Error monitoring, session replay, logging, distributed tracing, and more.

项目地址:https://gitcode.com/gh_mirrors/hi/highlight
点击查看免费下载

本篇指南围绕 Next.js 应用中分布式追踪(Distributed Tracing)的完整落地展开:从浏览器客户端注入追踪上下文、Next.js 服务端中间件传播 W3C Trace Context 头,到下游微服务接收 traceparent 并关联同一 Trace,最终在 Highlight 平台中可视化跨服务的瀑布流。读完本文,你将掌握 Highlight Next.js SDK 与 Node/Go SDK 的接入方式、withHighlightConfig各配置项的默认行为,以及propagation.extract/inject在源码中如何串联起一次完整的跨服务调用链。

什么是分布式追踪

分布式追踪用于理解和监控跨多个服务运行的复杂应用的执行情况:它把分散在不同服务中的操作(span)链接进同一条 trace,从而更容易定位性能瓶颈和排查问题。

在 Next.js 场景下,这条链路通常覆盖三段:

  1. 浏览器端:用户交互、页面渲染产生的操作;
  2. Next.js 服务端:middleware、API 路由、服务端组件中的业务逻辑;
  3. 下游微服务:Next.js 的 API 路由发起的 HTTP 调用(例如 Go 微服务)。

要实现三者关联,关键在于追踪上下文(trace context)的跨进程传播。Highlight 采用的是 W3C 标准traceparent/tracestate头(由 OpenTelemetry 的W3CTraceContextPropagator实现),此外还携带一个 Highlight 特有的x-highlight-request头,用于把服务端操作关联到触发该请求的用户会话(Session Replay)。

整体数据流

从源码结构看,完整的数据流如下:

浏览器 (highlight.run) │ 1. 发出 fetch 请求,自动携带 W3C trace context 头 │ 2. 初始化时把 sessionSecureID 写入 cookie ▼ Next.js middleware (highlightMiddleware) │ 3. 读取 cookie,补写 x-highlight-request 头 ▼ API 路由 (withHighlight / runWithHeaders) │ 4. propagation.extract 提取入站上下文 → 创建服务端 span │ 5. propagation.inject 把 traceparent 注入出站请求头 ▼ Go 微服务 (chi middleware + Go OTel SDK) 6. 提取 traceparent,挂到同一条 trace,输出 OTLP

第一步:客户端安装与初始化

在 Next.js 项目中安装 Highlight 客户端 SDK:

yarn add @highlight-run/next

在 App Router 的app/layout.tsx(或 Pages Router 的_app.tsx)中渲染HighlightInit组件:

import { HighlightInit } from '@highlight-run/next/client' export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <HighlightInit projectId="your-project-id" excludedHostnames={['localhost']} /> {children} </body> </html> ) }

客户端初始化完成后会自动为浏览器发出的请求注入追踪头。看 客户端入口源码 可以发现两件对追踪链路很关键的事:

  • 会话 Cookie:localH.init返回的sessionSecureID会被写入名为sessionSecureID的 cookie(next-client.tsx#L42-L49)。这是后续服务端把操作关联回会话回放(Session Replay)的基础。
  • 代理模式:如果构建期注入了configureHighlightProxy === 'true'(默认开启,见下文withHighlightConfig),初始化选项会被改写为backendUrl: '/highlight-events'和otlpEndpoint: window.location.origin(next-client.tsx#L32-L40)。也就是说,浏览器上报的 OTLP 数据(/v1/traces、/v1/metrics、/v1/logs)会走你自己的域名,由 Next.js 代理转发到 Highlight,避免跨域与混合内容问题。

第二步:服务端 middleware 传播请求上下文

客户端 cookie 只是标识,真正把追踪头“补齐”并交给后续逻辑的是 Next.js 的middleware.ts:

// middleware.ts import { highlightMiddleware } from '@highlight-run/next/server' import { NextResponse } from 'next/server' export async function middleware(request: Request) { await highlightMiddleware(request) return NextResponse.next() }

仓库中的 e2e 示例 e2e/nextjs/middleware.ts 与上面完全一致。highlightMiddleware的实现非常轻量(highlight-middleware.ts#L3-L9):

export async function highlightMiddleware(request: Request) { const sessionSecureID = (await cookies()).get('sessionSecureID')?.value const xHighlightRequest = request.headers.get('x-highlight-request') if (!xHighlightRequest && sessionSecureID) { request.headers.set('x-highlight-request', `${sessionSecureID}/`) } }

它从 cookie 取出sessionSecureID,在请求头不存在x-highlight-request时补写一个${sessionSecureID}/格式的值。这个头会一路传到 API 路由,服务端 SDK 据此解析出secureSessionId和requestId(见 client.ts 的 parseHeaders),把 span 打上highlight.session_id属性,从而在后端日志、错误和 trace 与前端会话回放之间建立关联。

第三步:withHighlightConfig 与 OTLP 代理配置

为了让浏览器与 Next.js 服务端能上报 OTLP 数据,Next.js 需要用withHighlightConfig包装配置文件。它会自动在rewrites中追加以下代理规则(源码见 with-highlight-config.ts#L141-L158):

sourcedestination用途
/highlight-eventshttps://pub.highlight.io事件/会话等通用上报代理
/v1/traceshttps://otel.highlight.io/v1/tracesOTLP trace 上报代理
/v1/metricshttps://otel.highlight.io/v1/metricsOTLP metric 上报代理
/v1/logshttps://otel.highlight.io/v1/logsOTLP log 上报代理

配置示例:

// next.config.mjs import { withHighlightConfig } from '@highlight-run/next/config' /** @type {import('next').NextConfig} */ const nextConfig = {} export default withHighlightConfig(nextConfig, { uploadSourceMaps: false, // 是否上传 source map(默认:生产构建且配置了 apiKey 时为 true) configureHighlightProxy: true, // 是否注入 /highlight-events、/v1/* 代理重写(默认 true) apiKey: process.env.HIGHLIGHT_SOURCEMAP_UPLOAD_API_KEY, environment: process.env.ENVIRONMENT, serviceName: 'my-nextjs-app', })

各选项的默认值可以直接从 getDefaultOpts 的源码确认:

选项默认值说明
uploadSourceMaps生产构建(NODE_ENV === 'production')且配置了apiKey或HIGHLIGHT_SOURCEMAP_UPLOAD_API_KEY时为true启用后会改写 webpackdevtool并注入上传插件,同时把/:path*.map重写为 404 以防 source map 泄漏
configureHighlightProxytrue注入上表中的 4 条代理 rewrite,并把configureHighlightProxy作为构建期环境变量暴露给客户端代码
apiKey''上传 source map 时关联项目用的 API key,也可用环境变量HIGHLIGHT_SOURCEMAP_UPLOAD_API_KEY提供
appVersion优先取 NextgenerateBuildId()的结果上传 source map 时使用的版本标识
environment''环境标识,用于区分 localhost 与生产环境的会话
serviceName''应用名
sourceMapsPath'.next/'source map 文件根目录
sourceMapsBasePath'_next/'上传后 source map URL 前缀
sourceMapsBackendUrl未设置(自托管部署时可选)自托管 Highlight 部署的后端地址

需要注意:withHighlightConfig兼容next.config的三种形态(对象、同步函数、异步函数),见 withHighlightConfig 入口。

第四步:API 路由的追踪包装与 W3C 上下文传播

Next.js 的 API 路由(以及 Route Handler)需要用 Node SDK 的 handler 包装,让每个请求都运行在一个从入站头提取出来的 span 上下文里。仓库 e2e 中封装好的用法见 e2e/nextjs/pages/api/page-router-trace.ts:

import { withPageRouterHighlight } from '@/app/_utils/page-router-highlight.config' import { H } from '@highlight-run/next/server' import { context, propagation } from '@opentelemetry/api' export default withPageRouterHighlight(async function handler( req: NextApiRequest, res: NextApiResponse, ) { const { span } = H.startWithHeaders('page-router-span', {}) const headers = {} // 1) 手动复制入站头(含 W3C traceparent)转发给 Go 服务 await fetch('http://localhost:3010/x-highlight-request', { method: 'GET', headers: Object.entries(req.headers).reduce( (acc, [key, value]) => ({ ...acc, [key, value ]: value }), {}, ), }) // 2) 用 OTel propagation API 把当前活跃 span 注入出站头 propagation.inject(context.active(), headers) await fetch('http://localhost:3010/traceparent', { method: 'GET', headers }) res.send('Trace sent!') span.end() })

包装器内部做的事情,本质上是 Node SDK 的runWithHeaders。从 client.ts#L425-L475 的实现可以看到完整的 W3C Trace Context 传播闭环:

async runWithHeaders<T>(name, headers, cb, options?) { const { span, ctx } = this.startWithHeaders(name, headers, options) return await api.context.with(ctx, async () => { propagation.inject(ctx, headers) // 注入 traceparent,供出站请求继承 try { return await cb(span) } catch (error) { span.recordException(error) throw error } finally { span.end() } }) } startWithHeaders(spanName, headers, options?) { const ctx = propagation.extract(api.context.active(), headers) // 1. 提取入站上下文 const span = this.tracer.startSpan(spanName, options, ctx) // 2. 派生子 span const contextWithSpanSet = api.trace.setSpan(ctx, span) let { secureSessionId, requestId } = this.parseHeaders(headers) // 3. 解析 x-highlight-request // ...将 secureSessionId 写入 span 属性 highlight.session_id 并放入 baggage propagation.inject(contextWithSpanSet, headers) // 4. 注入更新后的上下文 return { span, ctx: contextWithSpanSet } }

而W3CTraceContextPropagator正是该 SDK 注册的传播器(见 client.ts#L221-L237 的 provider 配置),因此propagation.extract/inject读写的就是标准traceparent头。这条链路解释了文章开头所说的机制:服务端从入站请求提取 trace 头,创建属于当前请求的 span,再把更新后的上下文注入到所有出站请求头中——下游服务只要解析traceparent,就能把自身操作挂到同一条 trace 上,同时仍可为任意代码块开启新的嵌套 span。

对于 Express 风格的框架,Node SDK 还提供了通用的 middleware 与 errorHandler,例如Handlers.middleware(options)会在每个请求上调用H.runWithHeaders('GET - /url', req.headers, ...),并自动附上http.request.method、http.route、http.response.status_code等语义约定属性;Handlers.errorHandler则通过parseHeaders把错误关联回对应的会话与请求(handlers.ts#L23-L42)。

第五步:下游微服务接收 traceparent

以仓库中的 Go 示例服务为例(e2e/nextjs/go-service/main.go),Highlight 的 Go SDK 基于 OpenTelemetry Go 模块,初始化如下:

highlight.SetDebugMode(logger) highlight.SetOTLPEndpoint("http://localhost:4318") highlight.Start( highlight.WithServiceName("my-go-service"), highlight.WithProjectID("1"), highlight.WithSamplingRateMap(map[trace.SpanKind]float64{ trace.SpanKindServer: 1., }), ) defer highlight.Stop()

Go 服务用对应框架的中间件接收上下文,SDK 在sdk/highlight-go/middleware/下为多个常见框架提供了开箱即用的实现:

  • chi:func Middleware(next http.Handler) http.Handler
  • echo
  • fiber
  • gin
  • gorillamux

以 chi 为例,在路由注册前先套一层即可:

r.Use(highlightChi.Middleware)

中间件内部使用 OTel 的propagation包从入站头提取traceparent,使 Go 服务创建的 server span 直接成为 Next.js span 的子 span。从源码结构看,Go SDK 还在 highlight.go#L90-L108 中定义了自己的 context key(HighlightRequestID、HighlightSessionSecureID),用于在服务内跨层传递 Highlight 会话与请求标识,实现与x-highlight-request头相同的会话关联能力。

可用的初始化选项(均为 functional options 风格,见 highlight.go#L49-L88)包括:WithProjectID、WithServiceName、WithServiceVersion、WithEnvironment、WithSamplingRate与WithSamplingRateMap(可按trace.SpanKind精细控制采样率)。

端到端可视化:瀑布流视图

完成上述接入后,一次请求就会产生一条跨三段的 trace:浏览器操作 → Next.js 服务端 span → Go 服务 span。在 Highlight 的 Trace 视图中,瀑布流(waterfall)会清晰展示每个 span 的时间窗口与父子关系,可以逐层展开定位是哪一段服务、哪一个操作耗时最长。由于会话 cookie 关联,你还可以从 Trace 直接跳转回触发该请求的用户会话回放。

与 OpenTelemetry、W3C 标准的关系

需要强调两点,这也是 Highlight 选择这套方案的原因:

  1. W3C Trace Context 是开放标准:traceparent/tracestate头的传播方式与任何可观测性厂商的追踪工具兼容,接入 Highlight 不会造成供应商锁定——同一批头可以被 Jaeger、Zipkin、Honeycomb 等任何遵循该标准的基础设施消费。
  2. OpenTelemetry 提供了更厂商中立的 SDK 层:Highlight 的 Node SDK(@highlight-run/node)与 Go SDK 都构建在官方 OpenTelemetry 包之上(如 Node 端依赖@opentelemetry/api、@opentelemetry/propagator-b3体系中的W3CTraceContextPropagator,Go 端依赖go.opentelemetry.io/otel全家桶,见 go.mod)。这意味着你可以复用 OTel 的 API、语义约定和生态中间件,同时把数据上报到 Highlight 的 OTLP 端点。

关键文件索引

内容路径
客户端初始化组件(cookie、代理模式)sdk/highlight-next/src/next-client.tsx
服务端 middleware(x-highlight-request)sdk/highlight-next/src/util/highlight-middleware.ts
next.config 包装与 OTLP 代理重写sdk/highlight-next/src/util/with-highlight-config.ts
W3C 上下文提取/注入(runWithHeaders)sdk/highlight-node/src/client.ts
Express/serverless handlersdk/highlight-node/src/handlers.ts
Go SDK 初始化与选项sdk/highlight-go/highlight.go
Go 框架中间件(chi/echo/fiber/gin/gorillamux)sdk/highlight-go/middleware/
e2e 完整示例(Next.js + Go 服务)e2e/nextjs/middleware.ts、e2e/nextjs/pages/api/page-router-trace.ts、e2e/nextjs/go-service/main.go

小结

在 Next.js 中落地分布式追踪的核心是“提取—注入”这一对称操作:客户端 SDK 为请求自动附加追踪头并维护会话 cookie;highlightMiddleware把会话标识补齐到请求头;withHighlightConfig配置 OTLP 代理;API 路由经runWithHeaders/startWithHeaders从traceparent提取上下文、创建子 span 并注入出站头;Go 微服务再用标准中间件提取上下文挂入同一条 trace。整条链路只依赖 W3C Trace Context 开放标准,既能在 Highlight 中获得端到端瀑布流与会话回放的联动,也能被任何兼容 OTel 的基础设施消费。

  • 可观测性
  • 后端

【免费下载链接】highlight

highlight.io: The open source, full-stack monitoring platform. Error monitoring, session replay, logging, distributed tracing, and more.

项目地址:https://gitcode.com/gh_mirrors/hi/highlight
点击查看免费下载

相关推荐

上一篇:告别乱码!5款Powerline字体打造完美终端显示效果
下一篇:react-redux-typescript-guide未来展望:TypeScript与React生态系统发展趋势

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询