diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandContext.cs b/src/Microsoft.DotNet.Cli.Utils/CommandContext.cs index b31edab08a..8209dd2f74 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandContext.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandContext.cs @@ -14,8 +14,8 @@ public static class Variables public static readonly string AnsiPassThru = Prefix + "ANSI_PASS_THRU"; } - private static Lazy _verbose = new Lazy(() => GetBool(Variables.Verbose)); - private static Lazy _ansiPassThru = new Lazy(() => GetBool(Variables.AnsiPassThru)); + private static Lazy _verbose = new Lazy(() => Env.GetEnvironmentVariableAsBool(Variables.Verbose)); + private static Lazy _ansiPassThru = new Lazy(() => Env.GetEnvironmentVariableAsBool(Variables.AnsiPassThru)); public static bool IsVerbose() { @@ -25,29 +25,6 @@ public static bool IsVerbose() public static bool ShouldPassAnsiCodesThrough() { return _ansiPassThru.Value; - } - - private static bool GetBool(string name, bool defaultValue = false) - { - var str = Environment.GetEnvironmentVariable(name); - if (string.IsNullOrEmpty(str)) - { - return defaultValue; - } - - switch (str.ToLowerInvariant()) - { - case "true": - case "1": - case "yes": - return true; - case "false": - case "0": - case "no": - return false; - default: - return defaultValue; - } - } + } } } diff --git a/src/Microsoft.DotNet.Cli.Utils/Env.cs b/src/Microsoft.DotNet.Cli.Utils/Env.cs index 30a117bcce..5d0d1382ba 100644 --- a/src/Microsoft.DotNet.Cli.Utils/Env.cs +++ b/src/Microsoft.DotNet.Cli.Utils/Env.cs @@ -33,5 +33,10 @@ public static string GetCommandPathFromRootPath(string rootPath, string commandN { return _environment.GetCommandPathFromRootPath(rootPath, commandName, extensions); } + + public static bool GetEnvironmentVariableAsBool(string name, bool defaultValue = false) + { + return _environment.GetEnvironmentVariableAsBool(name, defaultValue); + } } } diff --git a/src/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs b/src/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs index 1060443ff3..b44ffef717 100644 --- a/src/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs +++ b/src/Microsoft.DotNet.Cli.Utils/EnvironmentProvider.cs @@ -93,5 +93,29 @@ public string GetCommandPathFromRootPath(string rootPath, string commandName, IE return GetCommandPathFromRootPath(rootPath, commandName, extensionsArr); } + + public bool GetEnvironmentVariableAsBool(string name, bool defaultValue) + { + var str = Environment.GetEnvironmentVariable(name); + if (string.IsNullOrEmpty(str)) + { + return defaultValue; + } + + switch (str.ToLowerInvariant()) + { + case "true": + case "1": + case "yes": + return true; + case "false": + case "0": + case "no": + return false; + default: + return defaultValue; + } + } + } } diff --git a/src/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs b/src/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs index db04ebc0b6..754bf54a3e 100644 --- a/src/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs +++ b/src/Microsoft.DotNet.Cli.Utils/IEnvironmentProvider.cs @@ -16,5 +16,7 @@ public interface IEnvironmentProvider string GetCommandPathFromRootPath(string rootPath, string commandName, params string[] extensions); string GetCommandPathFromRootPath(string rootPath, string commandName, IEnumerable extensions); + + bool GetEnvironmentVariableAsBool(string name, bool defaultValue); } } diff --git a/src/Microsoft.DotNet.Cli.Utils/Product.cs b/src/Microsoft.DotNet.Cli.Utils/Product.cs new file mode 100644 index 0000000000..8d2a8f05bf --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/Product.cs @@ -0,0 +1,17 @@ +using System; +using System.Reflection; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class Product + { + public static readonly string LongName = ".NET Command Line Tools"; + public static readonly string Version = GetProductVersion(); + + private static string GetProductVersion() + { + var attr = typeof(Product).GetTypeInfo().Assembly.GetCustomAttribute(); + return attr?.InformationalVersion; + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/Telemetry.cs b/src/Microsoft.DotNet.Cli.Utils/Telemetry.cs new file mode 100644 index 0000000000..4a5bc518a8 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/Telemetry.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using Microsoft.ApplicationInsights; +using Microsoft.Extensions.PlatformAbstractions; +using System.Diagnostics; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class Telemetry + { + private static bool _isInitialized = false; + private static TelemetryClient _client = null; + + private static Dictionary _commonProperties = null; + private static Dictionary _commonMeasurements = null; + + private const string InstrumentationKey = "74cc1c9e-3e6e-4d05-b3fc-dde9101d0254"; + private const string TelemetryOptout = "DOTNET_CLI_TELEMETRY_OPTOUT"; + private const string OSVersion = "OS Version"; + private const string OSPlatform = "OS Platform"; + private const string RuntimeId = "Runtime Id"; + private const string ProductVersion = "Product Version"; + + public Telemetry() + { + if (_isInitialized) + return; + + bool optout = Env.GetEnvironmentVariableAsBool(TelemetryOptout); + + if (optout) + return; + + try + { + _client = new TelemetryClient(); + _client.InstrumentationKey = InstrumentationKey; + _client.Context.Session.Id = Guid.NewGuid().ToString(); + + var runtimeEnvironment = PlatformServices.Default.Runtime; + _client.Context.Device.OperatingSystem = runtimeEnvironment.OperatingSystem; + + _commonProperties = new Dictionary(); + _commonProperties.Add(OSVersion, runtimeEnvironment.OperatingSystemVersion); + _commonProperties.Add(OSPlatform, runtimeEnvironment.OperatingSystemPlatform.ToString()); + _commonProperties.Add(RuntimeId, runtimeEnvironment.GetRuntimeIdentifier()); + _commonProperties.Add(ProductVersion, Product.Version); + _commonMeasurements = new Dictionary(); + + _isInitialized = true; + } + catch (Exception) + { + // we dont want to fail the tool if telemetry fais. We should be able to detect abnormalities from data + // at the server end + } + } + + public void TrackCommand(string command, IDictionary properties = null, IDictionary measurements = null) + { + if (!_isInitialized) + return; + + Dictionary eventMeasurements = GetEventMeasures(measurements); + Dictionary eventProperties = GetEventProperties(properties); + + try + { + _client.TrackEvent(command, eventProperties, eventMeasurements); + _client.Flush(); + } + catch (Exception) { } + } + + + private Dictionary GetEventMeasures(IDictionary measurements) + { + Dictionary eventMeasurements = new Dictionary(_commonMeasurements); + if (measurements != null) + { + foreach (var m in measurements) + { + if (eventMeasurements.ContainsKey(m.Key)) + eventMeasurements[m.Key] = m.Value; + else + eventMeasurements.Add(m.Key, m.Value); + } + } + return eventMeasurements; + } + + private Dictionary GetEventProperties(IDictionary properties) + { + Dictionary eventProperties = new Dictionary(_commonProperties); + if (properties != null) + { + foreach (var p in properties) + { + if (eventProperties.ContainsKey(p.Key)) + eventProperties[p.Key] = p.Value; + else + eventProperties.Add(p.Key, p.Value); + } + } + return eventProperties; + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/project.json b/src/Microsoft.DotNet.Cli.Utils/project.json index ee19f5d237..8e49b99b81 100644 --- a/src/Microsoft.DotNet.Cli.Utils/project.json +++ b/src/Microsoft.DotNet.Cli.Utils/project.json @@ -5,6 +5,7 @@ "warningsAsErrors": true }, "dependencies": { + "Microsoft.ApplicationInsights": "2.0.0-rc1", "Microsoft.DotNet.ProjectModel": "1.0.0-*", "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16537", "NuGet.Versioning": "3.5.0-beta-1068", diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index 2b83d6e830..227cc5d780 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -121,22 +121,40 @@ private static int ProcessArgs(string[] args) ["test"] = TestCommand.Run }; + int exitCode; + var arguments = string.Empty; Func builtIn; if (builtIns.TryGetValue(command, out builtIn)) { - return builtIn(appArgs.ToArray()); + exitCode = builtIn(appArgs.ToArray()); + arguments = string.Join(" ", appArgs); } + else + { + CommandResult result = Command.Create("dotnet-" + command, appArgs, FrameworkConstants.CommonFrameworks.NetStandardApp15) + .ForwardStdErr() + .ForwardStdOut() + .Execute(); + arguments = result.StartInfo.Arguments; + exitCode = result.ExitCode; + } + + Telemetry telemetryClient = new Telemetry(); + + telemetryClient.TrackCommand( + command, + new Dictionary + { + ["ExitCode"] = exitCode + }); + + return exitCode; - return Command.Create("dotnet-" + command, appArgs, FrameworkConstants.CommonFrameworks.NetStandardApp15) - .ForwardStdErr() - .ForwardStdOut() - .Execute() - .ExitCode; } private static void PrintVersion() { - Reporter.Output.WriteLine(HelpCommand.ProductVersion); + Reporter.Output.WriteLine(Product.Version); } private static void PrintInfo() @@ -146,7 +164,7 @@ private static void PrintInfo() var commitSha = GetCommitSha() ?? "N/A"; Reporter.Output.WriteLine(); Reporter.Output.WriteLine("Product Information:"); - Reporter.Output.WriteLine($" Version: {HelpCommand.ProductVersion}"); + Reporter.Output.WriteLine($" Version: {Product.Version}"); Reporter.Output.WriteLine($" Commit Sha: {commitSha}"); Reporter.Output.WriteLine(); var runtimeEnvironment = PlatformServices.Default.Runtime; diff --git a/src/dotnet/commands/dotnet-help/HelpCommand.cs b/src/dotnet/commands/dotnet-help/HelpCommand.cs index 3f46d87ebf..31c5e65b6c 100644 --- a/src/dotnet/commands/dotnet-help/HelpCommand.cs +++ b/src/dotnet/commands/dotnet-help/HelpCommand.cs @@ -8,7 +8,6 @@ namespace Microsoft.DotNet.Tools.Help { public class HelpCommand { - private const string ProductLongName = ".NET Command Line Tools"; private const string UsageText = @"Usage: dotnet [common-options] [command] [arguments] Arguments: @@ -28,13 +27,6 @@ build Builds a .NET project run Compiles and immediately executes a .NET project repl Launch an interactive session (read, eval, print, loop) pack Creates a NuGet package"; - public static readonly string ProductVersion = GetProductVersion(); - - private static string GetProductVersion() - { - var attr = typeof(HelpCommand).GetTypeInfo().Assembly.GetCustomAttribute(); - return attr?.InformationalVersion; - } public static int Run(string[] args) { @@ -57,10 +49,10 @@ public static void PrintHelp() public static void PrintVersionHeader() { - var versionString = string.IsNullOrEmpty(ProductVersion) ? + var versionString = string.IsNullOrEmpty(Product.Version) ? string.Empty : - $" ({ProductVersion})"; - Reporter.Output.WriteLine(ProductLongName + versionString); + $" ({Product.Version})"; + Reporter.Output.WriteLine(Product.LongName + versionString); } } }