@@ -39,12 +39,12 @@ async function isContainerized(): Promise<boolean> {
39
39
}
40
40
41
41
export class Telemetry {
42
- private deviceId : string | undefined ;
43
- private containerEnv : boolean | undefined ;
44
42
private deviceIdAbortController = new AbortController ( ) ;
45
43
private eventCache : EventCache ;
46
44
private getRawMachineId : ( ) => Promise < string > ;
47
45
private getContainerEnv : ( ) => Promise < boolean > ;
46
+ private cachedCommonProperties ?: CommonProperties ;
47
+ private flushing : boolean = false ;
48
48
49
49
private constructor (
50
50
private readonly session : Session ,
@@ -64,7 +64,7 @@ export class Telemetry {
64
64
this . getContainerEnv = getContainerEnv ;
65
65
}
66
66
67
- static async create (
67
+ static create (
68
68
session : Session ,
69
69
userConfig : UserConfig ,
70
70
{
@@ -76,81 +76,82 @@ export class Telemetry {
76
76
getRawMachineId ?: ( ) => Promise < string > ;
77
77
getContainerEnv ?: ( ) => Promise < boolean > ;
78
78
} = { }
79
- ) : Promise < Telemetry > {
79
+ ) : Telemetry {
80
80
const instance = new Telemetry ( session , userConfig , {
81
81
eventCache,
82
82
getRawMachineId,
83
83
getContainerEnv,
84
84
} ) ;
85
85
86
- await instance . start ( ) ;
87
86
return instance ;
88
87
}
89
88
90
- private async start ( ) : Promise < void > {
91
- if ( ! this . isTelemetryEnabled ( ) ) {
92
- return ;
93
- }
94
-
95
- const deviceIdPromise = getDeviceId ( {
96
- getMachineId : ( ) => this . getRawMachineId ( ) ,
97
- onError : ( reason , error ) => {
98
- switch ( reason ) {
99
- case "resolutionError" :
100
- logger . debug ( LogId . telemetryDeviceIdFailure , "telemetry" , String ( error ) ) ;
101
- break ;
102
- case "timeout" :
103
- logger . debug ( LogId . telemetryDeviceIdTimeout , "telemetry" , "Device ID retrieval timed out" ) ;
104
- break ;
105
- case "abort" :
106
- // No need to log in the case of aborts
107
- break ;
108
- }
109
- } ,
110
- abortSignal : this . deviceIdAbortController . signal ,
111
- } ) ;
112
- const containerEnvPromise = this . getContainerEnv ( ) ;
113
-
114
- [ this . deviceId , this . containerEnv ] = await Promise . all ( [ deviceIdPromise , containerEnvPromise ] ) ;
115
- }
116
-
117
89
public async close ( ) : Promise < void > {
118
90
this . deviceIdAbortController . abort ( ) ;
119
- await this . emitEvents ( this . eventCache . getEvents ( ) ) ;
91
+ await this . flush ( this . eventCache . getEvents ( ) ) ;
120
92
}
121
93
122
94
/**
123
95
* Emits events through the telemetry pipeline
124
96
* @param events - The events to emit
125
97
*/
126
- public async emitEvents ( events : BaseEvent [ ] ) : Promise < void > {
127
- try {
128
- if ( ! this . isTelemetryEnabled ( ) ) {
129
- logger . info ( LogId . telemetryEmitFailure , "telemetry" , `Telemetry is disabled.` ) ;
130
- return ;
131
- }
132
-
133
- await this . emit ( events ) ;
134
- } catch {
135
- logger . debug ( LogId . telemetryEmitFailure , "telemetry" , `Error emitting telemetry events.` ) ;
136
- }
98
+ public emitEvents ( events : BaseEvent [ ] ) : void {
99
+ void this . flush ( events ) ;
137
100
}
138
101
139
102
/**
140
103
* Gets the common properties for events
141
104
* @returns Object containing common properties for all events
142
105
*/
143
- private getCommonProperties ( ) : CommonProperties {
144
- return {
145
- ...MACHINE_METADATA ,
146
- mcp_client_version : this . session . agentRunner ?. version ,
147
- mcp_client_name : this . session . agentRunner ?. name ,
148
- session_id : this . session . sessionId ,
149
- config_atlas_auth : this . session . apiClient . hasCredentials ( ) ? "true" : "false" ,
150
- config_connection_string : this . userConfig . connectionString ? "true" : "false" ,
151
- is_container_env : this . containerEnv ? "true" : "false" ,
152
- device_id : this . deviceId ,
153
- } ;
106
+ private async getCommonProperties ( ) : Promise < CommonProperties > {
107
+ if ( ! this . cachedCommonProperties ) {
108
+ let deviceId : string | undefined ;
109
+ try {
110
+ deviceId = await getDeviceId ( {
111
+ getMachineId : ( ) => this . getRawMachineId ( ) ,
112
+ onError : ( reason , error ) => {
113
+ switch ( reason ) {
114
+ case "resolutionError" :
115
+ logger . debug ( LogId . telemetryDeviceIdFailure , "telemetry" , String ( error ) ) ;
116
+ break ;
117
+ case "timeout" :
118
+ logger . debug (
119
+ LogId . telemetryDeviceIdTimeout ,
120
+ "telemetry" ,
121
+ "Device ID retrieval timed out"
122
+ ) ;
123
+ break ;
124
+ case "abort" :
125
+ // No need to log in the case of aborts
126
+ break ;
127
+ }
128
+ } ,
129
+ abortSignal : this . deviceIdAbortController . signal ,
130
+ } ) ;
131
+ } catch ( error : unknown ) {
132
+ const err = error instanceof Error ? error : new Error ( String ( error ) ) ;
133
+ logger . debug ( LogId . telemetryDeviceIdFailure , "telemetry" , err . message ) ;
134
+ }
135
+ let containerEnv : boolean | undefined ;
136
+ try {
137
+ containerEnv = await this . getContainerEnv ( ) ;
138
+ } catch ( error : unknown ) {
139
+ const err = error instanceof Error ? error : new Error ( String ( error ) ) ;
140
+ logger . debug ( LogId . telemetryContainerEnvFailure , "telemetry" , err . message ) ;
141
+ }
142
+ this . cachedCommonProperties = {
143
+ ...MACHINE_METADATA ,
144
+ mcp_client_version : this . session . agentRunner ?. version ,
145
+ mcp_client_name : this . session . agentRunner ?. name ,
146
+ session_id : this . session . sessionId ,
147
+ config_atlas_auth : this . session . apiClient . hasCredentials ( ) ? "true" : "false" ,
148
+ config_connection_string : this . userConfig . connectionString ? "true" : "false" ,
149
+ is_container_env : containerEnv ? "true" : "false" ,
150
+ device_id : deviceId ,
151
+ } ;
152
+ }
153
+
154
+ return this . cachedCommonProperties ;
154
155
}
155
156
156
157
/**
@@ -171,20 +172,32 @@ export class Telemetry {
171
172
}
172
173
173
174
/**
174
- * Attempts to emit events through authenticated and unauthenticated clients
175
+ * Attempts to flush events through authenticated and unauthenticated clients
175
176
* Falls back to caching if both attempts fail
176
177
*/
177
- private async emit ( events : BaseEvent [ ] ) : Promise < void > {
178
- const cachedEvents = this . eventCache . getEvents ( ) ;
179
- const allEvents = [ ...cachedEvents , ...events ] ;
180
-
181
- logger . debug (
182
- LogId . telemetryEmitStart ,
183
- "telemetry" ,
184
- `Attempting to send ${ allEvents . length } events (${ cachedEvents . length } cached)`
185
- ) ;
178
+ private async flush ( events : BaseEvent [ ] ) : Promise < void > {
179
+ if ( ! this . isTelemetryEnabled ( ) ) {
180
+ logger . info ( LogId . telemetryEmitFailure , "telemetry" , `Telemetry is disabled.` ) ;
181
+ return ;
182
+ }
183
+
184
+ if ( this . flushing ) {
185
+ this . eventCache . appendEvents ( events ) ;
186
+ return ;
187
+ }
188
+
189
+ this . flushing = true ;
186
190
187
191
try {
192
+ const cachedEvents = this . eventCache . getEvents ( ) ;
193
+ const allEvents = [ ...cachedEvents , ...events ] ;
194
+
195
+ logger . debug (
196
+ LogId . telemetryEmitStart ,
197
+ "telemetry" ,
198
+ `Attempting to send ${ allEvents . length } events (${ cachedEvents . length } cached)`
199
+ ) ;
200
+
188
201
await this . sendEvents ( this . session . apiClient , allEvents ) ;
189
202
this . eventCache . clearEvents ( ) ;
190
203
logger . debug (
@@ -200,16 +213,20 @@ export class Telemetry {
200
213
) ;
201
214
this . eventCache . appendEvents ( events ) ;
202
215
}
216
+
217
+ this . flushing = false ;
203
218
}
204
219
205
220
/**
206
221
* Attempts to send events through the provided API client
207
222
*/
208
223
private async sendEvents ( client : ApiClient , events : BaseEvent [ ] ) : Promise < void > {
224
+ const commonProperties = await this . getCommonProperties ( ) ;
225
+
209
226
await client . sendEvents (
210
227
events . map ( ( event ) => ( {
211
228
...event ,
212
- properties : { ...this . getCommonProperties ( ) , ...event . properties } ,
229
+ properties : { ...commonProperties , ...event . properties } ,
213
230
} ) )
214
231
) ;
215
232
}
0 commit comments