关于 error-monitoring
为 Next.js 应用提供错误处理、日志记录和监控方面的指导,涵盖错误边界、API 错误、Sentry、性能检查与恢复策略。
主要功能
- 01React 错误边界
- 02自定义错误类
- 03结构化上下文日志
- 04Sentry 错误跟踪
- 05性能监控
- 06重试策略
适用场景
- 01为 Next.js 应用添加错误边界
- 02记录带请求上下文的 API 故障
- 03将异常发送到 Sentry
- 04重试暂时性故障操作
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ jmsktm/ claude-settings --skill error-monitoring安装后在 Claude Code 中输入/error-monitoring确认已加载。
详细介绍
错误监控与日志记录技能
概述
此技能可帮助你在 Next.js 应用中实现稳健的错误处理、日志记录和监控。从本地开发到生产环境监控,涵盖了捕获和修复问题所需的一切。
错误边界(React)
基本错误边界
// app/error.tsx
'use client'
import { useEffect } from 'react'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useEffect(() => {
// Log error to your monitoring service
console.error('Error caught:', error)
}, [error])
return (
<div className="flex flex-col items-center justify-center min-h-screen p-4">
<h2 className="text-2xl font-bold mb-4">Something went wrong!</h2>
<p className="text-gray-600 mb-4">{error.message}</p>
<button
onClick={reset}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Try again
</button>
</div>
)
}
全局错误边界
// app/global-error.tsx
'use client'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html>
<body>
<h2>Application Error</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</body>
</html>
)
}
嵌套错误边界
// app/dashboard/error.tsx
'use client'
export default function DashboardError({
error,
reset,
}: {
error: Error
reset: () => void
}) {
return (
<div className="p-4">
<h2>Dashboard Error</h2>
<p>{error.message}</p>
<button onClick={reset}>Reload Dashboard</button>
</div>
)
}
// app/dashboard/settings/error.tsx - More specific
'use client'
export default function SettingsError({
error,
reset,
}: {
error: Error
reset: () => void
}) {
return (
<div className="p-4">
<h2>Settings Error</h2>
<p>Failed to load settings: {error.message}</p>
<button onClick={reset}>Reload Settings</button>
</div>
)
}
自定义错误类
// lib/errors.ts
export class AppError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number = 500,
public isOperational: boolean = true
) {
super(message)
this.name = this.constructor.name
Error.captureStackTrace(this, this.constructor)
}
}
export class ValidationError extends AppError {
constructor(message: string, public field?: string) {
super(message, 'VALIDATION_ERROR', 400)
}
}
export class AuthenticationError extends AppError {
constructor(message = 'Authentication required') {
super(message, 'AUTHENTICATION_ERROR', 401)
}
}
export class AuthorizationError extends AppError {
constructor(message = 'Insufficient permissions') {
super(message, 'AUTHORIZATION_ERROR', 403)
}
}
export class NotFoundError extends AppError {
constructor(resource: string) {
super(`${resource} not found`, 'NOT_FOUND', 404)
}
}
export class DatabaseError extends AppError {
constructor(message: string) {
super(message, 'DATABASE_ERROR', 500, false)
}
}
// Usage
throw new ValidationError('Invalid email format', 'email')
throw new NotFoundError('User')
throw new AuthenticationError()
错误记录器
// lib/logger.ts
type LogLevel = 'debug' | 'info' | 'warn' | 'error'
interface LogEntry {
level: LogLevel
message: string
timestamp: string
context?: Record<string, any>
error?: Error
}
class Logger {
private logs: LogEntry[] = []
private log(level: LogLevel, message: string, context?: Record<string, any>, error?: Error) {
const entry: LogEntry = {
level,
message,
timestamp: new Date().toISOString(),
context,
error
}
this.logs.push(entry)
// Log to console in development
if (process.env.NODE_ENV === 'development') {
const color = {
debug: '\x1b[36m',
info: '\x1b[32m',
warn: '\x1b[33m',
error: '\x1b[31m'
}[level]
console.log(
`${color}[${level.toUpperCase()}]\x1b[0m ${message}`,
context || '',
error || ''
)
}
// Send to monitoring service in production
if (process.env.NODE_ENV === 'production' && level === 'error') {
this.sendToMonitoring(entry)
}
}
debug(message: string, context?: Record<string, any>) {
this.log('debug', message, context)
}
info(message: string, context?: Record<string, any>) {
this.log('info', message, context)
}
warn(message: string, context?: Record<string, any>) {
this.log('warn', message, context)
}
error(message: string, error?: Error, context?: Record<string, any>) {
this.log('error', message, context, error)
}
private async sendToMonitoring(entry: LogEntry) {
// Send to Sentry, LogRocket, etc.
try {
await fetch('/api/logs', {
method: 'POST',
body: JSON.stringify(entry)
})
} catch (e) {
// Fallback: log to console
console.error('Failed to send log to monitoring:', e)
}
}
}
export const logger = new Logger()
// Usage
logger.info('User logged in', { userId: '123' })
logger.error('Payment failed', error, { orderId: '456' })
API 错误处理
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { AppError } from '@/lib/errors'
import { logger } from '@/lib/logger'
export async function GET(request: NextRequest) {
try {
const users = await db.users.findMany()
return NextResponse.json({ users })
} catch (error) {
return handleApiError(error)
}
}
function handleApiError(error: unknown): NextResponse {
// Log error
logger.error('API Error', error as Error)
// Handle known errors
if (error instanceof AppError) {
return NextResponse.json(
{
error: error.message,
code: error.code
},
{ status: error.statusCode }
)
}
// Handle Zod validation errors
if (error instanceof z.ZodError) {
return NextResponse.json(
{
error: 'Validation failed',
code: 'VALIDATION_ERROR',
issues: error.issues
},
{ status: 400 }
)
}
// Handle database errors
if (error.code === 'P2002') { // Prisma unique constraint
return NextResponse.json(
{
error: 'Resource already exists',
code: 'DUPLICATE_ERROR'
},
{ status: 409 }
)
}
// Handle unexpected errors
return NextResponse.json(
{
error: 'Internal server error',
code: 'INTERNAL_ERROR'
},
{ status: 500 }
)
}
Sentry 集成
设置
// instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./sentry.server.config')
}
if (process.env.NEXT_RUNTIME === 'edge') {
await import('./sentry.edge.config')
}
}
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs'
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 1.0,
debug: false,
replaysOnErrorSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
integrations: [
Sentry.replayIntegration({
maskAllText: true,
blockAllMedia: true,
}),
],
})
// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs'
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 1.0,
debug: false,
})
使用 Sentry
import * as Sentry from '@sentry/nextjs'
// Capture exception
try {
await riskyOperation()
} catch (error) {
Sentry.captureException(error, {
tags: {
section: 'payment',
},
extra: {
userId: user.id,
orderId: order.id,
},
})
throw error
}
// Capture message
Sentry.captureMessage('Something went wrong', {
level: 'warning',
tags: { feature: 'checkout' },
})
// Set user context
Sentry.setUser({
id: user.id,
email: user.email,
username: user.name,
})
// Add breadcrumb
Sentry.addBreadcrumb({
category: 'auth',
message: 'User logged in',
level: 'info',
})
客户端错误跟踪
// components/error-tracker.tsx
'use client'
import { useEffect } from 'react'
import * as Sentry from '@sentry/nextjs'
export function ErrorTracker() {
useEffect(() => {
// Catch unhandled errors
window.addEventListener('error', (event) => {
Sentry.captureException(event.error)
})
// Catch unhandled promise rejections
window.addEventListener('unhandledrejection', (event) => {
Sentry.captureException(event.reason)
})
}, [])
return null
}
// app/layout.tsx
import { ErrorTracker } from '@/components/error-tracker'
export default function RootLayout({ children }) {
return (
<html>
<body>
<ErrorTracker />
{children}
</body>
</html>
)
}
性能监控
// lib/performance.ts
export class PerformanceMonitor {
private marks: Map<string, number> = new Map()
start(label: string) {
this.marks.set(label, performance.now())
}
end(label: string) {
const start = this.marks.get(label)
if (!start) return
const duration = performance.now() - start
this.marks.delete(label)
logger.info(`Performance: ${label}`, { duration: `${duration.toFixed(2)}ms` })
// Send to monitoring if slow
if (duration > 1000) {
Sentry.captureMessage(`Slow operation: ${label}`, {
level: 'warning',
extra: { duration },
})
}
return duration
}
}
export const perfMonitor = new PerformanceMonitor()
// Usage
perfMonitor.start('fetchUsers')
const users = await db.users.findMany()
perfMonitor.end('fetchUsers')
结构化日志记录
// lib/structured-logger.ts
type LogContext = {
userId?: string
requestId?: string
ip?: string
userAgent?: string
[key: string]: any
}
class StructuredLogger {
private context: LogContext = {}
setContext(context: LogContext) {
this.context = { ...this.context, ...context }
}
clearContext() {
this.context = {}
}
log(level: string, message: string, data?: any) {
const logEntry = {
timestamp: new Date().toISOString(),
level,
message,
...this.context,
...data,
}
// In production, send to log aggregation service
if (process.env.NODE_ENV === 'production') {
this.sendToLogService(logEntry)
} else {
console.log(JSON.stringify(logEntry, null, 2))
}
}
private async sendToLogService(entry: any) {
// Send to DataDog, Logtail, etc.
}
info(message: string, data?: any) {
this.log('info', message, data)
}
error(message: string, error?: Error, data?: any) {
this.log('error', message, {
...data,
error: {
message: error?.message,
stack: error?.stack,
name: error?.name,
},
})
}
}
export const structuredLogger = new StructuredLogger()
// Usage in API route
export async function POST(request: NextRequest) {
const requestId = crypto.randomUUID()
structuredLogger.setContext({
requestId,
ip: request.ip,
userAgent: request.headers.get('user-agent'),
})
structuredLogger.info('Processing payment', { amount: 100 })
try {
await processPayment()
structuredLogger.info('Payment successful')
} catch (error) {
structuredLogger.error('Payment failed', error)
throw error
} finally {
structuredLogger.clearContext()
}
}
错误恢复策略
// lib/retry.ts
export async function retry<T>(
fn: () => Promise<T>,
options = { maxAttempts: 3, delay: 1000 }
): Promise<T> {
let lastError: Error
for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
try {
return await fn()
} catch (error) {
lastError = error as Error
logger.warn(`Attempt ${attempt} failed`, { error: lastError.message })
if (attempt < options.maxAttempts) {
await new Promise((resolve) =>
setTimeout(resolve, options.delay * attempt)
)
}
}
}
throw lastError!
}
// Usage
const data = await retry(
() => fetch('/api/data').then((r) => r.json()),
{ maxAttempts: 3, delay: 1000 }
)
最佳实践检查清单
- 在关键层级实现错误边界
- 针对不同类型使用自定义错误类
- 记录错误及其上下文(用户、请求 ID 等)
- 将错误发送到监控服务(Sentry)
- 妥善处理预期内的错误
- 显示对用户友好的错误消息
- 为瞬时故障加入重试逻辑
- 监控性能瓶颈
- 为严重错误设置警报
- 跟踪错误率和趋势
- 加入请求 ID 以便调试
- 从日志中清理敏感数据
- 测试错误场景
何时使用此技能
在以下情况下调用此技能:
- 设置错误边界
- 实现错误日志记录
- 集成 Sentry 或监控工具
- 处理 API 错误
- 创建自定义错误类
- 调试生产环境问题
- 设置性能监控
- 实现重试逻辑
- 跟踪用户会话
评论
支持 Markdown