@@ -39,12 +39,12 @@ async function isContainerized(): Promise<boolean> {
3939}
4040
4141export class Telemetry {
42- private deviceId : string | undefined ;
43- private containerEnv : boolean | undefined ;
4442 private deviceIdAbortController = new AbortController ( ) ;
4543 private eventCache : EventCache ;
4644 private getRawMachineId : ( ) => Promise < string > ;
4745 private getContainerEnv : ( ) => Promise < boolean > ;
46+ private cachedCommonProperties ?: CommonProperties ;
47+ private flushing : boolean = false ;
4848
4949 private constructor (
5050 private readonly session : Session ,
@@ -64,7 +64,7 @@ export class Telemetry {
6464 this . getContainerEnv = getContainerEnv ;
6565 }
6666
67- static async create (
67+ static create (
6868 session : Session ,
6969 userConfig : UserConfig ,
7070 {
@@ -76,81 +76,82 @@ export class Telemetry {
7676 getRawMachineId ?: ( ) => Promise < string > ;
7777 getContainerEnv ?: ( ) => Promise < boolean > ;
7878 } = { }
79- ) : Promise < Telemetry > {
79+ ) : Telemetry {
8080 const instance = new Telemetry ( session , userConfig , {
8181 eventCache,
8282 getRawMachineId,
8383 getContainerEnv,
8484 } ) ;
8585
86- await instance . start ( ) ;
8786 return instance ;
8887 }
8988
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-
11789 public async close ( ) : Promise < void > {
11890 this . deviceIdAbortController . abort ( ) ;
119- await this . emitEvents ( this . eventCache . getEvents ( ) ) ;
91+ await this . flush ( this . eventCache . getEvents ( ) ) ;
12092 }
12193
12294 /**
12395 * Emits events through the telemetry pipeline
12496 * @param events - The events to emit
12597 */
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 ) ;
137100 }
138101
139102 /**
140103 * Gets the common properties for events
141104 * @returns Object containing common properties for all events
142105 */
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 ;
154155 }
155156
156157 /**
@@ -171,20 +172,32 @@ export class Telemetry {
171172 }
172173
173174 /**
174- * Attempts to emit events through authenticated and unauthenticated clients
175+ * Attempts to flush events through authenticated and unauthenticated clients
175176 * Falls back to caching if both attempts fail
176177 */
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 ;
186190
187191 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+
188201 await this . sendEvents ( this . session . apiClient , allEvents ) ;
189202 this . eventCache . clearEvents ( ) ;
190203 logger . debug (
@@ -200,16 +213,20 @@ export class Telemetry {
200213 ) ;
201214 this . eventCache . appendEvents ( events ) ;
202215 }
216+
217+ this . flushing = false ;
203218 }
204219
205220 /**
206221 * Attempts to send events through the provided API client
207222 */
208223 private async sendEvents ( client : ApiClient , events : BaseEvent [ ] ) : Promise < void > {
224+ const commonProperties = await this . getCommonProperties ( ) ;
225+
209226 await client . sendEvents (
210227 events . map ( ( event ) => ( {
211228 ...event ,
212- properties : { ...this . getCommonProperties ( ) , ...event . properties } ,
229+ properties : { ...commonProperties , ...event . properties } ,
213230 } ) )
214231 ) ;
215232 }
0 commit comments