• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mongodb-js / mongodb-mcp-server / 17486339999

05 Sep 2025 07:10AM UTC coverage: 81.12% (-0.07%) from 81.188%
17486339999

Pull #521

github

web-flow
Merge 724add974 into 1f68c3e5d
Pull Request #521: fix: don't wait for telemetry events

925 of 1235 branches covered (74.9%)

Branch coverage included in aggregate %.

49 of 60 new or added lines in 4 files covered. (81.67%)

1 existing line in 1 file now uncovered.

4682 of 5677 relevant lines covered (82.47%)

90.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.03
/src/telemetry/telemetry.ts
1
import type { Session } from "../common/session.js";
2
import type { BaseEvent, CommonProperties } from "./types.js";
3
import type { UserConfig } from "../common/config.js";
4
import { LogId } from "../common/logger.js";
4✔
5
import type { ApiClient } from "../common/atlas/apiClient.js";
6
import { MACHINE_METADATA } from "./constants.js";
4✔
7
import { EventCache } from "./eventCache.js";
4✔
8
import { detectContainerEnv } from "../helpers/container.js";
4✔
9
import type { DeviceId } from "../helpers/deviceId.js";
10
import { EventEmitter } from "events";
4✔
11

12
type EventResult = {
13
    success: boolean;
14
    error?: Error;
15
};
16

17
export interface TelemetryEvents {
18
    "events-emitted": [];
19
    "events-send-failed": [];
20
    "events-skipped": [];
21
}
22

