TanStack Start 可观测性实战指南:从 Sentry 集成到 OpenTelemetry 的完整监控方案

发布时间:2026/9/15 11:02:38
TanStack Start 可观测性实战指南:从 Sentry 集成到 OpenTelemetry 的完整监控方案 TanStack Start 可观测性实战指南从 Sentry 集成到 OpenTelemetry 的完整监控方案【免费下载链接】router A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router现代 Web 应用的可观测性Observability早已不是可选项而是保障线上稳定性、定位性能瓶颈与排查错误的必备能力。本指南以 TanStack Start基于 TanStack Router 的全栈框架为核心完整讲解如何在服务端函数、路由、中间件等关键执行点上建立日志、追踪、指标与错误上报体系并带你实操 Sentry、New Relic、OpenTelemetry 等外部监控工具的接入流程。读完本文你将掌握一套从零搭建、覆盖客户端与服务端的可观测性实战方案。本文对应的官方指南位于 docs/start/framework/react/guide/observability.md文中所有示例均可在仓库中对应的 e2e 测试与源码中找到佐证。TanStack Start 的可观测性全景TanStack Start 的架构为可观测性提供了天然的切入点。它是一个客户端优先、服务端可用client-first, server-capable的全栈框架基于 TanStack Router 提供路由与加载能力并通过createServerFn服务端函数、createMiddleware请求/函数中间件以及文件路由的server.handlers机制将客户端与服务端无缝衔接。正因如此可观测性可以被精确地织入以下三个关键执行点服务端函数Server Functions每次 RPC 调用的耗时、参数、返回值与异常路由 Loader 与渲染SSR 阶段的加载耗时、客户端水合后的渲染耗时请求中间件每个请求的入站/出站时间、状态码与错误信息。从源码结构看这些能力由 packages/react-start 及其底层 packages/start-client-core 提供。其中createMiddleware、createServerFn、createIsomorphicFn、createStart等核心 API 都被显式地 re-export 为tanstack/react-start的公共 API见 index.ts这意味着你在文档中看到的所有可观测性示例都能直接依赖这些稳定入口实现。首选方案接入 SentrySentry 是 TanStack 官方推荐的错误追踪与性能监控合作伙伴。在官方指南中Sentry 被重点推荐它提供实时错误追踪跨全栈捕获并调试错误性能监控追踪慢事务、定位瓶颈发布健康度监控部署、跟踪错误率随时间的变化用户影响分析理解错误如何影响用户TanStack Start 集成与服务端函数和客户端代码无缝协作。客户端初始化在客户端入口如app.tsx中初始化 Sentry// Client-side (app.tsx) import * as Sentry from sentry/react Sentry.init({ dsn: import.meta.env.VITE_SENTRY_DSN, environment: import.meta.env.NODE_ENV, })仓库中的 Sentry 集成示例e2e/react-router/sentry-integration/src/main.tsx展示了一个更完整的浏览器端初始化还额外启用了路由追踪集成Sentry.init({ dsn: https://examplePublicKeyo0.ingest.sentry.io/0, integrations: [Sentry.tanstackRouterBrowserTracingIntegration(router)], tracesSampleRate: 0.2, })其中tanstackRouterBrowserTracingIntegration(router)是 Sentry 与 TanStack Router 的路由级联追踪集成它需要传入通过createRouter创建的 router 实例。注意该 e2e 示例将tracesSampleRate设为0.2即 20% 采样率在生产环境中应根据流量合理调整采样率以平衡成本与覆盖度。对应的 e2e 测试app.spec.ts验证了带 Sentry 初始化的应用可以正常加载渲染说明该初始化方式与路由渲染管线兼容。服务端函数中的错误捕获服务端函数运行在 Node 侧应使用sentry/node// Server functions import * as Sentry from sentry/node const serverFn createServerFn().handler(async () { try { return await riskyOperation() } catch (error) { Sentry.captureException(error) throw error } })这里的关键点是捕获异常并上报后必须将错误继续抛出throw error否则客户端将无法感知失败调用方拿到的会是伪成功结果。createServerFn是 TanStack Start 服务端函数的唯一入口位于 packages/start-client-core/src/createServerFn.ts支持method、validator、handler等链式配置。零依赖的内置可观测性模式在不引入任何外部依赖的情况下TanStack Start 的架构本身就提供了多种可观测性机会。以下模式均可直接复制到你的项目中。服务端函数日志在服务端函数中记录执行、性能与错误import { createServerFn } from tanstack/react-start const getUser createServerFn({ method: GET }) .validator((id: string) id) .handler(async ({ data: id }) { const startTime Date.now() try { console.log([SERVER] Fetching user ${id}) const user await db.users.findUnique({ where: { id } }) if (!user) { console.log([SERVER] User ${id} not found) throw new Error(User not found) } const duration Date.now() - startTime console.log([SERVER] User ${id} fetched in ${duration}ms) return user } catch (error) { const duration Date.now() - startTime console.error( [SERVER] Error fetching user ${id} after ${duration}ms:, error, ) throw error } })这个模式把耗时测量与日志输出绑定在同一个执行路径上使每条日志都自带耗时上下文便于事后按耗时排序排查慢函数。console.error应保留标准错误输出格式方便日志采集器如 Docker 日志驱动、云厂商日志服务按流区分。请求/响应中间件使用createMiddleware().server()创建全量请求日志中间件import { createMiddleware } from tanstack/react-start const requestLogger createMiddleware().server(async ({ request, next }) { const startTime Date.now() const timestamp new Date().toISOString() console.log([${timestamp}] ${request.method} ${request.url} - Starting) try { const result await next() const duration Date.now() - startTime console.log( [${timestamp}] ${request.method} ${request.url} - ${result.response.status} (${duration}ms), ) return result } catch (error) { const duration Date.now() - startTime console.error( [${timestamp}] ${request.method} ${request.url} - Error (${duration}ms):, error, ) throw error } }) // Apply to all server routes export const Route createFileRoute(/api/users)({ server: { middleware: [requestLogger], handlers: { GET: async () { return Response.json({ users: await getUsers() }) }, }, }, })从源码看createMiddlewarepackages/start-client-core/src/createMiddleware.ts支持.server()、.client()、.middleware()、.validator()等链式方法默认类型为request。请求中间件通过server.handlers中声明的路由挂载是记录每个 API 路由出入站情况的理想位置。注意next()的返回值包含response字段可通过result.response.status读取最终状态码。路由性能监控在路由 Loader 中同时覆盖 SSR 与客户端两个阶段import { createFileRoute } from tanstack/react-router export const Route createFileRoute(/dashboard)({ loader: async ({ context }) { const startTime Date.now() try { const data await loadDashboardData() const duration Date.now() - startTime // Log server-side performance if (typeof window undefined) { console.log([SSR] Dashboard loaded in ${duration}ms) } return data } catch (error) { const duration Date.now() - startTime console.error([LOADER] Dashboard error after ${duration}ms:, error) throw error } }, component: Dashboard, }) function Dashboard() { const data Route.useLoaderData() // Track client-side render time React.useEffect(() { const renderTime performance.now() console.log([CLIENT] Dashboard rendered in ${renderTime}ms) }, []) return divDashboard content/div }核心技巧是使用typeof window undefined判断当前执行环境Loader 在 SSR 阶段运行于 Node 侧此时记录的是服务端数据加载耗时而useEffect只在客户端水合后执行记录的是客户端渲染耗时。两者结合即可获得路由完整的端到端加载画像。这也是 TanStack Router 文档与源码中普遍采用的同构环境判别写法。健康检查端点通过文件路由的server.handlers暴露/health端点// routes/health.ts import { createFileRoute } from tanstack/react-router export const Route createFileRoute(/health)({ server: { handlers: { GET: async () { const checks { status: healthy, timestamp: new Date().toISOString(), uptime: process.uptime(), memory: process.memoryUsage(), database: await checkDatabase(), version: process.env.npm_package_version, } return Response.json(checks) }, }, }, }) async function checkDatabase() { try { await db.raw(SELECT 1) return { status: connected, latency: 0 } } catch (error) { return { status: error, error: error.message } } }该模式让健康检查返回结构化 JSON涵盖进程运行时长、内存占用、数据库连通性与应用版本可直接对接 Kubernetes 探针、负载均衡器或 uptime 监控服务。checkDatabase使用SELECT 1探测数据库连通性是业界通行的轻量健康检查手法。错误边界客户端使用react-error-boundary捕获渲染错误服务端在函数内记录带上下文的错误// Client-side error boundary import { ErrorBoundary } from react-error-boundary function ErrorFallback({ error, resetErrorBoundary }: any) { // Log client errors console.error([CLIENT ERROR]:, error) // Could also send to external service // sendErrorToService(error) return ( div rolealert h2Something went wrong/h2 button onClick{resetErrorBoundary}Try again/button /div ) } export function App() { return ( ErrorBoundary FallbackComponent{ErrorFallback} Router / /ErrorBoundary ) } // Server function error handling const riskyOperation createServerFn().handler(async () { try { return await performOperation() } catch (error) { // Log server errors with context console.error([SERVER ERROR]:, { error: error.message, stack: error.stack, timestamp: new Date().toISOString(), // Add request context if available }) // Return user-friendly error throw new Error(Operation failed. Please try again.) } })关于错误边界的更多细节TanStack Start 提供了专门指南 docs/start/framework/react/guide/error-boundaries.md可与本文对照阅读。性能指标收集实现一个轻量级的内存指标收集器并通过/metrics端点暴露// utils/metrics.ts class MetricsCollector { private metrics new Mapstring, number[]() recordTiming(name: string, duration: number) { if (!this.metrics.has(name)) { this.metrics.set(name, []) } this.metrics.get(name)!.push(duration) } getStats(name: string) { const timings this.metrics.get(name) || [] if (timings.length 0) return null const sorted timings.sort((a, b) a - b) return { count: timings.length, avg: timings.reduce((a, b) a b, 0) / timings.length, p50: sorted[Math.floor(sorted.length * 0.5)], p95: sorted[Math.floor(sorted.length * 0.95)], min: sorted[0], max: sorted[sorted.length - 1], } } getAllStats() { const stats: Recordstring, any {} for (const [name] of this.metrics) { stats[name] this.getStats(name) } return stats } } export const metrics new MetricsCollector() // Metrics endpoint // routes/metrics.ts export const Route createFileRoute(/metrics)({ server: { handlers: { GET: async () { return Response.json({ system: { uptime: process.uptime(), memory: process.memoryUsage(), timestamp: new Date().toISOString(), }, application: metrics.getAllStats(), }) }, }, }, })该收集器按名称聚合耗时采样计算avg、p50、p95、min、max等统计量。p95 是定位长尾延迟的关键指标当 p50 正常而 p95 明显偏高时说明存在少数慢请求拖累整体体验。注意该实现为内存存储进程重启后数据丢失生产环境建议将指标转发到 Prometheus 等外部存储。开发环境调试头在响应中注入调试信息仅在开发环境生效import { createMiddleware } from tanstack/react-start const debugMiddleware createMiddleware().server(async ({ next }) { const result await next() if (process.env.NODE_ENV development) { result.response.headers.set(X-Debug-Timestamp, new Date().toISOString()) result.response.headers.set(X-Debug-Node-Version, process.version) result.response.headers.set(X-Debug-Uptime, process.uptime().toString()) } return result })通过浏览器 DevTools 的网络面板即可查看这些X-Debug-*响应头无需打开终端即可快速确认请求是否命中、Node 版本与进程运行时长是开发期排障的轻量利器。环境感知的日志系统使用createIsomorphicFn构建同构日志器实现开发/生产差异化策略// utils/logger.ts import { createIsomorphicFn } from tanstack/react-start type LogLevel debug | info | warn | error const logger createIsomorphicFn() .server((level: LogLevel, message: string, data?: any) { const timestamp new Date().toISOString() if (process.env.NODE_ENV development) { // Development: Detailed console logging consolelevel}], message, data) } else { // Production: Structured JSON logging console.log( JSON.stringify({ timestamp, level, message, data, service: tanstack-start, environment: process.env.NODE_ENV, }), ) } }) .client((level: LogLevel, message: string, data?: any) { if (process.env.NODE_ENV development) { consolelevel}], message, data) } else { // Production: Send to analytics service // analytics.track(client_log, { level, message, data }) } }) // Usage anywhere in your app export { logger } // Example usage const fetchUserData createServerFn().handler(async ({ data: userId }) { logger(info, Fetching user data, { userId }) try { const user await db.users.findUnique({ where: { id: userId } }) logger(info, User data fetched successfully, { userId }) return user } catch (error) { logger(error, Failed to fetch user data, { userId, error: error.message, }) throw error } })createIsomorphicFn的核心价值在于同一段逻辑可以为服务端和客户端分别提供实现。生产环境服务端输出结构化 JSON 日志便于日志平台解析客户端则只上报分析服务避免在用户浏览器控制台输出无关信息。结构化日志的字段timestamp/level/message/data可直接对接 ELK、Loki 等日志系统。轻量错误上报不依赖外部服务的内存错误聚合器// utils/error-reporter.ts const errorStore new Map string, { count: number; lastSeen: Date; error: any } () export function reportError(error: Error, context?: any) { const key ${error.name}:${error.message} const existing errorStore.get(key) if (existing) { existing.count existing.lastSeen new Date() } else { errorStore.set(key, { count: 1, lastSeen: new Date(), error: { name: error.name, message: error.message, stack: error.stack, context, }, }) } // Log immediately console.error([ERROR REPORTED]:, { error: error.message, count: existing ? existing.count : 1, context, }) } // Error reporting endpoint // routes/errors.ts export const Route createFileRoute(/admin/errors)({ server: { handlers: { GET: async () { const errors Array.from(errorStore.entries()).map(([key, data]) ({ id: key, ...data, })) return Response.json({ errors }) }, }, }, })该模式以错误名:错误消息为键做聚合自动统计相同错误的出现次数与最后出现时间并通过/admin/errors端点提供查询入口。对中小项目而言这是接入完整监控平台前最经济的过渡方案。外部可观测性工具生态内置模式覆盖基础场景而外部工具提供更全面的监控能力。官方指南将主流工具分为三类APM 应用性能监控DataDog全栈监控 APM、New Relic性能监控与告警、Honeycomb面向复杂系统的可观测性错误追踪Bugsnag错误监控 部署追踪、Rollbar实时错误告警分析与用户行为PostHog产品分析 错误追踪、Mixpanel事件追踪与用户分析。New Relic 集成SSR大多数 APM 工具的接入模式类似以 New Relic 为例SSR 场景需要三步第一步在 New Relic 创建Node类型的 integration获得 license key并创建代理配置文件// newrelic.js - New Relic agent configuration exports.config { app_name: [YourTanStackApp], // Your application name in New Relic license_key: YOUR_NEW_RELIC_LICENSE_KEY, // Your New Relic license key agent_enabled: true, distributed_tracing: { enabled: true }, span_events: { enabled: true }, transaction_events: { enabled: true }, // Additional default settings }第二步通过自定义 handler 包装 SSR 处理器将事务按路由 ID 分组而不是按唯一的 URL 分组这样监控面板上聚合度更高并附加自定义属性// server.tsx import newrelic from newrelic // Make sure this is the first import import { createStartHandler, defaultStreamHandler, defineHandlerCallback, } from tanstack/react-start/server import type { ServerEntry } from tanstack/react-start/server-entry const customHandler defineHandlerCallback(async (ctx) { // We do this so that transactions are grouped under the route ID instead of unique URLs const matches ctx.router?.state?.matches ?? [] const leaf matches[matches.length - 1] const routeId leaf?.routeId ?? new URL(ctx.request.url).pathname newrelic.setControllerName(routeId, ctx.request.method ?? GET) newrelic.addCustomAttributes({ route.id: routeId, http.method: ctx.request.method, http.path: new URL(ctx.request.url).pathname, // Any other custom attributes you want to add }) return defaultStreamHandler(ctx) }) export default { fetch(request) { const handler createStartHandler(customHandler) return handler(request) }, } satisfies ServerEntry这里利用了 TanStack Router 的匹配结果ctx.router.state.matches取最后一个匹配路由的routeId作为事务分组名避免动态 URL如/user/123、/user/456在监控面板上产生海量事务条目。newrelic的 import 必须放在文件第一行确保代理最先加载。底层实现中createStartHandler、defaultStreamHandler等 SSR 处理器由 packages/react-start-server 提供。第三步通过 Node 的-r预加载参数启动服务端产物node -r newrelic .output/server/index.mjsNew Relic 集成服务端函数与路由如果需要监控服务端函数和服务端路由在上述步骤基础上将 New Relic 接入请求中间件与createStart实例// newrelic-middleware.ts import newrelic from newrelic import { createMiddleware } from tanstack/react-start export const nrTransactionMiddleware createMiddleware().server( async ({ request, next }) { const reqPath new URL(request.url).pathname newrelic.setControllerName(reqPath, request.method ?? GET) return await next() }, )// start.ts import { createStart } from tanstack/react-start import { nrTransactionMiddleware } from ./newrelic-middleware export const startInstance createStart(() { return { requestMiddleware: [nrTransactionMiddleware], } })createStart的配置选项定义在 packages/start-client-core/src/createStart.ts其中requestMiddleware接收一个中间件数组注册后会对所有请求生效。将 New Relic 的setControllerName放进请求中间件即可让服务端函数与路由的每个请求都获得 New Relic 事务分组。New Relic 集成SPA 与浏览器创建React类型的 New Relic integration 后将官方提供的集成脚本注入根路由的head// __root.tsx export const Route createRootRoute({ head: () ({ scripts: [ { id: new-relic, // either copy/paste your New Relic integration script here children: ..., // or you can create it in your public folder and then reference it here src: /newrelic.js, }, ], }), })两种方式二选一直接内联脚本内容或将脚本放到public目录后通过src引用。OpenTelemetry 集成实验性OpenTelemetry 是可观测性的行业标准。官方指南给出了一个实验性接入方案它需要手动初始化 SDK 并在关键路径上创建 Span。第一步在应用启动前初始化 Node SDK自动插桩// instrumentation.ts - Initialize before your app import { NodeSDK } from opentelemetry/sdk-node import { getNodeAutoInstrumentations } from opentelemetry/auto-instrumentations-node import { Resource } from opentelemetry/resources import { SemanticResourceAttributes } from opentelemetry/semantic-conventions const sdk new NodeSDK({ resource: new Resource({ [SemanticResourceAttributes.SERVICE_NAME]: tanstack-start-app, [SemanticResourceAttributes.SERVICE_VERSION]: 1.0.0, }), instrumentations: [getNodeAutoInstrumentations()], }) // Initialize BEFORE importing your app sdk.start()第二步在服务端函数中创建手动 Span// Server function tracing import { trace, SpanStatusCode } from opentelemetry/api const tracer trace.getTracer(tanstack-start) const getUserWithTracing createServerFn({ method: GET }) .validator((id: string) id) .handler(async ({ data: id }) { return tracer.startActiveSpan(get-user, async (span) { span.setAttributes({ user.id: id, operation: database.query, }) try { const user await db.users.findUnique({ where: { id } }) span.setStatus({ code: SpanStatusCode.OK }) return user } catch (error) { span.recordException(error) span.setStatus({ code: SpanStatusCode.ERROR, message: error.message, }) throw error } finally { span.end() } }) })第三步创建自动追踪中间件为每个请求生成 Span// Middleware for automatic tracing import { createMiddleware } from tanstack/react-start import { trace, SpanStatusCode } from opentelemetry/api const tracer trace.getTracer(tanstack-start) const tracingMiddleware createMiddleware().server( async ({ next, request }) { const url new URL(request.url) return tracer.startActiveSpan( ${request.method} ${url.pathname}, async (span) { span.setAttributes({ http.method: request.method, http.url: request.url, http.route: url.pathname, }) try { const result await next() span.setAttribute(http.status_code, result.response.status) span.setStatus({ code: SpanStatusCode.OK }) return result } catch (error) { span.recordException(error) span.setStatus({ code: SpanStatusCode.ERROR, message: error.message, }) throw error } finally { span.end() } }, ) }, )注意上述 OpenTelemetry 集成是实验性的需要手动搭建。官方正在探索一等公民的 OpenTelemetry 支持届时将自动为服务端函数、中间件与路由 Loader 提供插桩。通用快速集成模式大多数可观测性工具与 TanStack Start 的集成遵循相同模式在应用入口初始化工具在中间件中包裹追踪与错误捕获。// Initialize in app entry point import { initObservabilityTool } from your-tool initObservabilityTool({ dsn: import.meta.env.VITE_TOOL_DSN, environment: import.meta.env.NODE_ENV, }) // Server function middleware const observabilityMiddleware createMiddleware().handler(async ({ next }) { return yourTool.withTracing(server-function, async () { try { return await next() } catch (error) { yourTool.captureException(error) throw error } }) })掌握这个模式后接入任意同类工具都只是替换 API 名的问题。最佳实践清单开发与生产差异化// Different strategies per environment const observabilityConfig { development: { logLevel: debug, enableTracing: true, enableMetrics: false, // Too noisy in dev }, production: { logLevel: warn, enableTracing: true, enableMetrics: true, enableAlerting: true, }, }开发环境开启 debug 级别日志与追踪便于排障但关闭指标采集以避免噪声生产环境收紧日志级别、开启指标与告警。性能监控检查清单服务端函数性能追踪执行耗时路由加载耗时监控 Loader 性能数据库查询性能记录慢查询外部 API 延迟监控第三方服务调用内存占用跟踪内存消耗模式错误率监控错误频率与类型安全注意事项绝不记录敏感数据密码、令牌、PII使用结构化日志便于解析生产环境实施日志轮转考虑合规要求GDPR、CCPA。未来展望一等公民的 OpenTelemetry 支持官方指南明确指出TanStack Start 正在推进直接的 OpenTelemetry 支持届时将无需手动插桩即可自动为服务端函数、中间件和路由 Loader 提供埋点。对开发者而言当前基于createMiddleware与createServerFn的手动插桩方式仍是最稳妥的过渡方案——它完全基于公共 API 构建未来迁移成本可控。资源导航Sentry 官方集成示例e2e/react-router/sentry-integration含 main.tsx 初始化代码与 app.spec.ts e2e 测试核心公共 API 源码packages/react-start/src/index.ts、packages/start-client-core/src/createMiddleware.ts、packages/start-client-core/src/createStart.tsSSR 处理器实现packages/react-start-server/src/index.tsx错误边界专项指南docs/start/framework/react/guide/error-boundaries.md服务端函数与路由专项指南docs/start/framework/react/guide/server-functions.md、docs/start/framework/react/guide/server-routes.md。本文所有内置模式均基于 TanStack Start 的公开 API 构建无需修改框架源码即可落地外部工具Sentry、New Relic、OpenTelemetry接入后可在此基础上升级为完整的生产级可观测性体系。【免费下载链接】router A client-first, server-capable, fully type-safe router and full-stack framework for the web (React and more).项目地址: https://gitcode.com/GitHub_Trending/ro/router创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考