diff --git a/packages/cli/src/commands/engine.ts b/packages/cli/src/commands/engine.ts index e947a6609d..494bb8ad59 100644 --- a/packages/cli/src/commands/engine.ts +++ b/packages/cli/src/commands/engine.ts @@ -66,7 +66,10 @@ class CLIEngine { * entry point of the CLI engine */ async start(rootCmd: CLICommand): Promise { - Correlator.setId(); + // Seed the correlation id from ATK_CLI_CORRELATION_ID when a parent process + // (e.g. the wiqd CLI) passes one, so this run's telemetry shares the parent's + // `correlation-id`. Absent/malformed values fall back to a fresh UUID. + Correlator.setId(process.env.ATK_CLI_CORRELATION_ID); // Fire-and-forget: fetch latest metadata in background, same as VSC extension activation void getFxCore().fetchOnlineTemplateMetadata(); diff --git a/packages/cli/tests/unit/engine.tests.ts b/packages/cli/tests/unit/engine.tests.ts index aa9d4e0bb6..3f23c3c27a 100644 --- a/packages/cli/tests/unit/engine.tests.ts +++ b/packages/cli/tests/unit/engine.tests.ts @@ -15,6 +15,7 @@ import { MissingEnvironmentVariablesError, UserCancelError, VersionState, + Correlator, } from "@microsoft/teamsfx-core"; import { assert } from "chai"; import mockedEnv from "mocked-env"; @@ -746,4 +747,26 @@ describe("CLI Engine", () => { mockedEnvRestore(); }); }); + + describe("ATK_CLI_CORRELATION_ID env var", () => { + it("seeds the Correlator with the parent-provided id", async () => { + const externalId = "11111111-1111-4111-8111-111111111111"; + const mockedEnvRestore = mockedEnv({ ATK_CLI_CORRELATION_ID: externalId }); + const setIdStub = sandbox.stub(Correlator, "setId"); + sandbox.stub(process, "argv").value(["node", "cli", "-h"]); + sandbox.stub(logger, "info"); + await engine.start(rootCommand); + assert.isTrue(setIdStub.calledWith(externalId)); + mockedEnvRestore(); + }); + it("seeds the Correlator with undefined when the env var is not set", async () => { + const mockedEnvRestore = mockedEnv({ ATK_CLI_CORRELATION_ID: undefined }); + const setIdStub = sandbox.stub(Correlator, "setId"); + sandbox.stub(process, "argv").value(["node", "cli", "-h"]); + sandbox.stub(logger, "info"); + await engine.start(rootCommand); + assert.isTrue(setIdStub.calledWith(undefined)); + mockedEnvRestore(); + }); + }); }); diff --git a/packages/fx-core/src/common/correlator.ts b/packages/fx-core/src/common/correlator.ts index d7f70c5baf..41e29c05c9 100644 --- a/packages/fx-core/src/common/correlator.ts +++ b/packages/fx-core/src/common/correlator.ts @@ -8,10 +8,16 @@ import * as uuid from "uuid"; const asyncLocalStorage = new AsyncLocalStorage(); export class Correlator { - static setId(): string { - const id = uuid.v4(); - asyncLocalStorage.enterWith(id); - return id; + /** + * Sets the ambient correlation id for the current async context. A valid UUID + * `id` is adopted as-is — the seam that lets an external caller (e.g. the wiqd + * CLI) thread its own correlation id in; absent/malformed values mint a fresh + * UUID so the id is always well-formed. + */ + static setId(id?: string): string { + const newId = id && uuid.validate(id) ? id : uuid.v4(); + asyncLocalStorage.enterWith(newId); + return newId; } static run(work: (...args: [...T]) => R, ...args: [...T]): R { const id = asyncLocalStorage.getStore() || uuid.v4(); diff --git a/packages/fx-core/tests/common/correlator.test.ts b/packages/fx-core/tests/common/correlator.test.ts index 6738433550..cf07ccc248 100644 --- a/packages/fx-core/tests/common/correlator.test.ts +++ b/packages/fx-core/tests/common/correlator.test.ts @@ -27,4 +27,23 @@ describe("Correlator", () => { const getId = Correlator.getId(); chai.assert.isDefined(getId); }); + + it("setId adopts a valid externally-provided UUID", () => { + const externalId = "11111111-1111-4111-8111-111111111111"; + const setedId = Correlator.setId(externalId); + chai.assert.equal(setedId, externalId); + chai.assert.equal(Correlator.getId(), externalId); + }); + + it("setId mints a fresh UUID when the provided id is malformed", () => { + const setedId = Correlator.setId("not-a-uuid"); + chai.assert.notEqual(setedId, "not-a-uuid"); + chai.assert.equal(Correlator.getId(), setedId); + }); + + it("setId mints a fresh UUID when no id is provided", () => { + const setedId = Correlator.setId(); + chai.assert.isNotEmpty(setedId); + chai.assert.equal(Correlator.getId(), setedId); + }); });