-
Notifications
You must be signed in to change notification settings - Fork 89
feat: update connectionString appName param - [MCP-68] #406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c5c91e9
4cf78c2
026b91a
680e1e1
92aab61
eca40e1
6cf0ae6
524d965
72f1ab8
c3f0928
5669609
ff80ff5
251414c
10f7054
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,42 @@ | ||
import { MongoClientOptions } from "mongodb"; | ||
import ConnectionString from "mongodb-connection-string-url"; | ||
import { getDeviceIdForConnection } from "./deviceId.js"; | ||
|
||
export function setAppNameParamIfMissing({ | ||
export interface AppNameComponents { | ||
appName: string; | ||
deviceId?: string; | ||
clientName?: string; | ||
} | ||
|
||
/** | ||
* Sets the appName parameter with the extended format: appName--deviceId--clientName | ||
* Only sets the appName if it's not already present in the connection string | ||
* @param connectionString - The connection string to modify | ||
* @param components - The components to build the appName from | ||
* @returns The modified connection string | ||
*/ | ||
export async function setAppNameParamIfMissing({ | ||
connectionString, | ||
defaultAppName, | ||
components, | ||
}: { | ||
connectionString: string; | ||
defaultAppName?: string; | ||
}): string { | ||
components: AppNameComponents; | ||
}): Promise<string> { | ||
const connectionStringUrl = new ConnectionString(connectionString); | ||
|
||
const searchParams = connectionStringUrl.typedSearchParams<MongoClientOptions>(); | ||
|
||
if (!searchParams.has("appName") && defaultAppName !== undefined) { | ||
searchParams.set("appName", defaultAppName); | ||
// Only set appName if it's not already present | ||
if (searchParams.has("appName")) { | ||
return connectionStringUrl.toString(); | ||
} | ||
|
||
const deviceId = components.deviceId || (await getDeviceIdForConnection()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we shouldn't end up awaiting device ID a second time, can we organize it so this device ID gets passed from one point of entry and we never fallback to this function? |
||
const clientName = components.clientName || "unknown"; | ||
|
||
// Build the extended appName format: appName--deviceId--clientName | ||
const extendedAppName = `${components.appName}--${deviceId}--${clientName}`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [q] what if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see the code we are getting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the previous behaviour is that we would set it to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how would appName be defined? is it really a parameter? is there an env var? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
searchParams.set("appName", extendedAppName); | ||
|
||
return connectionStringUrl.toString(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { getDeviceId } from "@mongodb-js/device-id"; | ||
import nodeMachineId from "node-machine-id"; | ||
import logger, { LogId } from "../common/logger.js"; | ||
|
||
export const DEVICE_ID_TIMEOUT = 3000; | ||
|
||
/** | ||
* Retrieves the device ID for telemetry purposes. | ||
* The device ID is generated using the machine ID and additional logic to handle errors. | ||
* | ||
* @returns Promise that resolves to the device ID string | ||
* If an error occurs during retrieval, the function returns "unknown". | ||
* | ||
* @example | ||
* ```typescript | ||
* const deviceId = await getDeviceIdForConnection(); | ||
* console.log(deviceId); // Outputs the device ID or "unknown" in case of failure | ||
* ``` | ||
*/ | ||
export async function getDeviceIdForConnection(): Promise<string> { | ||
const controller = new AbortController(); | ||
|
||
try { | ||
const deviceId = await getDeviceId({ | ||
getMachineId: () => nodeMachineId.machineId(true), | ||
onError: (reason, error) => { | ||
switch (reason) { | ||
case "resolutionError": | ||
logger.debug(LogId.telemetryDeviceIdFailure, "deviceId", String(error)); | ||
break; | ||
case "timeout": | ||
logger.debug(LogId.telemetryDeviceIdTimeout, "deviceId", "Device ID retrieval timed out"); | ||
break; | ||
case "abort": | ||
// No need to log in the case of aborts | ||
break; | ||
} | ||
}, | ||
abortSignal: controller.signal, | ||
}); | ||
return deviceId; | ||
} catch (error) { | ||
logger.debug(LogId.telemetryDeviceIdFailure, "deviceId", `Failed to get device ID: ${String(error)}`); | ||
return "unknown"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,33 +5,27 @@ import logger, { LogId } from "../common/logger.js"; | |
import { ApiClient } from "../common/atlas/apiClient.js"; | ||
import { MACHINE_METADATA } from "./constants.js"; | ||
import { EventCache } from "./eventCache.js"; | ||
import nodeMachineId from "node-machine-id"; | ||
import { getDeviceId } from "@mongodb-js/device-id"; | ||
import { getDeviceIdForConnection } from "../helpers/deviceId.js"; | ||
import { detectContainerEnv } from "../helpers/container.js"; | ||
|
||
type EventResult = { | ||
success: boolean; | ||
error?: Error; | ||
}; | ||
|
||
export const DEVICE_ID_TIMEOUT = 3000; | ||
|
||
export class Telemetry { | ||
private isBufferingEvents: boolean = true; | ||
/** Resolves when the setup is complete or a timeout occurs */ | ||
public setupPromise: Promise<[string, boolean]> | undefined; | ||
private deviceIdAbortController = new AbortController(); | ||
private eventCache: EventCache; | ||
private getRawMachineId: () => Promise<string>; | ||
|
||
private constructor( | ||
private readonly session: Session, | ||
private readonly userConfig: UserConfig, | ||
private readonly commonProperties: CommonProperties, | ||
{ eventCache, getRawMachineId }: { eventCache: EventCache; getRawMachineId: () => Promise<string> } | ||
{ eventCache }: { eventCache: EventCache } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I think it'd still be good to pass |
||
) { | ||
this.eventCache = eventCache; | ||
this.getRawMachineId = getRawMachineId; | ||
} | ||
|
||
static create( | ||
|
@@ -40,14 +34,12 @@ export class Telemetry { | |
{ | ||
commonProperties = { ...MACHINE_METADATA }, | ||
eventCache = EventCache.getInstance(), | ||
getRawMachineId = () => nodeMachineId.machineId(true), | ||
}: { | ||
eventCache?: EventCache; | ||
getRawMachineId?: () => Promise<string>; | ||
commonProperties?: CommonProperties; | ||
} = {} | ||
): Telemetry { | ||
const instance = new Telemetry(session, userConfig, commonProperties, { eventCache, getRawMachineId }); | ||
const instance = new Telemetry(session, userConfig, commonProperties, { eventCache }); | ||
|
||
void instance.setup(); | ||
return instance; | ||
|
@@ -57,26 +49,7 @@ export class Telemetry { | |
if (!this.isTelemetryEnabled()) { | ||
return; | ||
} | ||
this.setupPromise = Promise.all([ | ||
getDeviceId({ | ||
getMachineId: () => this.getRawMachineId(), | ||
onError: (reason, error) => { | ||
switch (reason) { | ||
case "resolutionError": | ||
logger.debug(LogId.telemetryDeviceIdFailure, "telemetry", String(error)); | ||
break; | ||
case "timeout": | ||
logger.debug(LogId.telemetryDeviceIdTimeout, "telemetry", "Device ID retrieval timed out"); | ||
break; | ||
case "abort": | ||
// No need to log in the case of aborts | ||
break; | ||
} | ||
}, | ||
abortSignal: this.deviceIdAbortController.signal, | ||
}), | ||
detectContainerEnv(), | ||
]); | ||
this.setupPromise = Promise.all([getDeviceIdForConnection(), detectContainerEnv()]); | ||
|
||
const [deviceId, containerEnv] = await this.setupPromise; | ||
|
||
|
@@ -87,7 +60,6 @@ export class Telemetry { | |
} | ||
|
||
public async close(): Promise<void> { | ||
this.deviceIdAbortController.abort(); | ||
this.isBufferingEvents = false; | ||
blva marked this conversation as resolved.
Show resolved
Hide resolved
|
||
await this.emitEvents(this.eventCache.getEvents()); | ||
blva marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Compass / mongosh we only pass the device ID if it's an Atlas host to minimize visibility of this, can we also do this here?