관측 가능성
관측 가능성은 최신 웹 개발의 핵심 요소로, 애플리케이션의 성능과 오류를 모니터링하고 추적하며 디버깅할 수 있게 합니다. TanStack Start는 관측 가능성을 위한 기본 제공 패턴을 제공하며 외부 도구와 원활하게 통합되어 애플리케이션에 대한 포괄적인 인사이트를 제공합니다.
파트너 솔루션: Sentry
포괄적인 관측 가능성을 위해 오류 추적 및 성능 모니터링 분야의 신뢰할 수 있는 파트너인 Sentry를 권장합니다. Sentry는 다음 기능을 제공합니다:
- 실시간 오류 추적 - 전체 스택에서 오류를 포착하고 디버깅합니다
- 성능 모니터링 - 느린 트랜잭션을 추적하고 병목 지점을 최적화합니다
- 릴리스 상태 - 배포를 모니터링하고 시간 경과에 따른 오류율을 추적합니다
- 사용자 영향 분석 - 오류가 사용자에게 미치는 영향을 파악합니다
- TanStack Start 통합 - 서버 함수 및 클라이언트 코드와 원활하게 연동됩니다
빠른 설정:
// Client-side (app.tsx)
import * as Sentry from '@sentry/solid'
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
}
})
기본 제공 관측 가능성 패턴
TanStack Start의 아키텍처는 외부 종속성 없이 기본적으로 관측 가능성을 확보할 수 있는 여러 방법을 제공합니다:
서버 함수 로깅
실행, 성능 및 오류를 추적할 수 있도록 서버 함수에 로깅을 추가합니다:
import { createServerFn } from '@tanstack/solid-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/solid-start'
const requestLogger = createMiddleware().handler(async ({ next }) => {
const startTime = Date.now()
const timestamp = new Date().toISOString()
console.log(`[${timestamp}] ${request.method} ${request.url} - Starting`)
try {
const response = await next()
const duration = Date.now() - startTime
console.log(
`[${timestamp}] ${request.method} ${request.url} - ${response.status} (${duration}ms)`,
)
return response
} 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/solid-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
Solid.createEffect(() => {
const renderTime = performance.now()
console.log(`[CLIENT] Dashboard rendered in ${renderTime}ms`)
})
return <div>Dashboard content</div>
}
상태 확인 엔드포인트
상태 모니터링을 위한 서버 라우트를 생성합니다:
// routes/health.ts
import { createFileRoute } from '@tanstack/solid-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 'solid-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/solid-start'
const debugMiddleware = createMiddleware().handler(async ({ next }) => {
const response = await next()
if (process.env.NODE_ENV === 'development') {
response.headers.set('X-Debug-Timestamp', new Date().toISOString())
response.headers.set('X-Debug-Node-Version', process.version)
response.headers.set('X-Debug-Uptime', process.uptime().toString())
}
return response
})
환경별 로깅
개발 환경과 프로덕션 환경에 서로 다른 로깅 전략을 구성합니다:
// utils/logger.ts
import { createIsomorphicFn } from '@tanstack/solid-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는 기본 제공 관측 가능성 패턴을 제공하지만, 외부 도구는 더 포괄적인 모니터링을 제공합니다:
기타 인기 도구
애플리케이션 성능 모니터링:
오류 추적:
분석 및 사용자 행동:
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/solid-start'
import { trace, SpanStatusCode } from '@opentelemetry/api'
const tracer = trace.getTracer('tanstack-start')
const tracingMiddleware = createMiddleware().handler(
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 response = await next()
span.setAttribute('http.status_code', response.status)
span.setStatus({ code: SpanStatusCode.OK })
return response
} catch (error) {
span.recordException(error)
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
})
throw error
} finally {
span.end()
}
},
)
},
)
참고: 위 OpenTelemetry 통합은 실험적이며 수동 설정이 필요합니다. 서버 함수, 미들웨어 및 라우트 로더를 자동으로 계측하는 일급 OpenTelemetry 지원을 검토하고 있습니다.
빠른 통합 패턴
대부분의 관찰 가능성 도구는 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
}
})
})
모범 사례
개발 환경과 프로덕션 환경
// 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 직접 지원이 추가될 예정이며, 이를 통해 위와 같은 수동 설정 없이 서버 함수, 미들웨어, 라우트 로더를 자동으로 계측할 수 있게 됩니다.
리소스
- Sentry 문서
- OpenTelemetry 문서 - 업계 표준 관찰 가능성
- 작동 예제 - 관찰 가능성 패턴이 실제로 작동하는 모습을 확인합니다