From 7625c8d83d8e83f56a0f42e384baf3948d78db53 Mon Sep 17 00:00:00 2001 From: Brian O'Neill Date: Wed, 10 May 2017 08:55:46 -0700 Subject: [PATCH] Add command line option to specify the logging directory (#336) Two changes in this PR: * Add a --log-dir command line parameter and fix command line parsing * Fix command line parsing where arguments with parameter were parsed incorrectly --- src/Microsoft.SqlTools.Credentials/Program.cs | 48 ++++++++++-------- .../Utility/Logger.cs | 18 +++++++ .../Program.cs | 50 ++++++++++++------- .../Utility/CommandOptions.cs | 38 +++++++++----- .../ServiceHost/SrTests.cs | 4 +- .../Utility/CommandOptionsTests.cs | 21 ++++++-- 6 files changed, 124 insertions(+), 55 deletions(-) diff --git a/src/Microsoft.SqlTools.Credentials/Program.cs b/src/Microsoft.SqlTools.Credentials/Program.cs index b34bad42..9f66db5e 100644 --- a/src/Microsoft.SqlTools.Credentials/Program.cs +++ b/src/Microsoft.SqlTools.Credentials/Program.cs @@ -19,28 +19,36 @@ namespace Microsoft.SqlTools.Credentials /// internal static void Main(string[] args) { - // read command-line arguments - CommandOptions commandOptions = new CommandOptions(args); - if (commandOptions.ShouldExit) + try { - return; + // read command-line arguments + CommandOptions commandOptions = new CommandOptions(args); + if (commandOptions.ShouldExit) + { + return; + } + + // turn on Verbose logging during early development + // we need to switch to Normal when preparing for public preview + Logger.Initialize(minimumLogLevel: LogLevel.Verbose, isEnabled: commandOptions.EnableLogging); + Logger.Write(LogLevel.Normal, "Starting SqlTools Credentials Provider"); + + // set up the host details and profile paths + var hostDetails = new HostDetails( + name: "SqlTools Credentials Provider", + profileId: "Microsoft.SqlTools.Credentials", + version: new Version(1, 0)); + + SqlToolsContext sqlToolsContext = new SqlToolsContext(hostDetails); + CredentialsServiceHost serviceHost = HostLoader.CreateAndStartServiceHost(sqlToolsContext); + + serviceHost.WaitForExit(); + } + catch (Exception e) + { + Logger.Write(LogLevel.Error, string.Format("An unhandled exception occurred: {0}", e)); + Environment.Exit(1); } - - // turn on Verbose logging during early development - // we need to switch to Normal when preparing for public preview - Logger.Initialize(minimumLogLevel: LogLevel.Verbose, isEnabled: commandOptions.EnableLogging); - Logger.Write(LogLevel.Normal, "Starting SqlTools Credentials Provider"); - - // set up the host details and profile paths - var hostDetails = new HostDetails( - name: "SqlTools Credentials Provider", - profileId: "Microsoft.SqlTools.Credentials", - version: new Version(1, 0)); - - SqlToolsContext sqlToolsContext = new SqlToolsContext(hostDetails); - CredentialsServiceHost serviceHost = HostLoader.CreateAndStartServiceHost(sqlToolsContext); - - serviceHost.WaitForExit(); } } } diff --git a/src/Microsoft.SqlTools.Hosting/Utility/Logger.cs b/src/Microsoft.SqlTools.Hosting/Utility/Logger.cs index fa6dedf8..6b515a17 100644 --- a/src/Microsoft.SqlTools.Hosting/Utility/Logger.cs +++ b/src/Microsoft.SqlTools.Hosting/Utility/Logger.cs @@ -73,6 +73,24 @@ namespace Microsoft.SqlTools.Utility Logger.isInitialized = true; + // Create the log directory + string logDir = Path.GetDirectoryName(logFilePath); + if (!string.IsNullOrWhiteSpace(logDir)) + { + if (!Directory.Exists(logDir)) + { + try + { + Directory.CreateDirectory(logDir); + } + catch (Exception) + { + // Creating the log directory is a best effort operation, so ignore any failures. + } + } + } + + // get a unique number to prevent conflicts of two process launching at the same time int uniqueId; try diff --git a/src/Microsoft.SqlTools.ServiceLayer/Program.cs b/src/Microsoft.SqlTools.ServiceLayer/Program.cs index 13bab0b6..15d5e3ae 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Program.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Program.cs @@ -6,6 +6,7 @@ using Microsoft.SqlTools.ServiceLayer.Hosting; using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.SqlTools.ServiceLayer.Utility; using Microsoft.SqlTools.Utility; +using System.IO; namespace Microsoft.SqlTools.ServiceLayer { @@ -19,26 +20,39 @@ namespace Microsoft.SqlTools.ServiceLayer /// internal static void Main(string[] args) { - // read command-line arguments - CommandOptions commandOptions = new CommandOptions(args); - if (commandOptions.ShouldExit) + try { - return; + // read command-line arguments + CommandOptions commandOptions = new CommandOptions(args); + if (commandOptions.ShouldExit) + { + return; + } + + string logFilePath = "sqltools"; + if (!string.IsNullOrWhiteSpace(commandOptions.LoggingDirectory)) + { + logFilePath = Path.Combine(commandOptions.LoggingDirectory, logFilePath); + } + + // turn on Verbose logging during early development + // we need to switch to Normal when preparing for public preview + Logger.Initialize(logFilePath: logFilePath, minimumLogLevel: LogLevel.Verbose, isEnabled: commandOptions.EnableLogging); + Logger.Write(LogLevel.Normal, "Starting SQL Tools Service Host"); + + // set up the host details and profile paths + var hostDetails = new HostDetails(version: new Version(1, 0)); + + SqlToolsContext sqlToolsContext = new SqlToolsContext(hostDetails); + ServiceHost serviceHost = HostLoader.CreateAndStartServiceHost(sqlToolsContext); + + serviceHost.WaitForExit(); + } + catch (Exception e) + { + Logger.Write(LogLevel.Error, string.Format("An unhandled exception occurred: {0}", e)); + Environment.Exit(1); } - - // turn on Verbose logging during early development - // we need to switch to Normal when preparing for public preview - Logger.Initialize(minimumLogLevel: LogLevel.Verbose, isEnabled: commandOptions.EnableLogging); - Logger.Write(LogLevel.Normal, "Starting SQL Tools Service Host"); - - // set up the host details and profile paths - var hostDetails = new HostDetails(version: new Version(1, 0)); - - SqlToolsContext sqlToolsContext = new SqlToolsContext(hostDetails); - ServiceHost serviceHost = HostLoader.CreateAndStartServiceHost(sqlToolsContext); - - serviceHost.WaitForExit(); } - } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Utility/CommandOptions.cs b/src/Microsoft.SqlTools.ServiceLayer/Utility/CommandOptions.cs index 57eda1a9..59888389 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Utility/CommandOptions.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Utility/CommandOptions.cs @@ -5,6 +5,7 @@ using System; using System.Globalization; +using System.IO; namespace Microsoft.SqlTools.ServiceLayer.Utility { @@ -31,28 +32,24 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility // Extracting arguments and properties arg = arg.Substring(1).ToLowerInvariant(); string argName = arg; - string argProperty = ""; - int splitIndex = arg.IndexOf(' '); - if (splitIndex > 0) - { - argName = arg.Substring(0, splitIndex); - argProperty = arg.Substring(splitIndex + 1); - } switch (argName) { case "-enable-logging": EnableLogging = true; break; + case "-log-dir": + SetLoggingDirectory(args[++i]); + break; case "-locale": - SetLocale(argProperty); + SetLocale(args[++i]); break; case "h": case "-help": ShouldExit = true; return; default: - ErrorMessage += String.Format("Unknown argument \"{0}\" with property \"{1}\"" + Environment.NewLine, argName, argProperty); + ErrorMessage += String.Format("Unknown argument \"{0}\"" + Environment.NewLine, argName); break; } } @@ -81,6 +78,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility /// public bool EnableLogging { get; private set; } + /// + /// Gets the directory where log files are output. + /// + public string LoggingDirectory { get; private set; } + /// /// Whether the program should exit immediately. Set to true when the usage is printed. /// @@ -102,6 +104,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility "Microsoft.SqlTools.ServiceLayer.exe " + Environment.NewLine + " Options:" + Environment.NewLine + " [--enable-logging]" + Environment.NewLine + + " [--log-dir **] (default: current directory)" + Environment.NewLine + " [--help]" + Environment.NewLine + " [--locale **] (default: 'en')" + Environment.NewLine, ErrorMessage); @@ -109,6 +112,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility } } + private void SetLoggingDirectory(string loggingDirectory) + { + if (string.IsNullOrWhiteSpace(loggingDirectory)) + { + return; + } + + this.LoggingDirectory = Path.GetFullPath(loggingDirectory); + } + private void SetLocale(string locale) { try @@ -124,10 +137,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Utility // Setting our internal SR culture to our global culture SR.Culture = CultureInfo.CurrentCulture; } - catch(Exception ex) + catch (CultureNotFoundException) { - // Warn user of invalid locale, and fall back to english - Console.WriteLine(ex); + // Ignore CultureNotFoundException since it only is thrown before Windows 10. Windows 10, + // along with macOS and Linux, pick up the default culture if an invalid locale is passed + // into the CultureInfo constructor. } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ServiceHost/SrTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ServiceHost/SrTests.cs index 9400ee43..a8f4a07a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ServiceHost/SrTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/ServiceHost/SrTests.cs @@ -212,7 +212,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ServiceHost public void SrStringsTestWithEnLocalization() { string locale = "en"; - var args = new string[] { "--locale " + locale }; + var args = new string[] { "--locale", locale }; CommandOptions options = new CommandOptions(args); Assert.Equal(SR.Culture.Name, options.Locale); Assert.Equal(options.Locale, locale); @@ -225,7 +225,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ServiceHost public void SrStringsTestWithEsLocalization() { string locale = "es"; - var args = new string[] { "--locale " + locale }; + var args = new string[] { "--locale", locale }; CommandOptions options = new CommandOptions(args); Assert.Equal(SR.Culture.Name, options.Locale); Assert.Equal(options.Locale, locale); diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/CommandOptionsTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/CommandOptionsTests.cs index 0ba99322..3771c05a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/CommandOptionsTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/CommandOptionsTests.cs @@ -4,6 +4,7 @@ // using Microsoft.SqlTools.ServiceLayer.Utility; +using System.IO; using Xunit; namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility @@ -68,6 +69,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility Assert.False(options.EnableLogging); Assert.False(options.ShouldExit); + Assert.True(string.IsNullOrWhiteSpace(options.LoggingDirectory)); Assert.Equal(options.Locale, string.Empty); } @@ -76,7 +78,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility [InlineData("es")] public void LocaleSetWhenProvided(string locale) { - var args = new string[] {"--locale " + locale}; + var args = new string[] {"--locale", locale}; CommandOptions options = new CommandOptions(args); // Asserting all options were properly set @@ -86,10 +88,10 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility } [Fact] - public void ShouldExitSetWhenInvalidLocale() + public void ShouldExitNotSetWhenInvalidLocale() { string locale = "invalid"; - var args = new string[] { "--locale " + locale }; + var args = new string[] { "--locale", locale }; CommandOptions options = new CommandOptions(args); // Asserting all options were properly set @@ -109,5 +111,18 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility Assert.False(options.ShouldExit); Assert.Equal(options.Locale, string.Empty); } + + [Fact] + public void LoggingDirectorySet() + { + string logDir = Directory.GetCurrentDirectory(); + var args = new string[] { "--log-dir", logDir }; + CommandOptions options = new CommandOptions(args); + + // Asserting all options were properly set + Assert.NotNull(options); + Assert.False(options.ShouldExit); + Assert.Equal(options.LoggingDirectory, logDir); + } } }