@@ -7,16 +7,25 @@ import { MACHINE_METADATA } from "./constants.js";
7
7
import { EventCache } from "./eventCache.js" ;
8
8
import { detectContainerEnv } from "../helpers/container.js" ;
9
9
import type { DeviceId } from "../helpers/deviceId.js" ;
10
+ import { EventEmitter } from "events" ;
10
11
11
12
type EventResult = {
12
13
success : boolean ;
13
14
error ?: Error ;
14
15
} ;
15
16
17
+ export interface TelemetryEvents {
18
+ "events-emitted" : [ ] ;
19
+ "events-send-failed" : [ ] ;
20
+ "events-skipped" : [ ] ;
21
+ }
22
+
16
23
export class Telemetry {
17
24
private isBufferingEvents : boolean = true ;
18
25
/** Resolves when the setup is complete or a timeout occurs */
19
26
public setupPromise : Promise < [ string , boolean ] > | undefined ;
27
+ public readonly events : EventEmitter < TelemetryEvents > = new EventEmitter ( ) ;
28
+
20
29
private eventCache : EventCache ;
21
30
private deviceId : DeviceId ;
22
31
@@ -57,6 +66,12 @@ export class Telemetry {
57
66
58
67
private async setup ( ) : Promise < void > {
59
68
if ( ! this . isTelemetryEnabled ( ) ) {
69
+ this . session . logger . info ( {
70
+ id : LogId . telemetryEmitFailure ,
71
+ context : "telemetry" ,
72
+ message : "Telemetry is disabled." ,
73
+ noRedaction : true ,
74
+ } ) ;
60
75
return ;
61
76
}
62
77
@@ -71,34 +86,47 @@ export class Telemetry {
71
86
72
87
public async close ( ) : Promise < void > {
73
88
this . isBufferingEvents = false ;
74
- await this . emitEvents ( this . eventCache . getEvents ( ) ) ;
89
+
90
+ this . session . logger . debug ( {
91
+ id : LogId . telemetryClose ,
92
+ message : `Closing telemetry and flushing ${ this . eventCache . size } events` ,
93
+ context : "telemetry" ,
94
+ } ) ;
95
+
96
+ // Wait up to 5 seconds for events to be sent before closing, but don't throw if it times out
97
+ const flushMaxWaitTime = 5000 ;
98
+ let flushTimeout : NodeJS . Timeout | undefined ;
99
+ await Promise . race ( [
100
+ new Promise < void > ( ( resolve ) => {
101
+ flushTimeout = setTimeout ( ( ) => {
102
+ this . session . logger . debug ( {
103
+ id : LogId . telemetryClose ,
104
+ message : `Failed to flush remaining events within ${ flushMaxWaitTime } ms timeout` ,
105
+ context : "telemetry" ,
106
+ } ) ;
107
+ resolve ( ) ;
108
+ } , flushMaxWaitTime ) ;
109
+ flushTimeout . unref ( ) ;
110
+ } ) ,
111
+ this . emit ( [ ] ) ,
112
+ ] ) ;
113
+
114
+ clearTimeout ( flushTimeout ) ;
75
115
}
76
116
77
117
/**
78
118
* Emits events through the telemetry pipeline
79
119
* @param events - The events to emit
80
120
*/
81
- public async emitEvents ( events : BaseEvent [ ] ) : Promise < void > {
82
- try {
83
- if ( ! this . isTelemetryEnabled ( ) ) {
84
- this . session . logger . info ( {
85
- id : LogId . telemetryEmitFailure ,
86
- context : "telemetry" ,
87
- message : "Telemetry is disabled." ,
88
- noRedaction : true ,
89
- } ) ;
90
- return ;
91
- }
92
-
93
- await this . emit ( events ) ;
94
- } catch {
95
- this . session . logger . debug ( {
96
- id : LogId . telemetryEmitFailure ,
97
- context : "telemetry" ,
98
- message : "Error emitting telemetry events." ,
99
- noRedaction : true ,
100
- } ) ;
121
+ public emitEvents ( events : BaseEvent [ ] ) : void {
122
+ if ( ! this . isTelemetryEnabled ( ) ) {
123
+ this . events . emit ( "events-skipped" ) ;
124
+ return ;
101
125
}
126
+
127
+ // Don't wait for events to be sent - we should not block regular server
128
+ // operations on telemetry
129
+ void this . emit ( events ) ;
102
130
}
103
131
104
132
/**
@@ -144,32 +172,44 @@ export class Telemetry {
144
172
return ;
145
173
}
146
174
147
- const cachedEvents = this . eventCache . getEvents ( ) ;
148
- const allEvents = [ ...cachedEvents , ...events ] ;
175
+ try {
176
+ const cachedEvents = this . eventCache . getEvents ( ) ;
177
+ const allEvents = [ ...cachedEvents . map ( ( e ) => e . event ) , ...events ] ;
149
178
150
- this . session . logger . debug ( {
151
- id : LogId . telemetryEmitStart ,
152
- context : "telemetry" ,
153
- message : `Attempting to send ${ allEvents . length } events (${ cachedEvents . length } cached)` ,
154
- } ) ;
179
+ this . session . logger . debug ( {
180
+ id : LogId . telemetryEmitStart ,
181
+ context : "telemetry" ,
182
+ message : `Attempting to send ${ allEvents . length } events (${ cachedEvents . length } cached)` ,
183
+ } ) ;
184
+
185
+ const result = await this . sendEvents ( this . session . apiClient , allEvents ) ;
186
+ if ( result . success ) {
187
+ this . eventCache . removeEvents ( cachedEvents . map ( ( e ) => e . id ) ) ;
188
+ this . session . logger . debug ( {
189
+ id : LogId . telemetryEmitSuccess ,
190
+ context : "telemetry" ,
191
+ message : `Sent ${ allEvents . length } events successfully: ${ JSON . stringify ( allEvents ) } ` ,
192
+ } ) ;
193
+ this . events . emit ( "events-emitted" ) ;
194
+ return ;
195
+ }
155
196
156
- const result = await this . sendEvents ( this . session . apiClient , allEvents ) ;
157
- if ( result . success ) {
158
- this . eventCache . clearEvents ( ) ;
159
197
this . session . logger . debug ( {
160
- id : LogId . telemetryEmitSuccess ,
198
+ id : LogId . telemetryEmitFailure ,
161
199
context : "telemetry" ,
162
- message : `Sent ${ allEvents . length } events successfully : ${ JSON . stringify ( allEvents , null , 2 ) } ` ,
200
+ message : `Error sending event to client : ${ result . error instanceof Error ? result . error . message : String ( result . error ) } ` ,
163
201
} ) ;
164
- return ;
202
+ this . eventCache . appendEvents ( events ) ;
203
+ this . events . emit ( "events-send-failed" ) ;
204
+ } catch ( error ) {
205
+ this . session . logger . debug ( {
206
+ id : LogId . telemetryEmitFailure ,
207
+ context : "telemetry" ,
208
+ message : `Error emitting telemetry events: ${ error instanceof Error ? error . message : String ( error ) } ` ,
209
+ noRedaction : true ,
210
+ } ) ;
211
+ this . events . emit ( "events-send-failed" ) ;
165
212
}
166
-
167
- this . session . logger . debug ( {
168
- id : LogId . telemetryEmitFailure ,
169
- context : "telemetry" ,
170
- message : `Error sending event to client: ${ result . error instanceof Error ? result . error . message : String ( result . error ) } ` ,
171
- } ) ;
172
- this . eventCache . appendEvents ( events ) ;
173
213
}
174
214
175
215
/**
0 commit comments