Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add swap error logging to Sentry #5322

Merged
merged 4 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

## Unreleased

- added: Log swap errors to Sentry.
- changed: Various strings updated to UK compliance spec
- changed: Wording in light account persistent notification
- fixed: Fix error massaging in trackError
- fixed: Use Sentry context for logging metadata in `EdgeCrashEvent`
- removed: Bank Wire Transfer Buy for Florida
- removed: Paypal Sell for Canada
- removed: Moonpay, Simplex, and Paybis for UK
Expand Down
37 changes: 37 additions & 0 deletions src/components/scenes/SwapProcessingScene.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { captureException } from '@sentry/react-native'
import {
asMaybeInsufficientFundsError,
asMaybeSwapAboveLimitError,
Expand Down Expand Up @@ -111,6 +112,9 @@ function processSwapQuoteError({
// Some plugins get the insufficient funds error wrong:
const errorMessage = error instanceof Error ? error.message : String(error)

// Track swap errors to sentry:
trackSwapError(error, swapRequest)

// Check for known error types:
const insufficientFunds = asMaybeInsufficientFundsError(error)
if (insufficientFunds != null || errorMessage === 'InsufficientFundsError') {
Expand Down Expand Up @@ -179,3 +183,36 @@ function processSwapQuoteError({
error
}
}

/**
* REVIEWER BEWARE!!
*
* No specific account/wallet information should be included within the
* scope for this capture. No personal information such as wallet IDs,
* public keys, or transaction details, amounts, should be collected
* according to Edge's company policy.
*/
function trackSwapError(error: unknown, swapRequest: EdgeSwapRequest): void {
captureException(error, scope => {
// This is a warning level error because it's expected to occur but not wanted.
scope.setLevel('warning')
// Searchable tags:
scope.setTags({
errorType: 'swapQuoteFailure',
swapFromWalletKind: swapRequest.fromWallet.currencyInfo.pluginId,
swapFromCurrency: getCurrencyCode(swapRequest.fromWallet, swapRequest.fromTokenId),
swapToCurrency: getCurrencyCode(swapRequest.toWallet, swapRequest.toTokenId),
swapToWalletKind: swapRequest.toWallet.currencyInfo.pluginId,
swapDirectionType: swapRequest.quoteFor
})
// Unsearchable context data:
scope.setContext('Swap Request Details', {
fromTokenId: String(swapRequest.fromTokenId), // Stringify to include "null"
fromWalletType: swapRequest.fromWallet.type,
toTokenId: String(swapRequest.toTokenId), // Stringify to include "null"
toWalletType: swapRequest.fromWallet.type,
quoteFor: swapRequest.quoteFor
})
return scope
})
}
51 changes: 49 additions & 2 deletions src/components/services/EdgeCoreManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,55 @@ const crashReporter: EdgeCrashReporter = {
})
},
logCrash(event) {
const eventString = JSON.stringify(event, null, 2)
captureException(eventString, { level: 'fatal' })
// Index the crash error by the source and original error name:
const error = new Error(`${event.source}: ${String(event.error)}`)
// All of these crash errors are grouped together using this error name:
error.name = 'EdgeCrashLog'

captureException(error, scope => {
scope.setLevel('fatal')

const context: Record<string, unknown> = {}
addMetadataToContext(context, event.metadata)
scope.setContext('Edge Crash Metadata', context)

return scope
})
}
}

/**
* Recursively adds metadata to a context object.
*
* @param context The context object to which to add metadata
* @param metadata The metadata object to add to the context
* @param prefixKeys optional prefix keys to add to each entry
* @returns void (modifies context object)
*/
function addMetadataToContext(context: Record<string, unknown>, metadata: object, prefixKeys: string[] = []): void {
for (const [key, value] of Object.entries(metadata)) {
const allKeys = [...prefixKeys, key]
const fullKey = allKeys.join(' > ')

// Serialize error objects manually because JSON.stringify doesn't
// include all the error properties:
if (value instanceof Error) {
context[fullKey] = `${value.name}: ${value.message}`
if (value.stack != null) {
// Include the stack trace with indentation:
context[fullKey] += `\n ${value.stack.replace(/\n/g, '\n ')}`
}
continue
}

// Recurse over objects with added prefix keys:
if (typeof value === 'object' && value !== null) {
addMetadataToContext(context, value, allKeys)
continue
}

// Default
context[key] = value
}
}

Expand Down
22 changes: 14 additions & 8 deletions src/util/tracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,19 +152,25 @@ export function trackError(
[key: string]: any
}
): void {
let err: Error | string
if (error instanceof Error || typeof error === 'string') {
let err: Error
if (error instanceof Error) {
err = error
} else if (typeof error === 'string') {
err = new Error(error)
} else {
// At least send an error which should give us the callstack
err = 'Unknown error occurred'
// At least send an error which should give us the call-stack
err = new Error(`Unknown error occurred: ${String(error)}`)
}

if (tag == null) {
captureException(err)
} else {
captureException(err, { event_id: tag, data: metadata })
let hint: { event_id?: string; data?: { [key: string]: any } } | undefined
if (tag != null) {
hint = { event_id: tag }
}
if (metadata != null) {
hint = { data: metadata }
samholmes marked this conversation as resolved.
Show resolved Hide resolved
}

captureException(err, hint)
}

/**
Expand Down
Loading