@exodus/error-tracking 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/api/index.d.ts +0 -10
- package/api/index.js +6 -29
- package/atoms/index.js +7 -2
- package/index.js +34 -6
- package/module/error-tracking.js +29 -5
- package/module/remote-error-tracking.js +5 -1
- package/package.json +4 -4
- package/plugin/index.js +82 -0
- package/plugin/why-is-remote-tracking-disabled.js +23 -0
- package/is-empty.js +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [3.0.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/error-tracking@2.2.0...@exodus/error-tracking@3.0.0) (2025-07-01)
|
|
7
|
+
|
|
8
|
+
### ⚠ BREAKING CHANGES
|
|
9
|
+
|
|
10
|
+
- merge errorTracking.track/trackRemote, enable/disable remote tracking based on config (#13038)
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
- feat!: merge errorTracking.track/trackRemote, enable/disable remote tracking based on config (#13038)
|
|
15
|
+
|
|
6
16
|
## [2.2.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/error-tracking@2.1.0...@exodus/error-tracking@2.2.0) (2025-06-23)
|
|
7
17
|
|
|
8
18
|
### Features
|
package/api/index.d.ts
CHANGED
|
@@ -11,16 +11,6 @@ export interface ErrorTrackingApi {
|
|
|
11
11
|
* ```
|
|
12
12
|
*/
|
|
13
13
|
track(params: { error: Error; namespace: string; context?: any }): Promise<void>
|
|
14
|
-
/**
|
|
15
|
-
* Track an error remotely using sentry if available
|
|
16
|
-
* @example
|
|
17
|
-
* ```typescript
|
|
18
|
-
* exodus.errors.trackRemote({
|
|
19
|
-
* error: 'Encountered an issue when computing total balances',
|
|
20
|
-
* })
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
trackRemote(params: { error: string }): Promise<void>
|
|
24
14
|
}
|
|
25
15
|
|
|
26
16
|
declare const errorTrackingApiDefinition: {
|
package/api/index.js
CHANGED
|
@@ -1,33 +1,10 @@
|
|
|
1
|
-
const createTrackRemote = ({ remoteErrorTracking, logger }) => {
|
|
2
|
-
if (!remoteErrorTracking) {
|
|
3
|
-
return async ({ error }) => {
|
|
4
|
-
logger.debug('remote error tracking is disabled', error)
|
|
5
|
-
}
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
return async ({ error }) => {
|
|
9
|
-
try {
|
|
10
|
-
await remoteErrorTracking.captureError({
|
|
11
|
-
error,
|
|
12
|
-
})
|
|
13
|
-
} catch (err) {
|
|
14
|
-
logger.error('failed to remote track error', err)
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
1
|
export const errorTrackingApiDefinition = {
|
|
20
2
|
id: 'errorTrackingApi',
|
|
21
3
|
type: 'api',
|
|
22
|
-
factory: ({ errorTracking
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
trackRemote,
|
|
29
|
-
},
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
dependencies: ['logger', 'remoteErrorTracking?', 'errorTracking'],
|
|
4
|
+
factory: ({ errorTracking }) => ({
|
|
5
|
+
errors: {
|
|
6
|
+
track: errorTracking.track,
|
|
7
|
+
},
|
|
8
|
+
}),
|
|
9
|
+
dependencies: ['errorTracking'],
|
|
33
10
|
}
|
package/atoms/index.js
CHANGED
|
@@ -5,11 +5,16 @@ import { createInMemoryAtom } from '@exodus/atoms'
|
|
|
5
5
|
errors: [{ `namespace`, `error`, `context`, `time` }, ...]
|
|
6
6
|
}
|
|
7
7
|
*/
|
|
8
|
-
const errorsAtomDefinition = {
|
|
8
|
+
export const errorsAtomDefinition = {
|
|
9
9
|
id: 'errorsAtom',
|
|
10
10
|
type: 'atom',
|
|
11
11
|
factory: () => createInMemoryAtom({ defaultValue: { errors: [] } }),
|
|
12
12
|
dependencies: [],
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export
|
|
15
|
+
export const remoteErrorTrackingEnabledAtomDefinition = {
|
|
16
|
+
id: 'remoteErrorTrackingEnabledAtom',
|
|
17
|
+
type: 'atom',
|
|
18
|
+
// eslint-disable-next-line @exodus/hydra/in-memory-atom-default-value
|
|
19
|
+
factory: () => createInMemoryAtom(),
|
|
20
|
+
}
|
package/index.js
CHANGED
|
@@ -1,28 +1,47 @@
|
|
|
1
1
|
import typeforce from '@exodus/typeforce'
|
|
2
2
|
|
|
3
|
-
import { isEmpty } from './is-empty.js'
|
|
4
3
|
import { errorTrackingApiDefinition } from './api/index.js'
|
|
5
|
-
import
|
|
4
|
+
import { errorsAtomDefinition, remoteErrorTrackingEnabledAtomDefinition } from './atoms/index.js'
|
|
6
5
|
import errorTrackingReportDefinition from './report/index.js'
|
|
7
6
|
import { errorTrackingDefinition, remoteErrorTrackingDefinition } from './module/index.js'
|
|
7
|
+
import errorTrackingPluginDefinition from './plugin/index.js'
|
|
8
8
|
|
|
9
|
-
const defaultConfig = {
|
|
9
|
+
const defaultConfig = {
|
|
10
|
+
maxErrorsCount: 100,
|
|
11
|
+
sentryConfig: undefined,
|
|
12
|
+
remoteErrorTrackingABExperimentId: 'sentry',
|
|
13
|
+
trackWalletsCreatedAfter: new Date('2025-07-21'),
|
|
14
|
+
trackFundedWallets: false,
|
|
15
|
+
}
|
|
10
16
|
|
|
11
17
|
const configSchema = {
|
|
12
18
|
maxErrorsCount: (value) => typeof value === 'number' && value > 0 && value <= 9999,
|
|
13
19
|
sentryConfig: '?Object',
|
|
20
|
+
remoteErrorTrackingABExperimentId: '?String',
|
|
21
|
+
trackWalletsCreatedAfter: '?Date',
|
|
22
|
+
trackFundedWallets: '?Boolean',
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
const errorTracking = (config = Object.create(null)) => {
|
|
17
26
|
config = { ...defaultConfig, ...config }
|
|
18
27
|
|
|
19
|
-
const {
|
|
28
|
+
const {
|
|
29
|
+
maxErrorsCount,
|
|
30
|
+
sentryConfig,
|
|
31
|
+
remoteErrorTrackingABExperimentId,
|
|
32
|
+
trackWalletsCreatedAfter,
|
|
33
|
+
trackFundedWallets,
|
|
34
|
+
} = typeforce.parse(configSchema, config, true)
|
|
20
35
|
|
|
36
|
+
const remoteErrorTrackingAvailable = !!sentryConfig && !!remoteErrorTrackingABExperimentId
|
|
21
37
|
return {
|
|
22
38
|
id: 'errorTracking',
|
|
23
39
|
definitions: [
|
|
24
40
|
{
|
|
25
|
-
definition:
|
|
41
|
+
definition: errorsAtomDefinition,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
definition: remoteErrorTrackingEnabledAtomDefinition,
|
|
26
45
|
},
|
|
27
46
|
{
|
|
28
47
|
definition: errorTrackingApiDefinition,
|
|
@@ -32,13 +51,22 @@ const errorTracking = (config = Object.create(null)) => {
|
|
|
32
51
|
config: { maxErrorsCount },
|
|
33
52
|
},
|
|
34
53
|
{
|
|
35
|
-
if:
|
|
54
|
+
if: remoteErrorTrackingAvailable,
|
|
36
55
|
definition: remoteErrorTrackingDefinition,
|
|
37
56
|
config: sentryConfig,
|
|
38
57
|
},
|
|
58
|
+
// deprecated, errors will go to sentry
|
|
39
59
|
{
|
|
40
60
|
definition: errorTrackingReportDefinition,
|
|
41
61
|
},
|
|
62
|
+
{
|
|
63
|
+
definition: errorTrackingPluginDefinition,
|
|
64
|
+
config: {
|
|
65
|
+
remoteErrorTrackingABExperimentId,
|
|
66
|
+
trackWalletsCreatedAfter,
|
|
67
|
+
trackFundedWallets,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
42
70
|
],
|
|
43
71
|
}
|
|
44
72
|
}
|
package/module/error-tracking.js
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
const MODULE_ID = 'errorTracking'
|
|
2
2
|
|
|
3
|
-
const createErrorTracking = ({
|
|
3
|
+
const createErrorTracking = ({
|
|
4
|
+
errorsAtom,
|
|
5
|
+
remoteErrorTrackingEnabledAtom,
|
|
6
|
+
remoteErrorTracking,
|
|
7
|
+
config,
|
|
8
|
+
logger,
|
|
9
|
+
}) => {
|
|
4
10
|
const track = async ({ error, context, namespace }) => {
|
|
5
|
-
if (
|
|
6
|
-
throw new Error('
|
|
11
|
+
if (namespace !== undefined && typeof namespace !== 'string') {
|
|
12
|
+
throw new Error('namespace must be a string')
|
|
7
13
|
}
|
|
8
14
|
|
|
9
15
|
if (!(error instanceof Error)) {
|
|
10
16
|
throw new TypeError('error must be an instance of Error')
|
|
11
17
|
}
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
// TODO: figure out what to do with `context`
|
|
20
|
+
|
|
21
|
+
// eventually kill this and only track remote
|
|
22
|
+
await errorsAtom.set(({ errors }) => {
|
|
14
23
|
return {
|
|
15
24
|
// this array can be big. not sure about prefering spread operator here
|
|
16
25
|
// concat function seems like a better option
|
|
@@ -20,6 +29,15 @@ const createErrorTracking = ({ errorsAtom, config }) => {
|
|
|
20
29
|
.slice(0, config.maxErrorsCount),
|
|
21
30
|
}
|
|
22
31
|
})
|
|
32
|
+
|
|
33
|
+
if (remoteErrorTracking) {
|
|
34
|
+
remoteErrorTrackingEnabledAtom
|
|
35
|
+
.get()
|
|
36
|
+
.then((enabled) => enabled && remoteErrorTracking.track({ error }))
|
|
37
|
+
.catch((err) => {
|
|
38
|
+
logger.error('failed to upload error', error, err)
|
|
39
|
+
})
|
|
40
|
+
}
|
|
23
41
|
}
|
|
24
42
|
|
|
25
43
|
return { track }
|
|
@@ -29,6 +47,12 @@ export const errorTrackingDefinition = {
|
|
|
29
47
|
id: MODULE_ID,
|
|
30
48
|
type: 'module',
|
|
31
49
|
factory: createErrorTracking,
|
|
32
|
-
dependencies: [
|
|
50
|
+
dependencies: [
|
|
51
|
+
'config',
|
|
52
|
+
'errorsAtom',
|
|
53
|
+
'remoteErrorTrackingEnabledAtom',
|
|
54
|
+
'remoteErrorTracking?',
|
|
55
|
+
'logger',
|
|
56
|
+
],
|
|
33
57
|
public: true,
|
|
34
58
|
}
|
|
@@ -6,10 +6,14 @@ export const remoteErrorTrackingDefinition = {
|
|
|
6
6
|
type: 'module',
|
|
7
7
|
factory: ({ config, fetch }) => {
|
|
8
8
|
const fetchival = createFetchival({ fetch })
|
|
9
|
-
|
|
9
|
+
const client = createSentryClient({
|
|
10
10
|
config,
|
|
11
11
|
fetchival,
|
|
12
12
|
})
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
track: ({ error }) => client.captureError({ error }),
|
|
16
|
+
}
|
|
13
17
|
},
|
|
14
18
|
dependencies: ['config', 'fetch'],
|
|
15
19
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/error-tracking",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A simple error tracking package to let any feature collect errors and create the report",
|
|
6
6
|
"author": "Exodus Movement, Inc.",
|
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
"main": "index.js",
|
|
17
17
|
"exports": "./index.js",
|
|
18
18
|
"files": [
|
|
19
|
-
"index.d.ts",
|
|
20
|
-
"is-empty.js",
|
|
21
19
|
"api",
|
|
22
20
|
"atoms",
|
|
23
21
|
"module",
|
|
22
|
+
"plugin",
|
|
24
23
|
"report",
|
|
25
24
|
"CHANGELOG.md",
|
|
26
25
|
"README.md",
|
|
26
|
+
"index.d.ts",
|
|
27
27
|
"!**/__tests__/**"
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
@@ -43,5 +43,5 @@
|
|
|
43
43
|
"publishConfig": {
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "7425cf446dbe32c407ac46bd1c0463787cf8deb6"
|
|
47
47
|
}
|
package/plugin/index.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { combine, compute } from '@exodus/atoms'
|
|
2
|
+
|
|
3
|
+
import whyIsRemoteTrackingDisabled from './why-is-remote-tracking-disabled.js'
|
|
4
|
+
|
|
5
|
+
function errorTrackingPlugin({
|
|
6
|
+
abTestingAtom,
|
|
7
|
+
earliestTxDateAtom,
|
|
8
|
+
walletCreatedAtAtom,
|
|
9
|
+
getBuildMetadata,
|
|
10
|
+
remoteErrorTrackingEnabledAtom,
|
|
11
|
+
config: { remoteErrorTrackingABExperimentId, trackWalletsCreatedAfter, trackFundedWallets },
|
|
12
|
+
logger,
|
|
13
|
+
}) {
|
|
14
|
+
const subscriptions = []
|
|
15
|
+
|
|
16
|
+
const onAssetsSynced = async () => {
|
|
17
|
+
if (!abTestingAtom) {
|
|
18
|
+
logger.debug('remote error tracking is disabled, ab-testing feature not found')
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// defined locally instead of in /atoms to avoid circular dependency
|
|
23
|
+
// literally everything depends on error-tracking, so error-tracking atoms can't depend on other features
|
|
24
|
+
const internalRemoteErrorTrackingEnabledAtom = compute({
|
|
25
|
+
atom: combine({
|
|
26
|
+
abTesting: abTestingAtom,
|
|
27
|
+
earliestTxDate: earliestTxDateAtom,
|
|
28
|
+
walletCreatedAt: walletCreatedAtAtom,
|
|
29
|
+
}),
|
|
30
|
+
selector: async ({ abTesting, earliestTxDate, walletCreatedAt }) => {
|
|
31
|
+
const reasonDisabled = whyIsRemoteTrackingDisabled({
|
|
32
|
+
remoteErrorTrackingABExperimentId,
|
|
33
|
+
abTesting,
|
|
34
|
+
trackWalletsCreatedAfter,
|
|
35
|
+
walletCreatedAt,
|
|
36
|
+
trackFundedWallets,
|
|
37
|
+
earliestTxDate,
|
|
38
|
+
buildMetadata: await getBuildMetadata(),
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
if (reasonDisabled) {
|
|
42
|
+
logger.debug(`remote error tracking is disabled: ${reasonDisabled}`)
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
logger.debug('remote error tracking is enabled')
|
|
47
|
+
return true
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
subscriptions.push(
|
|
52
|
+
internalRemoteErrorTrackingEnabledAtom.observe(remoteErrorTrackingEnabledAtom.set)
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const onStop = () => {
|
|
57
|
+
subscriptions.forEach((unsubscribe) => unsubscribe())
|
|
58
|
+
subscriptions.length = 0
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
onAssetsSynced,
|
|
63
|
+
onStop,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const errorTrackingPluginDefinition = {
|
|
68
|
+
id: 'errorTrackingPlugin',
|
|
69
|
+
type: 'plugin',
|
|
70
|
+
factory: errorTrackingPlugin,
|
|
71
|
+
dependencies: [
|
|
72
|
+
'abTestingAtom?',
|
|
73
|
+
'earliestTxDateAtom',
|
|
74
|
+
'walletCreatedAtAtom',
|
|
75
|
+
'remoteErrorTrackingEnabledAtom',
|
|
76
|
+
'getBuildMetadata',
|
|
77
|
+
'config',
|
|
78
|
+
'logger',
|
|
79
|
+
],
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export default errorTrackingPluginDefinition
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export default function whyIsRemoteTrackingDisabled({
|
|
2
|
+
remoteErrorTrackingABExperimentId,
|
|
3
|
+
abTesting,
|
|
4
|
+
trackWalletsCreatedAfter,
|
|
5
|
+
walletCreatedAt,
|
|
6
|
+
trackFundedWallets,
|
|
7
|
+
earliestTxDate,
|
|
8
|
+
buildMetadata,
|
|
9
|
+
}) {
|
|
10
|
+
if (!remoteErrorTrackingABExperimentId) return 'missing-ab-experiment-id'
|
|
11
|
+
if (!abTesting.experiments?.[remoteErrorTrackingABExperimentId]?.enabled)
|
|
12
|
+
return 'ab-experiment-disabled'
|
|
13
|
+
if (
|
|
14
|
+
trackWalletsCreatedAfter &&
|
|
15
|
+
// the negation's a bit harder to read but is a safer check
|
|
16
|
+
!(new Date(walletCreatedAt) > trackWalletsCreatedAfter)
|
|
17
|
+
) {
|
|
18
|
+
return 'wallet-too-old'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!trackFundedWallets && earliestTxDate) return 'not-tracking-funded-wallets'
|
|
22
|
+
if (buildMetadata.dev) return 'dev-mode'
|
|
23
|
+
}
|