mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 13:55:50 +00:00
feat: 更新 sentry 错误上报
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import * as React from 'react'
|
||||
import { captureException } from 'src/utils/sentry.js'
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode
|
||||
/** Optional label for identifying which component boundary caught the error */
|
||||
name?: string
|
||||
}
|
||||
|
||||
interface State {
|
||||
@@ -18,6 +21,13 @@ export class SentryErrorBoundary extends React.Component<Props, State> {
|
||||
return { hasError: true }
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
|
||||
captureException(error, {
|
||||
componentBoundary: this.props.name || 'SentryErrorBoundary',
|
||||
componentStack: errorInfo.componentStack,
|
||||
})
|
||||
}
|
||||
|
||||
render(): React.ReactNode {
|
||||
if (this.state.hasError) {
|
||||
return null
|
||||
|
||||
@@ -48,6 +48,7 @@ import { configureGlobalAgents } from '../utils/proxy.js'
|
||||
import { isBetaTracingEnabled } from '../utils/telemetry/betaSessionTracing.js'
|
||||
import { getTelemetryAttributes } from '../utils/telemetryAttributes.js'
|
||||
import { setShellIfWindows } from '../utils/windowsPaths.js'
|
||||
import { initSentry } from '../utils/sentry.js'
|
||||
|
||||
// initialize1PEventLogging is dynamically imported to defer OpenTelemetry sdk-logs/resources
|
||||
|
||||
@@ -150,6 +151,9 @@ export const init = memoize(async (): Promise<void> => {
|
||||
logForDebugging('[init] configureGlobalAgents complete')
|
||||
profileCheckpoint('init_network_configured')
|
||||
|
||||
// Initialize Sentry for error reporting (no-op if SENTRY_DSN not set)
|
||||
initSentry()
|
||||
|
||||
// Preconnect to the Anthropic API — overlap TCP+TLS handshake
|
||||
// (~100-200ms) with the ~100ms of action-handler work before the API
|
||||
// request. After CA certs + proxy agents are configured so the warmed
|
||||
|
||||
@@ -2805,7 +2805,8 @@ export function REPL({
|
||||
})) {
|
||||
onQueryEvent(event);
|
||||
}
|
||||
if (feature('BUDDY')) {
|
||||
// TODO: implement fireCompanionObserver — companion model reaction after each query turn
|
||||
if (feature('BUDDY') && typeof fireCompanionObserver === 'function') {
|
||||
void fireCompanionObserver(messagesRef.current, reaction => setAppState(prev => prev.companionReaction === reaction ? prev : {
|
||||
...prev,
|
||||
companionReaction: reaction as string | undefined
|
||||
|
||||
@@ -20,6 +20,7 @@ import { logForDebugging } from './debug.js'
|
||||
import { getFsImplementation } from './fsOperations.js'
|
||||
import { attachErrorLogSink, dateToFilename } from './log.js'
|
||||
import { jsonStringify } from './slowOperations.js'
|
||||
import { captureException } from './sentry.js'
|
||||
|
||||
const DATE = dateToFilename(new Date())
|
||||
|
||||
@@ -171,6 +172,9 @@ function logErrorImpl(error: Error): void {
|
||||
appendToLog(getErrorsPath(), {
|
||||
error: `${context}${errorStr}`,
|
||||
})
|
||||
|
||||
// Also report to Sentry (no-op if not initialized)
|
||||
captureException(error)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,6 +42,7 @@ import { logForDiagnosticsNoPII } from './diagLogs.js'
|
||||
import { isEnvTruthy } from './envUtils.js'
|
||||
import { getCurrentSessionTitle, sessionIdExists } from './sessionStorage.js'
|
||||
import { sleep } from './sleep.js'
|
||||
import { closeSentry } from './sentry.js'
|
||||
import { profileReport } from './startupProfiler.js'
|
||||
|
||||
/**
|
||||
@@ -503,7 +504,7 @@ export async function gracefulShutdown(
|
||||
// Lost analytics on slow networks are acceptable; a hanging exit is not.
|
||||
try {
|
||||
await Promise.race([
|
||||
Promise.all([shutdown1PEventLogging(), shutdownDatadog()]),
|
||||
Promise.all([shutdown1PEventLogging(), shutdownDatadog(), closeSentry(2000)]),
|
||||
sleep(500),
|
||||
])
|
||||
} catch {
|
||||
|
||||
160
src/utils/sentry.ts
Normal file
160
src/utils/sentry.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Sentry integration module
|
||||
*
|
||||
* Initializes Sentry SDK when SENTRY_DSN environment variable is set.
|
||||
* When DSN is not configured, all exports are no-ops.
|
||||
*/
|
||||
|
||||
import * as Sentry from '@sentry/node'
|
||||
import { logForDebugging } from './debug.js'
|
||||
|
||||
let initialized = false
|
||||
|
||||
/**
|
||||
* Initialize Sentry SDK. Safe to call multiple times — subsequent calls are no-ops.
|
||||
* Only activates when SENTRY_DSN environment variable is set.
|
||||
*/
|
||||
export function initSentry(): void {
|
||||
if (initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
const dsn = process.env.SENTRY_DSN
|
||||
if (!dsn) {
|
||||
logForDebugging('[sentry] SENTRY_DSN not set, skipping initialization')
|
||||
return
|
||||
}
|
||||
|
||||
Sentry.init({
|
||||
dsn,
|
||||
release: typeof MACRO !== 'undefined' ? MACRO.VERSION : undefined,
|
||||
environment:
|
||||
typeof BUILD_ENV !== 'undefined' ? BUILD_ENV : process.env.NODE_ENV || 'development',
|
||||
|
||||
// Limit breadcrumbs and attachments to control payload size
|
||||
maxBreadcrumbs: 20,
|
||||
|
||||
// Sample rate for error events (1.0 = capture all)
|
||||
sampleRate: 1.0,
|
||||
|
||||
// Filter sensitive information before sending
|
||||
beforeSend(event) {
|
||||
// Strip auth headers from request data
|
||||
const request = event.request
|
||||
if (request?.headers) {
|
||||
const sensitiveHeaders = [
|
||||
'authorization',
|
||||
'x-api-key',
|
||||
'cookie',
|
||||
'set-cookie',
|
||||
]
|
||||
for (const key of Object.keys(request.headers)) {
|
||||
if (sensitiveHeaders.includes(key.toLowerCase())) {
|
||||
delete request.headers[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return event
|
||||
},
|
||||
|
||||
// Ignore specific error patterns
|
||||
ignoreErrors: [
|
||||
// Network errors from unreachable hosts — not actionable
|
||||
'ECONNREFUSED',
|
||||
'ECONNRESET',
|
||||
'ENOTFOUND',
|
||||
'ETIMEDOUT',
|
||||
// User-initiated aborts
|
||||
'AbortError',
|
||||
'The user aborted a request',
|
||||
// Interactive cancellation signals
|
||||
'CancelError',
|
||||
],
|
||||
|
||||
beforeSendTransaction(event) {
|
||||
// Don't send performance transactions for now — errors only
|
||||
return null
|
||||
},
|
||||
})
|
||||
|
||||
initialized = true
|
||||
logForDebugging('[sentry] Initialized successfully')
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture an exception and send it to Sentry.
|
||||
* No-op if Sentry has not been initialized.
|
||||
*/
|
||||
export function captureException(error: unknown, context?: Record<string, unknown>): void {
|
||||
if (!initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
Sentry.withScope(scope => {
|
||||
if (context) {
|
||||
scope.setExtras(context)
|
||||
}
|
||||
Sentry.captureException(error)
|
||||
})
|
||||
} catch {
|
||||
// Sentry itself failed — don't let it crash the app
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a tag on the current scope for grouping/filtering in Sentry.
|
||||
* No-op if Sentry has not been initialized.
|
||||
*/
|
||||
export function setTag(key: string, value: string): void {
|
||||
if (!initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
Sentry.setTag(key, value)
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user context in Sentry for error attribution.
|
||||
* No-op if Sentry has not been initialized.
|
||||
*/
|
||||
export function setUser(user: { id?: string; email?: string; username?: string }): void {
|
||||
if (!initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
Sentry.setUser(user)
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush pending Sentry events and close the client.
|
||||
* Call during graceful shutdown to ensure events are sent.
|
||||
*/
|
||||
export async function closeSentry(timeoutMs = 2000): Promise<void> {
|
||||
if (!initialized) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await Sentry.close(timeoutMs)
|
||||
logForDebugging('[sentry] Closed successfully')
|
||||
} catch {
|
||||
// Ignore — we're shutting down anyway
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Sentry is initialized. Useful for conditional UI rendering.
|
||||
*/
|
||||
export function isSentryInitialized(): boolean {
|
||||
return initialized
|
||||
}
|
||||
Reference in New Issue
Block a user