1
+ import { datadogLogs } from '@datadog/browser-logs'
2
+ import { RumEvent , datadogRum } from '@datadog/browser-rum'
3
+ import { getDatadogEnvironment } from 'src/app/version'
4
+ import { config } from 'uniswap/src/config'
5
+ import {
6
+ DatadogIgnoredErrorsConfigKey ,
7
+ DatadogIgnoredErrorsValType ,
8
+ DatadogSessionSampleRateKey ,
9
+ DatadogSessionSampleRateValType ,
10
+ DynamicConfigs ,
11
+ } from 'uniswap/src/features/gating/configs'
12
+ import { Experiments } from 'uniswap/src/features/gating/experiments'
13
+ import { WALLET_FEATURE_FLAG_NAMES } from 'uniswap/src/features/gating/flags'
14
+ import { getDynamicConfigValue } from 'uniswap/src/features/gating/hooks'
15
+ import { Statsig } from 'uniswap/src/features/gating/sdk/statsig'
16
+ import { getUniqueId } from 'utilities/src/device/getUniqueId'
17
+ import { datadogEnabled , localDevDatadogEnabled } from 'utilities/src/environment/constants'
18
+ import { logger } from 'utilities/src/logger/logger'
19
+
20
+ // In case Statsig is not available
21
+ const EXTENSION_DEFAULT_DATADOG_SESSION_SAMPLE_RATE = 10 // percent
22
+
1
23
export const enum DatadogAppNameTag {
2
24
Sidebar = 'sidebar' ,
3
25
Onboarding = 'onboarding' ,
@@ -6,3 +28,115 @@ export const enum DatadogAppNameTag {
6
28
Popup = 'popup' ,
7
29
UnitagClaim = 'unitag-claim' ,
8
30
}
31
+
32
+ function beforeSend ( event : RumEvent ) : boolean {
33
+ // otherwise DataDog will ignore error events
34
+ event . view . url = event . view . url . replace ( / ^ c h r o m e - e x t e n s i o n : \/ \/ [ a - z ] { 32 } \/ / i, '' )
35
+ if ( event . error && event . type === 'error' ) {
36
+ if ( event . error . source === 'console' ) {
37
+ return false
38
+ }
39
+ const ignoredErrors = getDynamicConfigValue <
40
+ DynamicConfigs . DatadogIgnoredErrors ,
41
+ DatadogIgnoredErrorsConfigKey ,
42
+ DatadogIgnoredErrorsValType
43
+ > ( DynamicConfigs . DatadogIgnoredErrors , DatadogIgnoredErrorsConfigKey . Errors , [ ] )
44
+
45
+ const ignoredError = ignoredErrors . find ( ( { messageContains } ) => event . error ?. message . includes ( messageContains ) )
46
+ if ( ignoredError && Math . random ( ) > ignoredError . sampleRate ) {
47
+ return false
48
+ }
49
+
50
+ Object . defineProperty ( event . error , 'stack' , {
51
+ value : event . error . stack ?. replace ( / c h r o m e - e x t e n s i o n : \/ \/ [ a - z ] { 32 } / gi, '' ) ,
52
+ writable : false ,
53
+ configurable : true ,
54
+ } )
55
+ }
56
+
57
+ return true
58
+ }
59
+
60
+ export async function initializeDatadog ( appName : string ) : Promise < void > {
61
+ if ( ! datadogEnabled ) {
62
+ return
63
+ }
64
+
65
+ const sessionSampleRate = getDynamicConfigValue <
66
+ DynamicConfigs . DatadogSessionSampleRate ,
67
+ DatadogSessionSampleRateKey ,
68
+ DatadogSessionSampleRateValType
69
+ > (
70
+ DynamicConfigs . DatadogSessionSampleRate ,
71
+ DatadogSessionSampleRateKey . Rate ,
72
+ EXTENSION_DEFAULT_DATADOG_SESSION_SAMPLE_RATE ,
73
+ )
74
+
75
+ const sharedDatadogConfig = {
76
+ clientToken : config . datadogClientToken ,
77
+ service : `extension-${ getDatadogEnvironment ( ) } ` ,
78
+ env : getDatadogEnvironment ( ) ,
79
+ version : process . env . VERSION ,
80
+ trackingConsent : undefined ,
81
+ }
82
+
83
+ datadogRum . init ( {
84
+ ...sharedDatadogConfig ,
85
+ applicationId : config . datadogProjectId ,
86
+ sessionSampleRate : localDevDatadogEnabled ? 100 : sessionSampleRate ,
87
+ sessionReplaySampleRate : 0 ,
88
+ trackResources : true ,
89
+ trackLongTasks : true ,
90
+ trackUserInteractions : true ,
91
+ enablePrivacyForActionName : true ,
92
+ beforeSend,
93
+ } )
94
+
95
+ // According to the Datadog RUM documentation:
96
+ // https://docs.datadoghq.com/real_user_monitoring/browser/setup/client?tab=rum#access-internal-context
97
+ // datadogRum.init() seems to be synchronous and internal context is immediately available.
98
+ // Local testing confirms this behavior, explaining why no "onInitialization" callback is needed.
99
+ const internalContext = datadogRum . getInternalContext ( )
100
+ const sessionIsSampled = internalContext ?. session_id !== undefined
101
+
102
+ // we do not want to log anything if session is not sampled
103
+ if ( sessionIsSampled ) {
104
+ datadogLogs . init ( {
105
+ ...sharedDatadogConfig ,
106
+ site : 'datadoghq.com' ,
107
+ forwardErrorsToLogs : false ,
108
+ } )
109
+ logger . setWalletDatadogEnabled ( true )
110
+ }
111
+
112
+ try {
113
+ const userId = await getUniqueId ( )
114
+ datadogRum . setUser ( {
115
+ id : userId ,
116
+ } )
117
+ } catch ( e ) {
118
+ logger . error ( e , {
119
+ tags : { file : 'datadog.ts' , function : 'initializeDatadog' } ,
120
+ } )
121
+ }
122
+
123
+ datadogRum . setGlobalContextProperty ( 'app' , appName )
124
+
125
+ for ( const [ _ , flagKey ] of WALLET_FEATURE_FLAG_NAMES . entries ( ) ) {
126
+ datadogRum . addFeatureFlagEvaluation (
127
+ // Datadog has a limited set of accepted symbols in feature flags
128
+ // https://docs.datadoghq.com/real_user_monitoring/guide/setup-feature-flag-data-collection/?tab=reactnative#feature-flag-naming
129
+ flagKey . replaceAll ( '-' , '_' ) ,
130
+ Statsig . checkGateWithExposureLoggingDisabled ( flagKey ) ,
131
+ )
132
+ }
133
+
134
+ for ( const experiment of Object . values ( Experiments ) ) {
135
+ datadogRum . addFeatureFlagEvaluation (
136
+ // Datadog has a limited set of accepted symbols in feature flags
137
+ // https://docs.datadoghq.com/real_user_monitoring/guide/setup-feature-flag-data-collection/?tab=reactnative#feature-flag-naming
138
+ `experiment_${ experiment . replaceAll ( '-' , '_' ) } ` ,
139
+ Statsig . getExperimentWithExposureLoggingDisabled ( experiment ) . getGroupName ( ) ,
140
+ )
141
+ }
142
+ }
0 commit comments