diff --git a/.github/workflows/add-lockdown-label.yml b/.github/workflows/add-lockdown-label.yml index dfafacd12008..648f7484ed36 100644 --- a/.github/workflows/add-lockdown-label.yml +++ b/.github/workflows/add-lockdown-label.yml @@ -13,6 +13,9 @@ permissions: actions: write # For managing the operation state cache issues: write +env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} + jobs: add-label: runs-on: ubuntu-latest diff --git a/.github/workflows/add-servicing-consider-label.yml b/.github/workflows/add-servicing-consider-label.yml index 02b6352784da..8cda9c324342 100644 --- a/.github/workflows/add-servicing-consider-label.yml +++ b/.github/workflows/add-servicing-consider-label.yml @@ -13,6 +13,9 @@ permissions: contents: read pull-requests: write +env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} + jobs: add-servicing-label: runs-on: ubuntu-latest diff --git a/.github/workflows/aot-size-analysis.yml b/.github/workflows/aot-size-analysis.yml index 926403e791c9..d232bcb49632 100644 --- a/.github/workflows/aot-size-analysis.yml +++ b/.github/workflows/aot-size-analysis.yml @@ -31,6 +31,7 @@ on: permissions: read-all env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} AZDO_ORG: dnceng-public AZDO_PROJECT: public AZDO_PIPELINE_ID: "101" diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 5efe762cf00f..5b0c31c4e722 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -15,6 +15,9 @@ permissions: pull-requests: write actions: write +env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} + jobs: backport: uses: dotnet/arcade/.github/workflows/backport-base.yml@main diff --git a/.github/workflows/check-vendored-files.yml b/.github/workflows/check-vendored-files.yml index 7241aafd2088..cf1e4cf3b203 100644 --- a/.github/workflows/check-vendored-files.yml +++ b/.github/workflows/check-vendored-files.yml @@ -33,6 +33,9 @@ concurrency: group: vendored-files-check cancel-in-progress: false +env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} + jobs: validate: name: Validate manifest diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index db1219a15c42..71480452ccdf 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -18,6 +18,8 @@ jobs: copilot-setup-steps: runs-on: ubuntu-latest + env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: diff --git a/.github/workflows/detect-netsdk-diagnostics.yml b/.github/workflows/detect-netsdk-diagnostics.yml index 31a7007eef78..43394b00a8a1 100644 --- a/.github/workflows/detect-netsdk-diagnostics.yml +++ b/.github/workflows/detect-netsdk-diagnostics.yml @@ -25,6 +25,9 @@ on: permissions: read-all +env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} + jobs: detect-diagnostics: name: Detect New NETSDK Diagnostic Codes diff --git a/.github/workflows/fix-completions-on-comment.yml b/.github/workflows/fix-completions-on-comment.yml index f439ba7a0f53..fbf7bad3f6ab 100644 --- a/.github/workflows/fix-completions-on-comment.yml +++ b/.github/workflows/fix-completions-on-comment.yml @@ -13,6 +13,7 @@ permissions: contents: read env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} REPO_NAME: ${{ github.event.repository.name }} RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} diff --git a/.github/workflows/inter-branch-merge-flow.yml b/.github/workflows/inter-branch-merge-flow.yml index 9d9c7e4a61aa..f7f8bd048bd5 100644 --- a/.github/workflows/inter-branch-merge-flow.yml +++ b/.github/workflows/inter-branch-merge-flow.yml @@ -9,6 +9,9 @@ permissions: contents: write pull-requests: write +env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} + jobs: Merge: uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@main \ No newline at end of file diff --git a/.github/workflows/labeler-cache-retention.yml b/.github/workflows/labeler-cache-retention.yml index 5cdacfa05670..8acf9136b26e 100644 --- a/.github/workflows/labeler-cache-retention.yml +++ b/.github/workflows/labeler-cache-retention.yml @@ -21,6 +21,7 @@ on: default: "ACTIVE" env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} CACHE_KEY: ${{ inputs.cache_key || 'ACTIVE' }} jobs: diff --git a/.github/workflows/labeler-predict-issues.yml b/.github/workflows/labeler-predict-issues.yml index 88c36b9cab33..7e0b060347ae 100644 --- a/.github/workflows/labeler-predict-issues.yml +++ b/.github/workflows/labeler-predict-issues.yml @@ -23,6 +23,7 @@ on: default: "ACTIVE" env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} # Do not allow failure for jobs triggered automatically (as this causes red noise on the workflows list) ALLOW_FAILURE: ${{ github.event_name == 'workflow_dispatch' }} diff --git a/.github/workflows/labeler-predict-pulls.yml b/.github/workflows/labeler-predict-pulls.yml index 9fae31e42bc1..06eb5b8ce4a4 100644 --- a/.github/workflows/labeler-predict-pulls.yml +++ b/.github/workflows/labeler-predict-pulls.yml @@ -36,6 +36,7 @@ on: default: "ACTIVE" env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} # Do not allow failure for jobs triggered automatically (this can block PR merge) ALLOW_FAILURE: ${{ github.event_name == 'workflow_dispatch' }} diff --git a/.github/workflows/labeler-promote.yml b/.github/workflows/labeler-promote.yml index c01086c51779..4913cfa7e21d 100644 --- a/.github/workflows/labeler-promote.yml +++ b/.github/workflows/labeler-promote.yml @@ -30,6 +30,9 @@ on: permissions: actions: write +env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} + jobs: promote-issues: if: ${{ inputs.issues }} diff --git a/.github/workflows/labeler-train.yml b/.github/workflows/labeler-train.yml index 8c0fb0d8cfad..639dcd38dfc3 100644 --- a/.github/workflows/labeler-train.yml +++ b/.github/workflows/labeler-train.yml @@ -45,6 +45,7 @@ on: default: "staged" env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} CACHE_KEY: ${{ inputs.cache_key_suffix }} REPOSITORY: ${{ github.repository }} LABEL_PREFIX: "Area-" diff --git a/.github/workflows/pr-analysis.yml b/.github/workflows/pr-analysis.yml index 92190984c88f..1451e765a8b2 100644 --- a/.github/workflows/pr-analysis.yml +++ b/.github/workflows/pr-analysis.yml @@ -5,6 +5,9 @@ on: permissions: contents: read pull-requests: read +env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} + jobs: allowed-labels: runs-on: ubuntu-latest diff --git a/.github/workflows/remove-lockdown-label.yml b/.github/workflows/remove-lockdown-label.yml index 0013a4b383f0..ee75dd844175 100644 --- a/.github/workflows/remove-lockdown-label.yml +++ b/.github/workflows/remove-lockdown-label.yml @@ -12,6 +12,9 @@ permissions: actions: write pull-requests: write +env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} + jobs: remove-labels: # Only run on the main repository, not forks diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 1dff2caf6bf1..c2d6664d3f68 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -5,6 +5,7 @@ on: workflow_dispatch: # Allow manual trigger for testing env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} KBE_LABEL: 'Known Build Error' STALE_LABEL: 'stale' KEEP_OPEN_LABEL: 'keep-open' diff --git a/.github/workflows/update-man-pages.yml b/.github/workflows/update-man-pages.yml index d86a2d884506..bbbff0b7074d 100644 --- a/.github/workflows/update-man-pages.yml +++ b/.github/workflows/update-man-pages.yml @@ -9,6 +9,9 @@ permissions: contents: write pull-requests: write +env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} + jobs: update-man-pages: runs-on: ubuntu-latest diff --git a/.github/workflows/update-static-web-assets-baselines.yml b/.github/workflows/update-static-web-assets-baselines.yml index cdfdfe111268..67cc94a380ac 100644 --- a/.github/workflows/update-static-web-assets-baselines.yml +++ b/.github/workflows/update-static-web-assets-baselines.yml @@ -11,6 +11,9 @@ on: permissions: contents: read +env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} + jobs: metadata: name: Fetch Pull Request Metadata diff --git a/.github/workflows/update-xlf-on-comment.yml b/.github/workflows/update-xlf-on-comment.yml index 41c98b4949de..2e33a49fd1c9 100644 --- a/.github/workflows/update-xlf-on-comment.yml +++ b/.github/workflows/update-xlf-on-comment.yml @@ -13,6 +13,7 @@ permissions: contents: read env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} REPO_NAME: ${{ github.event.repository.name }} RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} jobs: diff --git a/.github/workflows/validate-pat-pool.yml b/.github/workflows/validate-pat-pool.yml index 91b93919ed87..131dbfff338e 100644 --- a/.github/workflows/validate-pat-pool.yml +++ b/.github/workflows/validate-pat-pool.yml @@ -8,6 +8,9 @@ on: # No GitHub API permissions needed permissions: {} +env: + DOTNET_CLI_TELEMETRY_SESSIONID: gha-${{ github.repository_id }}-${{ github.run_id }}-${{ github.run_attempt }} + jobs: validate: environment: copilot-pat-pool diff --git a/.vsts-ci.yml b/.vsts-ci.yml index 7a69824b298c..007067abdab9 100644 --- a/.vsts-ci.yml +++ b/.vsts-ci.yml @@ -35,6 +35,8 @@ variables: - template: /eng/pipelines/templates/variables/sdk-defaults.yml # Variables used: DncEngInternalBuildPool - template: /eng/common/templates-official/variables/pool-providers.yml +- name: DOTNET_CLI_TELEMETRY_SESSIONID + value: azdo-$(System.CollectionId)-$(System.TeamProjectId)-$(Build.BuildId) # Helix testing requires a token when internally run. # Variables used: HelixApiAccessToken - group: DotNet-HelixApi-Access diff --git a/.vsts-pr.yml b/.vsts-pr.yml index db1ae21f8fed..fc891e61d32b 100644 --- a/.vsts-pr.yml +++ b/.vsts-pr.yml @@ -34,6 +34,8 @@ variables: - template: /eng/pipelines/templates/variables/sdk-defaults.yml # Variables used: DncEngPublicBuildPool - template: /eng/common/templates/variables/pool-providers.yml +- name: DOTNET_CLI_TELEMETRY_SESSIONID + value: azdo-$(System.CollectionId)-$(System.TeamProjectId)-$(Build.BuildId) resources: containers: diff --git a/documentation/project-docs/telemetry.md b/documentation/project-docs/telemetry.md index efc28db56228..30d0f2426b07 100644 --- a/documentation/project-docs/telemetry.md +++ b/documentation/project-docs/telemetry.md @@ -33,6 +33,11 @@ The .NET SDK telemetry can be disabled using the following environment variable: - Values: `false`, `0`, or `no` to allow messages - Default: `false` (messages are displayed) - Note: This flag does not affect telemetry collection itself +- **`DOTNET_CLI_TELEMETRY_SESSIONID`**: Seeds the initial telemetry session ID + - When set, the CLI uses this value instead of generating a new GUID for the first `TelemetryClient` created in the process + - This is useful for correlating multiple `dotnet` invocations that belong to the same CI workflow, job, or higher-level user session + - If not set, the CLI generates a new GUID per process as before + - **`DOTNET_CLI_TELEMETRY_DISABLE_TRACE_EXPORT`**: Set to `1`, `true`, or `yes` to disable exporting trace telemetry to Azure Monitor (both persistence and upload). Metrics/OTLP export and the local disk log are @@ -125,7 +130,7 @@ Every telemetry event automatically includes these common properties: | **Product Type** | Type of .NET product | Product identifier | | **Libc Release** | Libc release information | Libc release version | | **Libc Version** | Libc version information | Libc version number | -| **SessionId** | Unique session identifier | GUID | +| **SessionId** | Unique session identifier | GUID or CI-specific correlation identifier | ## Telemetry Events diff --git a/src/Cli/dotnet/README.md b/src/Cli/dotnet/README.md index 5d8d6bb80127..85d6ffe4ca35 100644 --- a/src/Cli/dotnet/README.md +++ b/src/Cli/dotnet/README.md @@ -85,3 +85,7 @@ Specifies the location of the servicing index to use by the shared host when loa `DOTNET_CLI_TELEMETRY_OPTOUT` Specifies whether data about the .NET Core tools usage is collected and sent to Microsoft. **true** to opt-out of the telemetry feature (values true, 1 or yes accepted); otherwise, **false** (values false, 0 or no accepted). If not set, it defaults to **false**, that is, the telemetry feature is on. + +`DOTNET_CLI_TELEMETRY_SESSIONID` + +Seeds the initial telemetry session identifier for the current `dotnet` process. When set, multiple `dotnet` invocations can share the same value to correlate telemetry across a CI workflow or other higher-level session. diff --git a/src/Cli/dotnet/Telemetry/TelemetryClient.cs b/src/Cli/dotnet/Telemetry/TelemetryClient.cs index 0c377e724435..244ba62c821a 100644 --- a/src/Cli/dotnet/Telemetry/TelemetryClient.cs +++ b/src/Cli/dotnet/Telemetry/TelemetryClient.cs @@ -191,7 +191,11 @@ public TelemetryClient(string? sessionId, IEnvironmentProvider? environmentProvi s_tracerProvider ??= s_tracerProviderBuilder.Build(); } - CurrentSessionId ??= !string.IsNullOrEmpty(sessionId) ? sessionId : Guid.NewGuid().ToString(); + var initialSessionId = !string.IsNullOrEmpty(sessionId) + ? sessionId + : environmentProvider.GetEnvironmentVariable(EnvironmentVariableNames.DOTNET_CLI_TELEMETRY_SESSIONID); + + CurrentSessionId ??= !string.IsNullOrEmpty(initialSessionId) ? initialSessionId : Guid.NewGuid().ToString(); s_commonProperties = new TelemetryCommonProperties().GetTelemetryCommonProperties(CurrentSessionId); } diff --git a/test/dotnet.Tests/CommandTests/MSBuild/GivenMsbuildForwardingApp.cs b/test/dotnet.Tests/CommandTests/MSBuild/GivenMsbuildForwardingApp.cs index 1aea107f584e..48f2618bee76 100644 --- a/test/dotnet.Tests/CommandTests/MSBuild/GivenMsbuildForwardingApp.cs +++ b/test/dotnet.Tests/CommandTests/MSBuild/GivenMsbuildForwardingApp.cs @@ -3,7 +3,11 @@ #nullable disable +using Microsoft.DotNet.Cli; using Microsoft.DotNet.Cli.Commands.MSBuild; +using Microsoft.DotNet.Cli.Telemetry; +using Microsoft.DotNet.Cli.Utils; +using Moq; namespace Microsoft.DotNet.Cli.MSBuild.Tests { @@ -19,7 +23,7 @@ public GivenMsbuildForwardingApp() public void DotnetExeIsExecuted() { var msbuildPath = ""; - new MSBuildForwardingApp(new string[0], msbuildPath) + new MSBuildForwardingApp(Array.Empty(), msbuildPath) .GetProcessStartInfo().FileName.Should().EndWith("dotnet.exe"); } @@ -28,7 +32,7 @@ public void DotnetExeIsExecuted() public void DotnetIsExecuted() { var msbuildPath = ""; - new MSBuildForwardingApp(new string[0], msbuildPath) + new MSBuildForwardingApp(Array.Empty(), msbuildPath) .GetProcessStartInfo().FileName.Should().EndWith("dotnet"); } @@ -39,7 +43,7 @@ public void DotnetIsExecuted() public void ItSetsEnvironmentalVariables(string envVarName) { var msbuildPath = ""; - var startInfo = new MSBuildForwardingApp(new string[0], msbuildPath).GetProcessStartInfo(); + var startInfo = new MSBuildForwardingApp(Array.Empty(), msbuildPath).GetProcessStartInfo(); startInfo.Environment.ContainsKey(envVarName).Should().BeTrue(); } @@ -48,7 +52,7 @@ public void ItSetsMSBuildExtensionPathToExistingPath() { var msbuildPath = ""; var envVar = "MSBuildExtensionsPath"; - new DirectoryInfo(new MSBuildForwardingApp(new string[0], msbuildPath) + new DirectoryInfo(new MSBuildForwardingApp(Array.Empty(), msbuildPath) .GetProcessStartInfo() .Environment[envVar]) .Should() @@ -60,7 +64,7 @@ public void ItSetsMSBuildSDKsPathToExistingPath() { var msbuildPath = ""; var envVar = "MSBuildSDKsPath"; - new DirectoryInfo(new MSBuildForwardingApp(new string[0], msbuildPath) + new DirectoryInfo(new MSBuildForwardingApp(Array.Empty(), msbuildPath) .GetProcessStartInfo() .Environment[envVar]) .Should() @@ -72,7 +76,7 @@ public void ItSetsOrIgnoresTelemetrySessionId() { var msbuildPath = ""; var envVar = "DOTNET_CLI_TELEMETRY_SESSIONID"; - var startInfo = new MSBuildForwardingApp(new string[0], msbuildPath) + var startInfo = new MSBuildForwardingApp(Array.Empty(), msbuildPath) .GetProcessStartInfo(); string sessionId = startInfo.Environment[envVar]; @@ -83,8 +87,41 @@ public void ItSetsOrIgnoresTelemetrySessionId() // for session ID, so ignore if they already set it if (sessionId != "test") { - (sessionId == null || Guid.TryParse(sessionId, out _)) - .Should().BeTrue("DOTNET_CLI_TELEMETRY_SESSIONID should be null or current session id"); + (sessionId == null || Guid.TryParse(sessionId, out _) || sessionId == TelemetryClient.CurrentSessionId) + .Should().BeTrue("DOTNET_CLI_TELEMETRY_SESSIONID should be null, current session id, or a guid"); + } + } + + [TestMethod] + [DoNotParallelize] + public void ItUsesSeededTelemetrySessionId() + { + const string sessionId = "gha-12345-1"; + var msbuildPath = ""; + var environmentProvider = new Mock(MockBehavior.Strict); + + TelemetryClient.DisabledForTests = true; + TelemetryClient.DisabledForTests = false; + + try + { + environmentProvider + .Setup(p => p.GetEnvironmentVariableAsBool(EnvironmentVariableNames.TELEMETRY_OPTOUT, It.IsAny())) + .Returns(false); + environmentProvider + .Setup(p => p.GetEnvironmentVariable(EnvironmentVariableNames.DOTNET_CLI_TELEMETRY_SESSIONID)) + .Returns(sessionId); + + _ = new TelemetryClient(sessionId: null, environmentProvider: environmentProvider.Object); + + var startInfo = new MSBuildForwardingApp(Array.Empty(), msbuildPath) + .GetProcessStartInfo(); + + startInfo.Environment["DOTNET_CLI_TELEMETRY_SESSIONID"].Should().Be(sessionId); + } + finally + { + TelemetryClient.DisabledForTests = true; } } @@ -92,7 +129,7 @@ public void ItSetsOrIgnoresTelemetrySessionId() public void ItDoesNotSetCurrentWorkingDirectory() { var msbuildPath = ""; - var startInfo = new MSBuildForwardingApp(new string[0], msbuildPath) + var startInfo = new MSBuildForwardingApp(Array.Empty(), msbuildPath) .GetProcessStartInfo().WorkingDirectory.Should().Be(""); } diff --git a/test/dotnet.Tests/TelemetryTests/TelemetryClientTests.cs b/test/dotnet.Tests/TelemetryTests/TelemetryClientTests.cs index 8c8f6df82107..f53c7e6fc782 100644 --- a/test/dotnet.Tests/TelemetryTests/TelemetryClientTests.cs +++ b/test/dotnet.Tests/TelemetryTests/TelemetryClientTests.cs @@ -1,7 +1,11 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Json.Nodes; +using Microsoft.DotNet.Cli; +using Microsoft.DotNet.Cli.Telemetry; +using Microsoft.DotNet.Cli.Utils; +using Moq; namespace Microsoft.DotNet.Tests.TelemetryTests; @@ -24,8 +28,7 @@ public class TelemetryClientTests : SdkTest // Only runs on Windows because OTel libraries are only referenced on Windows builds. // Thus, this test that writes telemetry logs will not work on other platforms. [TestMethod] - - [OSCondition(OperatingSystems.Windows)] + [OSCondition(OperatingSystems.Windows)] [DynamicData(nameof(CommandsWithExitCode))] public void ItProcessesTelemetryData(string[] commandArgs, string exitCodeExpected) { @@ -67,4 +70,61 @@ public void ItProcessesTelemetryData(string[] commandArgs, string exitCodeExpect var exitCode = tags["exitCode"]?.GetValue(); exitCode.Should().Be(exitCodeExpected); } + + [TestMethod] + [DoNotParallelize] + public void ItSeedsCurrentSessionIdFromEnvironmentWhenSessionIdIsNotProvided() + { + const string sessionId = "gha-12345-1"; + var environmentProvider = new Mock(MockBehavior.Strict); + + TelemetryClient.DisabledForTests = true; + TelemetryClient.DisabledForTests = false; + + try + { + environmentProvider + .Setup(p => p.GetEnvironmentVariableAsBool(EnvironmentVariableNames.TELEMETRY_OPTOUT, It.IsAny())) + .Returns(false); + environmentProvider + .Setup(p => p.GetEnvironmentVariable(EnvironmentVariableNames.DOTNET_CLI_TELEMETRY_SESSIONID)) + .Returns(sessionId); + + var telemetry = new TelemetryClient(sessionId: null, environmentProvider: environmentProvider.Object); + + telemetry.Enabled.Should().BeTrue(); + TelemetryClient.CurrentSessionId.Should().Be(sessionId); + } + finally + { + TelemetryClient.DisabledForTests = true; + } + } + + [TestMethod] + [DoNotParallelize] + public void ItPrefersExplicitSessionIdOverEnvironmentSeed() + { + const string sessionId = "explicit-session"; + var environmentProvider = new Mock(MockBehavior.Strict); + + TelemetryClient.DisabledForTests = true; + TelemetryClient.DisabledForTests = false; + + try + { + environmentProvider + .Setup(p => p.GetEnvironmentVariableAsBool(EnvironmentVariableNames.TELEMETRY_OPTOUT, It.IsAny())) + .Returns(false); + + var telemetry = new TelemetryClient(sessionId, environmentProvider: environmentProvider.Object); + + telemetry.Enabled.Should().BeTrue(); + TelemetryClient.CurrentSessionId.Should().Be(sessionId); + } + finally + { + TelemetryClient.DisabledForTests = true; + } + } }