TanStack Start 中文文档
部署与运维

可观测性(Observability)

可观测性(Observability)是现代 Web 开发的关键方面,让你能够监控、追踪和调试应用的性能与错误。TanStack Start 为可观测性提供了内置模式,并与外部工具无缝集成,为你的应用提供全面的洞察。

合作伙伴方案:Sentry

Sentry

对于全面的可观测性,我们推荐 Sentry——我们值得信赖的错误追踪与性能监控合作伙伴。Sentry 提供:

  • 实时错误追踪——捕获并调试你整个技术栈的错误
  • 性能监控——追踪慢事务并优化瓶颈
  • 发布健康度(Release Health)——监控部署情况并随时间追踪错误率
  • 用户影响分析——了解错误如何影响你的用户
  • TanStack Start 集成——与服务器函数和客户端代码无缝配合

快速设置:

// 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,
})

// Server functions
import * as Sentry from '@sentry/node'

const serverFn = createServerFn().handler(async () => {
  try {
    return await riskyOperation()
  } catch (error) {
    Sentry.captureException(error)
    throw error
  }
})

开始使用 Sentry → | 查看集成示例 →

内置可观测性模式

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
    }
  })

请求/响应中间件

创建中间件来记录所有请求和响应:

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() })
      },
    },
  },
})

路由性能监控

在客户端和服务端追踪路由加载性能:

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 <div>Dashboard content</div>
}

健康检查端点

创建用于健康监控的服务器路由:

// 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 }
  }
}

错误边界

实现全面的错误处理:

// 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 role="alert">
      <h2>Something 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.')
  }
})

性能指标收集

收集并暴露基础性能指标:

// utils/metrics.ts
class MetricsCollector {
  private metrics = new Map<string, 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: Record<string, 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(),
        })
      },
    },
  },
})

开发用的调试响应头

向响应添加有用的调试信息:

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
})

按环境区分的日志

为开发和生产配置不同的日志策略:

// 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
      console[level](`[${timestamp}] [${level.toUpperCase()}]`, 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') {
      console[level](`[CLIENT] [${level.toUpperCase()}]`, 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
  }
})

简单的错误上报

不依赖外部服务的错误上报:

// 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 })
      },
    },
  },
})

外部可观测性工具

虽然 TanStack Start 提供了内置的可观测性模式,但外部工具能提供更全面的监控:

其他流行工具

应用性能监控(APM):

错误追踪:

  • Bugsnag——带部署追踪的错误监控
  • Rollbar——实时错误告警

分析与用户行为:

  • PostHog——带错误追踪的产品分析
  • Mixpanel——事件追踪与用户分析

New Relic 集成

New Relic 是一个流行的应用性能监控工具。下面是如何把它集成到 TanStack Start。

SSR

要为服务端渲染启用 New Relic,你需要做以下事情:

在 New Relic 上创建一个 Node 类型的新集成。你会得到一个许可证密钥(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
}
// 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
node -r newrelic .output/server/index.mjs

服务器函数与服务器路由

如果你想为服务器函数和服务器路由添加监控,需要按照上面的步骤操作,然后添加以下内容:

// 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],
  }
})

SPA 与浏览器

在 New Relic 上创建一个 React 类型的新集成。

设置好之后,你需要把 New Relic 提供给你的集成脚本添加到根路由。

// __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',
      },
    ],
  }),
})

OpenTelemetry 集成(实验性)

OpenTelemetry 是可观测性的行业标准。下面是一个把它集成到 TanStack Start 的实验性方法:

// 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()
// 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()
      }
    })
  })
// 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 的一等公民支持,那将为服务器函数、中间件和路由加载器提供自动插桩(automatic instrumentation)。

快速集成模式

大多数可观测性工具与 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
    }
  })
})

最佳实践

开发 vs 生产

// 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,
  },
}

性能监控检查清单

  • 服务器函数性能:追踪执行时间
  • 路由加载时间:监控加载器性能
  • 数据库查询性能:记录慢查询
  • 外部 API 延迟:监控第三方服务调用
  • 内存使用:追踪内存消耗模式
  • 错误率:监控错误频率和类型

安全考量

  • 绝不记录敏感数据(密码、令牌、个人身份信息)
  • 使用结构化日志,便于解析
  • 在生产环境实现日志轮转
  • 考虑合规要求(GDPR、CCPA)

未来的 OpenTelemetry 支持

TanStack Start 即将提供对 OpenTelemetry 的直接支持,它将为服务器函数、中间件和路由加载器提供自动插桩,无需上面展示的手动设置。

资源

On this page