23
export class Telemetry {
4✔
24
    private isBufferingEvents: boolean = true;
4✔
25
    /** Resolves when the setup is complete or a timeout occurs */
26
    public setupPromise: Promise<[string, boolean]> | undefined;
27
    public readonly events: EventEmitter<TelemetryEvents> = new EventEmitter();
4✔
28

29
    private eventCache: EventCache;
30
    private deviceId: DeviceId;
31

32
    private constructor(
4✔
33
        private readonly session: Session,
156✔
34
        private readonly userConfig: UserConfig,
156✔
35
        private readonly commonProperties: CommonProperties,
156✔
36
        { eventCache, deviceId }: { eventCache: EventCache; deviceId: DeviceId }
156✔
37
    ) {
156✔
38
        this.eventCache = eventCache;
156✔
39
        this.deviceId = deviceId;
156✔
40
    }
156✔
41

42
    static create(
4✔
43
        session: Session,
156✔
44
        userConfig: UserConfig,
156✔
45
        deviceId: DeviceId,
156✔
46
        {
156✔
47
            commonProperties = {},
156✔
48
            eventCache = EventCache.getInstance(),
156✔
49
        }: {
156✔
50
            commonProperties?: Partial<CommonProperties>;
51
            eventCache?: EventCache;
52
        } = {}
156✔
53
    ): Telemetry {
156✔
54
        const mergedProperties = {
156✔
55
            ...MACHINE_METADATA,
156✔
56
            ...commonProperties,
156✔
57
        };
156✔
58
        const instance = new Telemetry(session, userConfig, mergedProperties, {
156✔
59
            eventCache,
156✔
60
            deviceId,
156✔
61
        });
156✔
62

63
        void instance.setup();
156✔
64
        return instance;
156✔
65
    }
156✔
66

67
    private async setup(): Promise<void> {
4✔
68
        if (!this.isTelemetryEnabled()) {
156✔
69
            this.session.logger.info({
120✔
70
                id: LogId.telemetryEmitFailure,
120✔
71
                context: "telemetry",
120✔
72
                message: "Telemetry is disabled.",
120✔
73
                noRedaction: true,
120✔
74
            });
120✔
75
            return;
120✔
76
        }
120!
77

78
        this.setupPromise = Promise.all([this.deviceId.get(), detectContainerEnv()]);
36✔
79
        const [deviceIdValue, containerEnv] = await this.setupPromise;
36✔
80

81
        this.commonProperties.device_id = deviceIdValue;
36✔
82
        this.commonProperties.is_container_env = containerEnv;
36✔
83

84
        this.isBufferingEvents = false;
36✔
85
    }
156✔
86

87
    public async close(): Promise<void> {
4✔
88
        this.isBufferingEvents = false;
122✔
89

90
        // Wait up to 5 seconds for events to be sent before closing, but don't throw if it times out
91
        await Promise.race([new Promise((resolve) => setTimeout(resolve, 5000)), this.emit([])]);
122✔
92
    }
122✔
93

94
    /**
95
     * Emits events through the telemetry pipeline
96
     * @param events - The events to emit
97
     */
98
    public emitEvents(events: BaseEvent[]): void {
4✔
99
        if (!this.isTelemetryEnabled()) {
256✔
100
            this.events.emit("events-skipped");
240✔
101
            return;
240✔
102
        }
240!
103

104
        // Don't wait for events to be sent - we should not block regular server
105
        // operations on telemetry
106
        void this.emit(events);
16✔
107
    }
256✔
108

109
    /**
110
     * Gets the common properties for events
111
     * @returns Object containing common properties for all events
112
     */
113
    public getCommonProperties(): CommonProperties {
4✔
114
        return {
46✔
115
            ...this.commonProperties,
46✔
116
            transport: this.userConfig.transport,
46✔
117
            mcp_client_version: this.session.mcpClient?.version,
46✔
118
            mcp_client_name: this.session.mcpClient?.name,
46✔
119
            session_id: this.session.sessionId,
46✔
120
            config_atlas_auth: this.session.apiClient.hasCredentials() ? "true" : "false",
46✔
121
            config_connection_string: this.userConfig.connectionString ? "true" : "false",
46!
122
        };
46✔
123
    }
46✔
124

125
    /**
126
     * Checks if telemetry is currently enabled
127
     * This is a method rather than a constant to capture runtime config changes
128
     *
129
     * Follows the Console Do Not Track standard (https://consoledonottrack.com/)
130
     * by respecting the DO_NOT_TRACK environment variable
131
     */
132
    public isTelemetryEnabled(): boolean {
4✔
133
        // Check if telemetry is explicitly disabled in config
134
        if (this.userConfig.telemetry === "disabled") {
1,100✔
135
            return false;
1,046✔
136
        }
1,046!
137

138
        const doNotTrack = "DO_NOT_TRACK" in process.env;
54✔
139
        return !doNotTrack;
54✔
140
    }
1,100✔
141

142
    /**
143
     * Attempts to emit events through authenticated and unauthenticated clients
144
     * Falls back to caching if both attempts fail
145
     */
146
    private async emit(events: BaseEvent[]): Promise<void> {
4✔
147
        if (this.isBufferingEvents) {
138!
148
            this.eventCache.appendEvents(events);
×
149
            return;
×
150
        }
×
151

152
        try {
138✔
153
            const cachedEvents = this.eventCache.getEvents();
138✔
154
            const allEvents = [...cachedEvents, ...events];
138✔
155

156
            this.session.logger.debug({
138✔
157
                id: LogId.telemetryEmitStart,
138✔
158
                context: "telemetry",
138✔
159
                message: `Attempting to send ${allEvents.length} events (${cachedEvents.length} cached)`,
138✔
160
            });
138✔
161

162
            const result = await this.sendEvents(this.session.apiClient, allEvents);
138✔
163
            if (result.success) {
138✔
164
                this.eventCache.clearEvents();
136✔
165
                this.session.logger.debug({
136✔
166
                    id: LogId.telemetryEmitSuccess,
136✔
167
                    context: "telemetry",
136✔
168
                    message: `Sent ${allEvents.length} events successfully: ${JSON.stringify(allEvents, null, 2)}`,
136✔
169
                });
136✔
170
                this.events.emit("events-emitted");
136✔
171
                return;
136✔
172
            }
136!
173

174
            this.session.logger.debug({
2✔
175
                id: LogId.telemetryEmitFailure,
2✔
176
                context: "telemetry",
2✔
177
                message: `Error sending event to client: ${result.error instanceof Error ? result.error.message : String(result.error)}`,
138!
178
            });
138✔
179
            this.eventCache.appendEvents(events);
138✔
180
            this.events.emit("events-send-failed");
138✔
181
        } catch (error) {
138!
NEW
182
            this.session.logger.debug({
×
NEW
183
                id: LogId.telemetryEmitFailure,
×
NEW
184
                context: "telemetry",
×
NEW
185
                message: `Error emitting telemetry events: ${error instanceof Error ? error.message : String(error)}`,
×
NEW
186
                noRedaction: true,
×
NEW
187
            });
×
NEW
188
            this.events.emit("events-send-failed");
×
UNCOV
189
        }
×
190
    }
138✔
191

192
    /**
193
     * Attempts to send events through the provided API client
194
     */
195
    private async sendEvents(client: ApiClient, events: BaseEvent[]): Promise<EventResult> {
4✔
196
        try {
138✔
197
            await client.sendEvents(
138✔
198
                events.map((event) => ({
138✔
199
                    ...event,
18✔
200
                    properties: { ...this.getCommonProperties(), ...event.properties },
18✔
201
                }))
138✔
202
            );
138✔
203
            return { success: true };
136✔
204
        } catch (error) {
138!
205
            return {
2✔
206
                success: false,
2✔
207
                error: error instanceof Error ? error : new Error(String(error)),
2!
208
            };
2✔
209
        }
2✔
210
    }
138✔
211
}
4✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc