| File: Telemetry\TelemetryManager.cs | Web Access |
| Project: src\msbuild\src\Framework\Microsoft.Build.Framework.csproj (Microsoft.Build.Framework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #if NETFRAMEWORK using Microsoft.VisualStudio.Telemetry; #endif using System; using System.IO; using System.Runtime.CompilerServices; namespace Microsoft.Build.Framework.Telemetry { /// <summary> /// Manages telemetry collection and reporting for MSBuild. /// This class provides a centralized way to initialize, configure, and manage telemetry sessions. /// </summary> /// <remarks> /// The TelemetryManager is a singleton that handles both standalone and integrated telemetry scenarios. /// On .NET Framework, it integrates with Visual Studio telemetry services. /// On .NET Core it provides a lightweight telemetry implementation through exposing an activity source. /// </remarks> internal class TelemetryManager { /// <summary> /// Lock object for thread-safe initialization and disposal. /// </summary> private static readonly LockType s_lock = new(); private static bool s_initialized; private static bool s_disposed; private TelemetryManager() { } /// <summary> /// Optional activity source for MSBuild or other telemetry usage. /// </summary> public MSBuildActivitySource? DefaultActivitySource { get; private set; } public static TelemetryManager Instance { get; } = new TelemetryManager(); /// <summary> /// Initializes the telemetry manager with the specified configuration. /// </summary> /// <param name="isStandalone"> /// Indicates whether MSBuild is running in standalone mode (e.g., MSBuild.exe directly invoked) /// versus integrated mode (e.g., running within Visual Studio or dotnet CLI). /// When <c>true</c>, creates and manages its own telemetry session on .NET Framework. /// </param> [MethodImpl(MethodImplOptions.NoInlining)] public void Initialize(bool isStandalone) { lock (s_lock) { if (s_initialized) { return; } s_initialized = true; if (IsOptOut()) { return; } TryInitializeTelemetry(isStandalone); } } /// <summary> /// Resets the TelemetryManager state for TESTING purposes. /// </summary> internal static void ResetForTest() { lock (s_lock) { s_initialized = false; s_disposed = false; Instance.DefaultActivitySource = null; } } /// <summary> /// Initializes MSBuild telemetry. /// This method is deliberately not inlined to ensure /// the Telemetry related assemblies are only loaded when this method is called, /// allowing the calling code to catch assembly loading exceptions. /// </summary> [MethodImpl(MethodImplOptions.NoInlining)] private void TryInitializeTelemetry(bool isStandalone) { try { #if NETFRAMEWORK DefaultActivitySource = VsTelemetryInitializer.Initialize(isStandalone); #else DefaultActivitySource = new MSBuildActivitySource(TelemetryConstants.DefaultActivitySourceNamespace); #endif } catch (Exception ex) when (ex is FileNotFoundException or FileLoadException or TypeLoadException) { // Microsoft.VisualStudio.Telemetry or System.Diagnostics.DiagnosticSource might not be available outside of VS or dotnet. // This is expected in standalone application scenarios (when MSBuild.exe is invoked directly). DefaultActivitySource = null; } } public void Dispose() { lock (s_lock) { if (s_disposed) { return; } #if NETFRAMEWORK try { DisposeVsTelemetry(); } catch (Exception ex) when ( ex is FileNotFoundException or FileLoadException or TypeLoadException) { // Assembly was never loaded, nothing to dispose. } #endif s_disposed = true; } } /// <summary> /// Determines if the user has explicitly opted out of telemetry. /// </summary> internal static bool IsOptOut() => #if NETFRAMEWORK Traits.Instance.FrameworkTelemetryOptOut; #else Traits.Instance.SdkTelemetryOptOut; #endif #if NETFRAMEWORK [MethodImpl(MethodImplOptions.NoInlining)] private static void DisposeVsTelemetry() => VsTelemetryInitializer.Dispose(); #endif } #if NETFRAMEWORK internal static class VsTelemetryInitializer { // Telemetry API key for Visual Studio telemetry service. private const string CollectorApiKey = "f3e86b4023cc43f0be495508d51f588a-f70d0e59-0fb0-4473-9f19-b4024cc340be-7296"; // Store as object to avoid type reference at class load time private static object? s_telemetrySession; private static bool s_ownsSession = false; [MethodImpl(MethodImplOptions.NoInlining)] public static MSBuildActivitySource Initialize(bool isStandalone) { TelemetrySession session; if (isStandalone) { session = TelemetryService.CreateAndGetDefaultSession(CollectorApiKey); TelemetryService.DefaultSession.UseVsIsOptedIn(); TelemetryService.DefaultSession.Start(); s_ownsSession = true; } else { session = TelemetryService.DefaultSession; } s_telemetrySession = session; return new MSBuildActivitySource(session); } [MethodImpl(MethodImplOptions.NoInlining)] public static void Dispose() { if (s_ownsSession && s_telemetrySession is TelemetrySession session) { session.Dispose(); } s_telemetrySession = null; } } #endif }