Skip to content

Commit f4bfbf0

Browse files
committed
move async into closer to sending
1 parent 6ba2346 commit f4bfbf0

File tree

7 files changed

+118
-90
lines changed

7 files changed

+118
-90
lines changed

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ try {
2020
version: packageInfo.version,
2121
});
2222

23-
const telemetry = await Telemetry.create(session, config);
23+
const telemetry = Telemetry.create(session, config);
2424

2525
const server = new Server({
2626
mcpServer,

src/logger.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const LogId = {
2525
telemetryMetadataError: mongoLogId(1_002_005),
2626
telemetryDeviceIdFailure: mongoLogId(1_002_006),
2727
telemetryDeviceIdTimeout: mongoLogId(1_002_007),
28+
telemetryContainerEnvFailure: mongoLogId(1_002_008),
2829

2930
toolExecute: mongoLogId(1_003_001),
3031
toolExecuteFailure: mongoLogId(1_003_002),

src/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export class Server {
130130
}
131131
}
132132

133-
this.telemetry.emitEvents([event]).catch(() => {});
133+
this.telemetry.emitEvents([event]);
134134
}
135135

136136
private registerTools() {

src/telemetry/telemetry.ts

Lines changed: 83 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ async function isContainerized(): Promise<boolean> {
3939
}
4040

4141
export 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
}

src/tools/tool.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,12 @@ export abstract class ToolBase {
7474
logger.debug(LogId.toolExecute, "tool", `Executing ${this.name} with args: ${JSON.stringify(args)}`);
7575

7676
const result = await this.execute(...args);
77-
await this.emitToolEvent(startTime, result, ...args).catch(() => {});
77+
this.emitToolEvent(startTime, result, ...args);
7878
return result;
7979
} catch (error: unknown) {
8080
logger.error(LogId.toolExecuteFailure, "tool", `Error executing ${this.name}: ${error as string}`);
8181
const toolResult = await this.handleError(error, args[0] as ToolArgs<typeof this.argsShape>);
82-
await this.emitToolEvent(startTime, toolResult, ...args).catch(() => {});
82+
this.emitToolEvent(startTime, toolResult, ...args);
8383
return toolResult;
8484
}
8585
};
@@ -179,11 +179,11 @@ export abstract class ToolBase {
179179
* @param result - Whether the command succeeded or failed
180180
* @param args - The arguments passed to the tool
181181
*/
182-
private async emitToolEvent(
182+
private emitToolEvent(
183183
startTime: number,
184184
result: CallToolResult,
185185
...args: Parameters<ToolCallback<typeof this.argsShape>>
186-
): Promise<void> {
186+
): void {
187187
if (!this.telemetry.isTelemetryEnabled()) {
188188
return;
189189
}
@@ -209,6 +209,6 @@ export abstract class ToolBase {
209209
event.properties.project_id = metadata.projectId;
210210
}
211211

212-
await this.telemetry.emitEvents([event]);
212+
this.telemetry.emitEvents([event]);
213213
}
214214
}

tests/integration/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export function setupIntegrationTest(getUserConfig: () => UserConfig): Integrati
6767

6868
userConfig.telemetry = "disabled";
6969

70-
const telemetry = await Telemetry.create(session, userConfig);
70+
const telemetry = Telemetry.create(session, userConfig);
7171

7272
mcpServer = new Server({
7373
session,

0 commit comments

Comments
 (0)