Skip to content

Commit 40aaef0

Browse files
committed
wip: simple approach
1 parent af714b4 commit 40aaef0

File tree

3 files changed

+57
-22
lines changed

3 files changed

+57
-22
lines changed

src/telemetry/telemetry.ts

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import logger from '../logger.js';
66
import { mongoLogId } from 'mongodb-log-writer';
77
import { ApiClient } from '../common/atlas/apiClient.js';
88
import { ApiClientError } from '../common/atlas/apiClientError.js';
9+
import fs from 'fs/promises';
10+
import path from 'path';
11+
12+
const isTelemetryEnabled = config.telemetry === 'enabled';
13+
const CACHE_FILE = path.join(process.cwd(), '.telemetry-cache.json');
914

1015
export class Telemetry {
1116
constructor(private readonly session: Session) {}
@@ -23,10 +28,8 @@ export class Telemetry {
2328
os_version: config.os_version,
2429
};
2530

26-
private readonly isTelemetryEnabled = config.telemetry === 'enabled';
27-
2831
async emitToolEvent(command: string, category: string, startTime: number, result: 'success' | 'failure', error?: Error): Promise<void> {
29-
if (!this.isTelemetryEnabled) {
32+
if (!isTelemetryEnabled) {
3033
logger.debug(mongoLogId(1_000_000), "telemetry", `Telemetry is disabled, skipping event.`);
3134
return;
3235
}
@@ -50,26 +53,72 @@ export class Telemetry {
5053
event.properties.error_code = error?.message;
5154
}
5255

53-
await this.emit(event);
56+
await this.emit([event]);
5457
}
5558

56-
private async emit(event: BaseEvent): Promise<void> {
59+
private async emit(events: BaseEvent[]): Promise<void> {
60+
// First try to read any cached events
61+
const cachedEvents = await this.readCache();
62+
const allEvents = [...cachedEvents, ...events];
63+
64+
logger.debug(mongoLogId(1_000_000), "telemetry", `Attempting to send ${allEvents.length} events (${cachedEvents.length} cached)`);
65+
5766
try {
5867
if (this.session.apiClient) {
59-
await this.session.apiClient.sendEvents([event]);
68+
await this.session.apiClient.sendEvents(allEvents);
69+
// If successful, clear the cache
70+
await this.clearCache();
71+
return;
6072
}
6173
} catch (error) {
6274
logger.warning(mongoLogId(1_000_000), "telemetry", `Error sending event to authenticated client: ${error}`);
75+
// Cache the events that failed to send
76+
await this.cacheEvents(allEvents);
6377
}
6478

65-
// if it is unauthenticated, send to temp client
79+
// Try unauthenticated client as fallback
6680
try {
6781
const tempApiClient = new ApiClient({
6882
baseUrl: config.apiBaseUrl,
6983
});
70-
await tempApiClient.sendEvents([event]);
84+
await tempApiClient.sendEvents(allEvents);
85+
// If successful, clear the cache
86+
await this.clearCache();
7187
} catch (error) {
7288
logger.warning(mongoLogId(1_000_000), "telemetry", `Error sending event to unauthenticated client: ${error}`);
89+
// Cache the events that failed to send
90+
await this.cacheEvents(allEvents);
91+
}
92+
}
93+
94+
private async readCache(): Promise<BaseEvent[]> {
95+
try {
96+
const data = await fs.readFile(CACHE_FILE, 'utf-8');
97+
return JSON.parse(data);
98+
} catch (error) {
99+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
100+
logger.warning(mongoLogId(1_000_000), "telemetry", `Error reading telemetry cache: ${error}`);
101+
}
102+
return [];
103+
}
104+
}
105+
106+
private async cacheEvents(events: BaseEvent[]): Promise<void> {
107+
try {
108+
await fs.writeFile(CACHE_FILE, JSON.stringify(events, null, 2));
109+
logger.debug(mongoLogId(1_000_000), "telemetry", `Cached ${events.length} events for later sending`);
110+
} catch (error) {
111+
logger.warning(mongoLogId(1_000_000), "telemetry", `Failed to cache telemetry events: ${error}`);
112+
}
113+
}
114+
115+
private async clearCache(): Promise<void> {
116+
try {
117+
await fs.unlink(CACHE_FILE);
118+
} catch (error) {
119+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
120+
logger.warning(mongoLogId(1_000_000), "telemetry", `Error clearing telemetry cache: ${error}`);
121+
}
73122
}
74123
}
75124
}

src/tools/atlas/atlasTool.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { ToolBase, ToolCategory } from "../tool.js";
22
import { Session } from "../../session.js";
33

44
export abstract class AtlasToolBase extends ToolBase {
5-
protected category = "atlas";
65
constructor(protected readonly session: Session) {
76
super(session);
87
}

src/tools/tool.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,15 @@ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
44
import { Session } from "../session.js";
55
import logger from "../logger.js";
66
import { mongoLogId } from "mongodb-log-writer";
7-
<<<<<<< HEAD
87
import config from "../config.js";
9-
=======
108
import { Telemetry } from "../telemetry/telemetry.js";
11-
>>>>>>> 61e295a (wip)
129

1310
export type ToolArgs<Args extends ZodRawShape> = z.objectOutputType<Args, ZodNever>;
1411

1512
export type OperationType = "metadata" | "read" | "create" | "delete" | "update" | "cluster";
1613
export type ToolCategory = "mongodb" | "atlas";
1714

1815
export abstract class ToolBase {
19-
<<<<<<< HEAD
2016
protected abstract name: string;
2117

2218
protected abstract category: ToolCategory;
@@ -26,13 +22,7 @@ export abstract class ToolBase {
2622
protected abstract description: string;
2723

2824
protected abstract argsShape: ZodRawShape;
29-
=======
30-
protected abstract readonly name: string;
31-
protected abstract readonly description: string;
32-
protected abstract readonly argsShape: ZodRawShape;
33-
>>>>>>> 61e295a (wip)
3425

35-
protected abstract category: string;
3626
private readonly telemetry: Telemetry;
3727

3828
protected abstract execute(...args: Parameters<ToolCallback<typeof this.argsShape>>): Promise<CallToolResult>;
@@ -76,7 +66,6 @@ export abstract class ToolBase {
7666
server.tool(this.name, this.description, this.argsShape, callback);
7767
}
7868

79-
<<<<<<< HEAD
8069
// Checks if a tool is allowed to run based on the config
8170
private verifyAllowed(): boolean {
8271
let errorClarification: string | undefined;
@@ -101,8 +90,6 @@ export abstract class ToolBase {
10190
return true;
10291
}
10392

104-
=======
105-
>>>>>>> 61e295a (wip)
10693
// This method is intended to be overridden by subclasses to handle errors
10794
protected handleError(error: unknown): Promise<CallToolResult> | CallToolResult {
10895
return {

0 commit comments

Comments
 (0)