| File: AppInsightsTelemetryClient.cs | Web Access |
| Project: src\src\dotnet-svcutil\lib\src\dotnet-svcutil-lib.csproj (dotnet-svcutil-lib) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights.Extensibility; using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; namespace Microsoft.Tools.ServiceModel.Svcutil { // Provides the TelemetryClient instance for logging telemetry using AppInsights. internal class AppInsightsTelemetryClient { private const string instrumentationKey = "97d0a8a2-1954-4c71-b95d-89df9627dccb"; internal const string OptOutVariable = "DOTNET_SVCUTIL_TELEMETRY_OPTOUT"; private const string eventNamePrefix = "VS/dotnetSvcutil/"; private const string testModeVariable = "DOTNET_SVCUTIL_TEST_MODE"; private static bool? s_isUserOptedIn = null; public static bool IsUserOptedIn { get { if (!s_isUserOptedIn.HasValue) { string optOut = Environment.GetEnvironmentVariable(OptOutVariable); if (string.IsNullOrEmpty(optOut)) { s_isUserOptedIn = true; } else { // We parse the same values here as the dotnet SDK's opt out. switch (optOut.ToLowerInvariant()) { case "true": case "1": case "yes": s_isUserOptedIn = false; break; case "false": case "0": case "no": default: s_isUserOptedIn = true; break; } } } return s_isUserOptedIn.Value; } set { s_isUserOptedIn = value; } } private static readonly object s_lockObj = new object(); private static AppInsightsTelemetryClient s_instance = null; private TelemetryClient _telemetryClient = null; private AppInsightsTelemetryClient(TelemetryClient telemetryClient) { _telemetryClient = telemetryClient; } public static async Task<AppInsightsTelemetryClient> GetInstanceAsync(CancellationToken cancellationToken) { if (s_instance == null) { try { if (!bool.TryParse(Environment.GetEnvironmentVariable(testModeVariable), out bool testMode)) { testMode = false; } lock (s_lockObj) { if (s_instance == null) { if (!IsUserOptedIn) { // If the user hasn't opted in return now with a null telemetry client to ensure we don't create any telemetry context. return new AppInsightsTelemetryClient(null); } TelemetryConfiguration config; config = new TelemetryConfiguration(); config.TelemetryChannel.DeveloperMode = testMode; s_instance = new AppInsightsTelemetryClient(new TelemetryClient(config)); } } var telemetryClient = s_instance._telemetryClient; telemetryClient.InstrumentationKey = instrumentationKey; // Populate context with properties that are common and should be logged for all events. var context = telemetryClient.Context; context.Device.OperatingSystem = GetOperatingSystemString(); #if !NETCORE10 // Set the user id to a stable hash of the user's current username. Users with the same username // or those with hash collisions will show up as the same id. So the user id won't be perfectly unique. // However, it will give us some idea of how many different users are using the tool. context.User.Id = GetStableHashCode(Environment.UserName).ToString(); #endif // DebugLogger tracks telemetry when adding exceptions. We pass null for the logger to avoid the possibility of an endless cyclic call if something goes wrong in GetSdkVersionAsync. var sdkVersion = await ProjectPropertyResolver.GetSdkVersionAsync(System.IO.Directory.GetCurrentDirectory(), null /* logger */, cancellationToken).ConfigureAwait(false); context.GlobalProperties["SvcUtil.Version"] = Tool.PackageVersion; context.GlobalProperties["Dotnet.Version"] = string.IsNullOrEmpty(sdkVersion) ? "unknown" : sdkVersion; context.GlobalProperties["TestMode"] = testMode.ToString(); } catch (Exception ex) { #if DEBUG ToolConsole.WriteWarning(ex.Message); #endif s_isUserOptedIn = false; } } return s_instance; } // This is copied from the 32 bit implementation from String.GetHashCode. // It's a stable string hashing algorithm so it won't change with each run of the tool. private static int GetStableHashCode(string str) { unsafe { fixed (char* src = str) { int hash1 = (5381 << 16) + 5381; int hash2 = hash1; int* pint = (int*)src; int len = str.Length; while (len > 2) { hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0]; hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1]; pint += 2; len -= 4; } if (len > 0) { hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0]; } return hash1 + (hash2 * 1566083941); } } } private static string GetOperatingSystemString() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return "Windows"; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return "macOS"; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { return "Linux"; } else { return "Unknown"; } } public void TrackEvent(string eventName) { if (IsUserOptedIn) { _telemetryClient.TrackEvent(eventNamePrefix + eventName); _telemetryClient.Flush(); } } public void TrackEvent(string eventName, Dictionary<string, string> properties) { if (IsUserOptedIn) { _telemetryClient.TrackEvent(eventNamePrefix + eventName, properties); _telemetryClient.Flush(); } } public void TrackError(string eventName, Exception exceptionObject) { this.TrackError(eventName, exceptionObject.ToString()); } public void TrackError(string eventName, string exceptionString) { if (IsUserOptedIn) { var properties = new Dictionary<string, string>(); properties.Add("ExceptionString", exceptionString); _telemetryClient.TrackEvent(eventNamePrefix + eventName, properties); _telemetryClient.Flush(); } } } }