diff --git a/loc/lcl/CHS/OpenFolderSchema.json.lcl b/loc/lcl/CHS/OpenFolderSchema.json.lcl index a17ab9b24..1d907d5d2 100644 --- a/loc/lcl/CHS/OpenFolderSchema.json.lcl +++ b/loc/lcl/CHS/OpenFolderSchema.json.lcl @@ -865,6 +865,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/CHT/OpenFolderSchema.json.lcl b/loc/lcl/CHT/OpenFolderSchema.json.lcl index 0ce4d648b..5072a7d00 100644 --- a/loc/lcl/CHT/OpenFolderSchema.json.lcl +++ b/loc/lcl/CHT/OpenFolderSchema.json.lcl @@ -865,6 +865,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/CSY/OpenFolderSchema.json.lcl b/loc/lcl/CSY/OpenFolderSchema.json.lcl index d3dd4ad6f..796e5db74 100644 --- a/loc/lcl/CSY/OpenFolderSchema.json.lcl +++ b/loc/lcl/CSY/OpenFolderSchema.json.lcl @@ -865,6 +865,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/DEU/OpenFolderSchema.json.lcl b/loc/lcl/DEU/OpenFolderSchema.json.lcl index b8941d421..e89c066ea 100644 --- a/loc/lcl/DEU/OpenFolderSchema.json.lcl +++ b/loc/lcl/DEU/OpenFolderSchema.json.lcl @@ -865,6 +865,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/ESN/OpenFolderSchema.json.lcl b/loc/lcl/ESN/OpenFolderSchema.json.lcl index 8a9a7376d..3e06b2806 100644 --- a/loc/lcl/ESN/OpenFolderSchema.json.lcl +++ b/loc/lcl/ESN/OpenFolderSchema.json.lcl @@ -814,6 +814,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/FRA/OpenFolderSchema.json.lcl b/loc/lcl/FRA/OpenFolderSchema.json.lcl index 27f67fc05..613cfd697 100644 --- a/loc/lcl/FRA/OpenFolderSchema.json.lcl +++ b/loc/lcl/FRA/OpenFolderSchema.json.lcl @@ -865,6 +865,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/ITA/OpenFolderSchema.json.lcl b/loc/lcl/ITA/OpenFolderSchema.json.lcl index 7443d1a3b..96eae5bef 100644 --- a/loc/lcl/ITA/OpenFolderSchema.json.lcl +++ b/loc/lcl/ITA/OpenFolderSchema.json.lcl @@ -865,6 +865,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/JPN/OpenFolderSchema.json.lcl b/loc/lcl/JPN/OpenFolderSchema.json.lcl index 68e3dd5f7..07566f91b 100644 --- a/loc/lcl/JPN/OpenFolderSchema.json.lcl +++ b/loc/lcl/JPN/OpenFolderSchema.json.lcl @@ -865,6 +865,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/KOR/OpenFolderSchema.json.lcl b/loc/lcl/KOR/OpenFolderSchema.json.lcl index 2aa095794..ecdc13638 100644 --- a/loc/lcl/KOR/OpenFolderSchema.json.lcl +++ b/loc/lcl/KOR/OpenFolderSchema.json.lcl @@ -865,6 +865,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/PLK/OpenFolderSchema.json.lcl b/loc/lcl/PLK/OpenFolderSchema.json.lcl index e716460bb..e6f347966 100644 --- a/loc/lcl/PLK/OpenFolderSchema.json.lcl +++ b/loc/lcl/PLK/OpenFolderSchema.json.lcl @@ -865,6 +865,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/PTB/OpenFolderSchema.json.lcl b/loc/lcl/PTB/OpenFolderSchema.json.lcl index ad42d83f9..aaecef349 100644 --- a/loc/lcl/PTB/OpenFolderSchema.json.lcl +++ b/loc/lcl/PTB/OpenFolderSchema.json.lcl @@ -865,6 +865,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/RUS/OpenFolderSchema.json.lcl b/loc/lcl/RUS/OpenFolderSchema.json.lcl index 03db31052..3e4c12882 100644 --- a/loc/lcl/RUS/OpenFolderSchema.json.lcl +++ b/loc/lcl/RUS/OpenFolderSchema.json.lcl @@ -814,6 +814,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/loc/lcl/TRK/OpenFolderSchema.json.lcl b/loc/lcl/TRK/OpenFolderSchema.json.lcl index 8ceb87e84..7553f7bec 100644 --- a/loc/lcl/TRK/OpenFolderSchema.json.lcl +++ b/loc/lcl/TRK/OpenFolderSchema.json.lcl @@ -865,6 +865,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AndroidDebugLauncher/InstallPaths.cs b/src/AndroidDebugLauncher/InstallPaths.cs index 72a1b9f12..70c456b10 100644 --- a/src/AndroidDebugLauncher/InstallPaths.cs +++ b/src/AndroidDebugLauncher/InstallPaths.cs @@ -69,7 +69,7 @@ public static InstallPaths Resolve(CancellationToken token, AndroidLaunchOptions ThrowExternalFileNotFoundException(ndkReleaseVersionFile, LauncherResources.ProductName_NDK); } - logger.WriteLine("Using NDK '{0}' from path '{1}'", ndkReleaseId, ndkRoot); + logger.WriteLine(Microsoft.DebugEngineHost.LogLevel.Verbose, "Using NDK '{0}' from path '{1}'", ndkReleaseId, ndkRoot); // 32 vs 64-bit doesn't matter when comparing var r11 = new NdkReleaseId(11, 'a'); diff --git a/src/AndroidDebugLauncher/Launcher.cs b/src/AndroidDebugLauncher/Launcher.cs index 1ca37f537..0730aef67 100644 --- a/src/AndroidDebugLauncher/Launcher.cs +++ b/src/AndroidDebugLauncher/Launcher.cs @@ -52,7 +52,7 @@ void IPlatformAppLauncher.Initialize(HostConfigurationStore configStore, IDevice _eventCallback = eventCallback; RegistryRoot.Set(configStore.RegistryRoot); - Logger = MICore.Logger.EnsureInitialized(configStore); + Logger = MICore.Logger.EnsureInitialized(); } void IPlatformAppLauncher.SetLaunchOptions(string exePath, string args, string dir, object launcherXmlOptions, TargetEngine targetEngine) @@ -747,7 +747,7 @@ private Task StartGdbServer(string gdbServerRemotePath, string workingDirectory, debugMessage.Replace("\r", "\\r"); debugMessage.Replace("\n", "\\n"); debugMessage.Replace("\t", "\\t"); - Logger.WriteLine(debugMessage.ToString()); + Logger.WriteLine(LogLevel.Verbose, debugMessage.ToString()); } // Here is the expected output from GDB Server -- @@ -769,7 +769,7 @@ private Task StartGdbServer(string gdbServerRemotePath, string workingDirectory, _gdbServerExecCancellationSource.Token.ThrowIfCancellationRequested(); - Logger.WriteLine("ADB<-{0}", gdbServerCommand); + Logger.WriteLine(LogLevel.Verbose, "ADB<-{0}", gdbServerCommand); Task serverExitedOrCanceled = _shell.ExecAsync(gdbServerCommand, _gdbServerExecCancellationSource.Token, outputHandler); int completedTask = Task.WaitAny(serverReady.Task, serverExitedOrCanceled); @@ -781,7 +781,7 @@ private Task StartGdbServer(string gdbServerRemotePath, string workingDirectory, // they fail, try again with TCP. if (useUnixSocket && HasGdbServerInvalidSocketError(errorOutput)) { - Logger.WriteLine("Retrying GDB Server launch using TCP socket."); + Logger.WriteLine(LogLevel.Verbose, "Retrying GDB Server launch using TCP socket."); return StartGdbServer(gdbServerRemotePath, workingDirectory, /*useUnixSocket:*/ false, out gdbServerSocketDescription); } @@ -842,11 +842,11 @@ private string ExecCommand(string command) { Debug.Assert(_shell != null, "ExecCommand called before m_shell is set"); - Logger.WriteLine("ADB<-{0}", command); + Logger.WriteLine(LogLevel.Verbose, "ADB<-{0}", command); string response = ExecCommandNoLog(command); - Logger.WriteTextBlock("ADB->", response); + Logger.WriteTextBlock(LogLevel.Verbose, "ADB->", response); return response; } @@ -892,7 +892,7 @@ void IPlatformAppLauncher.OnResume() } catch (JDbg.JdwpException e) { - Logger.WriteLine("JdwpException: {0}", e.Message); + Logger.WriteLine(LogLevel.Warning, "JdwpException: {0}", e.Message); string message = LauncherResources.Warning_JDbgResumeFailure; diff --git a/src/DebugEngineHost.Common/HostLogChannel.cs b/src/DebugEngineHost.Common/HostLogChannel.cs new file mode 100644 index 000000000..14bdc1b57 --- /dev/null +++ b/src/DebugEngineHost.Common/HostLogChannel.cs @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +/* + * NOTE: This file is shared between DebugEngineHost and DebugEngineHost.VSCode + */ + +using System; +using System.Globalization; +using System.IO; + +namespace Microsoft.DebugEngineHost +{ + public enum LogLevel + { + /// + /// Logs that are used for interactive investigation during development. + /// These logs should primarily contain information useful for debugging and have no long-term value. + /// + Verbose, + /// + /// Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the application execution to stop. + /// + Warning, + /// + /// Logs that highlight when the current flow of execution is stopped due to a failure. + /// These should indicate a failure in the current activity, not an application-wide failure. + /// + Error, + /// + /// Not used for writing log messages. + /// Specifies that a logging category should not write any messages. + /// + None + } + + // This must match the interface in DebugEngineHost.ref.cs + public interface ILogChannel + { + void SetLogLevel(LogLevel level); + + void WriteLine(LogLevel level, string message); + + void WriteLine(LogLevel level, string format, params object[] values); + + void Flush(); + + void Close(); + } + + public class HostLogChannel : ILogChannel + { + private readonly Action _log; + private StreamWriter _logFile; + private LogLevel _minLevelToBeLogged; + + private readonly object _lock = new object(); + + private HostLogChannel() { } + + public HostLogChannel(Action logAction, string file, LogLevel logLevel) + { + _log = logAction; + + if (!string.IsNullOrEmpty(file)) + { + _logFile = File.CreateText(file); + } + + _minLevelToBeLogged = logLevel; + } + + /// + /// Sets the log level to the provided level. + /// + /// The level to set the logger. + public void SetLogLevel(LogLevel level) + { + _minLevelToBeLogged = level; + } + + /// + /// + /// + /// + /// + public void WriteLine(LogLevel level, string message) + { + if (level >= _minLevelToBeLogged) + { + lock (_lock) + { + string prefix = string.Empty; + // Only indicate level if not verbose. + if (level != LogLevel.Verbose) + { + prefix = string.Format(CultureInfo.InvariantCulture, "[{0}] ", level.ToString()); + } + string levelMsg = string.Format(CultureInfo.InvariantCulture, "{0}{1}", prefix, message); + _log?.Invoke(levelMsg); + _logFile?.WriteLine(levelMsg); + _logFile?.Flush(); + } + + } + } + + /// + /// + /// + /// + /// + /// + public void WriteLine(LogLevel level, string format, params object[] values) + { + if (level >= _minLevelToBeLogged) + { + lock (_lock) + { + string message = string.Format(CultureInfo.CurrentCulture, format, values); + this.WriteLine(level, message); + } + } + } + + public void Flush() + { + lock (_lock) + { + _logFile?.Flush(); + } + } + + public void Close() + { + lock (_lock) + { + _logFile?.Close(); + _logFile = null; + } + } + } +} diff --git a/src/DebugEngineHost.Stub/DebugEngineHost.ref.cs b/src/DebugEngineHost.Stub/DebugEngineHost.ref.cs index 9d528c1bd..9b42b62e0 100644 --- a/src/DebugEngineHost.Stub/DebugEngineHost.ref.cs +++ b/src/DebugEngineHost.Stub/DebugEngineHost.ref.cs @@ -162,21 +162,6 @@ public void GetExceptionCategorySettings(Guid categoryId, out HostConfigurationS throw new NotImplementedException(); } - /// - /// Checks if logging is enabled, and if so returns a logger object. - /// - /// In VS, this is wired up to read from the registry and return a logger which writes a log file to %TMP%\log-file-name. - /// In VS Code, this will check if the '--engineLogging' switch is enabled, and if so return a logger that will write to the Console. - /// - /// [Optional] In VS, the name of the settings key to check if logging is enabled. - /// If not specified, this will check 'EnableLogging' in the AD7 Metrics. - /// [Required] name of the log file to open if logging is enabled. - /// [Optional] If logging is enabled, the logging object. - public HostLogger GetLogger(string enableLoggingSettingName, string logFileName) - { - throw new NotImplementedException(); - } - /// /// Read the debugger setting /// @@ -197,56 +182,129 @@ public object GetCustomLauncher(string launcherTypeName) { throw new NotImplementedException(); } -} + } + + /// + /// Level of logging used for HostLogChannel + /// + public enum LogLevel + { + /// + /// Logs that are used for interactive investigation during development. + /// These logs should primarily contain information useful for debugging and have no long-term value. + /// + Verbose, + /// + /// Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the application execution to stop. + /// + Warning, + /// + /// Logs that highlight when the current flow of execution is stopped due to a failure. + /// These should indicate a failure in the current activity, not an application-wide failure. + /// + Error, + /// + /// Not used for writing log messages. + /// Specifies that a logging category should not write any messages. + /// + None + } + + /// + /// The channel used for logging messages. + /// Channels are used if there are multiple types of logs, + /// e.g. Engine logs and Natvis logs + /// + public interface ILogChannel + { + /// + /// Changes the log level of the channel + /// + /// The new log level to use. + void SetLogLevel(LogLevel newLevel); + + /// + /// Writes the given message with a newline to the log channel. + /// + /// The level of the log + /// The message string to send. + void WriteLine(LogLevel level, string message); + + /// + /// Writes the given formatted message with the additional values with a newline to the log channel. + /// + /// The level of the log + /// Format to use. + /// Values to use within the provided format. + void WriteLine(LogLevel level, string format, params object[] values); -/// -/// The host logger returned from HostConfigurationStore.GetLogger. -/// -public sealed class HostLogger + /// + /// If the log is implemented as a file, this flushes the file. + /// + void Flush(); + + /// + /// If the log is implemented as a file, this closes the file. + /// + void Close(); + } + + /// + /// + /// + public static class HostLogger { /// - /// Callback for programmatic display of log messages + /// Enables engine logging if not already enabled. /// - /// - public delegate void OutputCallback(string outputMessage); + /// The callback to use to send the engine log. + /// The level of the log to filter the channel on. + public static void EnableHostLogging(Action callback, LogLevel level = LogLevel.Verbose) + { + throw new NotImplementedException(); + } - private HostLogger() + /// + /// Enables natvis logging if not already enabled. + /// + /// The callback to use to send the natvis log. + /// The level of the log to filter the channel on. + public static void EnableNatvisDiagnostics(Action callback, LogLevel level = LogLevel.Verbose) { throw new NotImplementedException(); } /// - /// Writes a line to the log + /// Sets the log file to write to. /// - /// Line to write. - public void WriteLine(string line) + /// The file to write engine logs to. + public static void SetEngineLogFile(string logFile) { throw new NotImplementedException(); } /// - /// If the log is implemented as a file, this flushes the file. + /// Gets the engine log channel created by 'EnableHostLogging' /// - public void Flush() + /// A logger object if logging is enabled, or null if it is not + public static ILogChannel GetEngineLogChannel() { throw new NotImplementedException(); } /// - /// If the log is implemented as a file, this closes the file. + /// Gets the Natvis log channel if its been created. /// - public void Close() + /// A logger object if logging is enabled, or null if it is not + public static ILogChannel GetNatvisLogChannel() { throw new NotImplementedException(); } /// - /// Get a logger after the user has explicitly configured a log file/callback + /// Clears the logging objects if enabled. /// - /// - /// - /// The host logger object - public static HostLogger GetLoggerFromCmd(string logFileName, HostLogger.OutputCallback callback) + public static void Reset() { throw new NotImplementedException(); } @@ -394,6 +452,14 @@ public static void FindNatvis(NatvisLoader loader) throw new NotImplementedException(); } + /// + /// Enable's tracking the VS 'Natvis Diagnostic Messages (C++ only)' setting. + /// + public static IDisposable WatchNatvisOptionSetting(HostConfigurationStore configStore, ILogChannel natvisLogger) + { + throw new NotImplementedException(); + } + /// /// Return the solution's root directory, null if no solution /// diff --git a/src/DebugEngineHost.VSCode/DebugEngineHost.VSCode.csproj b/src/DebugEngineHost.VSCode/DebugEngineHost.VSCode.csproj index eef5e304b..428c8b4ca 100644 --- a/src/DebugEngineHost.VSCode/DebugEngineHost.VSCode.csproj +++ b/src/DebugEngineHost.VSCode/DebugEngineHost.VSCode.csproj @@ -23,6 +23,8 @@ + + diff --git a/src/DebugEngineHost.VSCode/HostConfigurationStore.cs b/src/DebugEngineHost.VSCode/HostConfigurationStore.cs index 8eaf45de3..9736c3735 100644 --- a/src/DebugEngineHost.VSCode/HostConfigurationStore.cs +++ b/src/DebugEngineHost.VSCode/HostConfigurationStore.cs @@ -64,20 +64,6 @@ public void GetExceptionCategorySettings(Guid categoryId, out HostConfigurationS categoryConfigSection = new HostConfigurationSection(category.DefaultTriggers); } - /// - /// Checks if logging is enabled, and if so returns a logger object. - /// - /// In VS, this is wired up to read from the registry and return a logger which writes a log file to %TMP%\log-file-name. - /// In VS Code, this will check if the '--engineLogging' switch is enabled, and if so return a logger that wil write to the logger output. - /// - /// [Optional] In VS, the name of the settings key to check if logging is enabled. If not specified, this will check 'EnableLogging' in the AD7 Metrics. - /// [Required] name of the log file to open if logging is enabled. This is ignored for VSCode. - /// [Optional] If logging is enabled, the logging object. - public HostLogger GetLogger(string enableLoggingSettingName, string logFileName) - { - return HostLogger.Instance; - } - /// /// Read the debugger setting /// diff --git a/src/DebugEngineHost.VSCode/HostLogger.cs b/src/DebugEngineHost.VSCode/HostLogger.cs index 71658194d..6723a4aee 100644 --- a/src/DebugEngineHost.VSCode/HostLogger.cs +++ b/src/DebugEngineHost.VSCode/HostLogger.cs @@ -5,85 +5,51 @@ namespace Microsoft.DebugEngineHost { - public sealed class HostLogger + public static class HostLogger { - public delegate void OutputCallback(string outputMessage); + private static ILogChannel s_natvisLogChannel; + private static ILogChannel s_engineLogChannel; - private static HostLogger s_instance; - private static readonly object s_lock = new object(); + private static string s_engineLogFile; - /// [Optional] VSCode-only host logger instance. - public static HostLogger Instance { get { return s_instance; } } - - /// [Optional] VSCode-only method for obtaining the current host logger instance. - public static void EnableHostLogging() + public static void EnableNatvisDiagnostics(Action callback, LogLevel level = LogLevel.Verbose) { - if (s_instance == null) + if (s_natvisLogChannel == null) { - lock (s_lock) - { - if (s_instance == null) - { - s_instance = new HostLogger(); - } - } + // TODO: Support writing natvis logs to a file. + s_natvisLogChannel = new HostLogChannel(callback, null, level); } } - private string _logFilePath = null; - private System.IO.StreamWriter _logFile = null; - - /// Callback for logging text to the desired output stream. - public Action LogCallback { get; set; } = null; - - /// The path to the log file. - public string LogFilePath + public static void EnableHostLogging(Action callback, LogLevel level = LogLevel.Verbose) { - get + if (s_engineLogChannel == null) { - return _logFilePath; - } - set - { - _logFile?.Dispose(); - _logFilePath = value; - - if (!String.IsNullOrEmpty(_logFilePath)) - { - _logFile = System.IO.File.CreateText(_logFilePath); - } + s_engineLogChannel = new HostLogChannel(callback, s_engineLogFile, level); } } - private HostLogger() { } - - public void WriteLine(string line) + public static void SetEngineLogFile(string logFile) { - lock (s_lock) - { - _logFile?.WriteLine(line); - _logFile?.Flush(); - LogCallback?.Invoke(line); - } + s_engineLogFile = logFile; } - public void Flush() + public static ILogChannel GetEngineLogChannel() { + return s_engineLogChannel; } - public void Close() + public static ILogChannel GetNatvisLogChannel() { + return s_natvisLogChannel; } - /// - /// Get a logger after the user has explicitly configured a log file/callback - /// - /// - /// - /// The host logger object - public static HostLogger GetLoggerFromCmd(string logFileName, HostLogger.OutputCallback callback) + public static void Reset() { - throw new NotImplementedException(); + s_natvisLogChannel?.Close(); + s_natvisLogChannel = null; + s_engineLogChannel?.Close(); + s_engineLogChannel = null; } } } diff --git a/src/DebugEngineHost.VSCode/HostNatvisProject.cs b/src/DebugEngineHost.VSCode/HostNatvisProject.cs index 7456f3b1d..e24c4f812 100644 --- a/src/DebugEngineHost.VSCode/HostNatvisProject.cs +++ b/src/DebugEngineHost.VSCode/HostNatvisProject.cs @@ -14,6 +14,12 @@ public static void FindNatvis(NatvisLoader loader) // In-solution natvis is not supported for VS Code now, so do nothing. } + public static IDisposable WatchNatvisOptionSetting(HostConfigurationStore configStore, ILogChannel natvisLogger) + { + // VS Code does not have a registry setting for Natvis Diagnostics + return null; + } + public static string FindSolutionRoot() { // This was added in MIEngine to support breakpoint sourcefile mapping. diff --git a/src/DebugEngineHost.VSCode/Logger.cs b/src/DebugEngineHost.VSCode/Logger.cs deleted file mode 100644 index 19dcaa868..000000000 --- a/src/DebugEngineHost.VSCode/Logger.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.DebugEngineHost -{ - /// - /// VS Code only class to write to the log. This is enabled through the '--engineLogging[=file]' command line argument. - /// - public static class Logger - { - public static void WriteFrame([CallerMemberName]string caller = null) - { - CoreWrite(caller); - } - - public static void WriteLine(string s) - { - CoreWrite(s); - } - - private static void CoreWrite(string line) - { - HostLogger.Instance?.WriteLine(line); - } - } -} diff --git a/src/DebugEngineHost/DebugEngineHost.csproj b/src/DebugEngineHost/DebugEngineHost.csproj index ba45d7f48..5a09a365d 100755 --- a/src/DebugEngineHost/DebugEngineHost.csproj +++ b/src/DebugEngineHost/DebugEngineHost.csproj @@ -29,6 +29,8 @@ + + diff --git a/src/DebugEngineHost/HostConfigurationSection.cs b/src/DebugEngineHost/HostConfigurationSection.cs index c076e8e18..7f3f3f402 100644 --- a/src/DebugEngineHost/HostConfigurationSection.cs +++ b/src/DebugEngineHost/HostConfigurationSection.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.Win32; +using Microsoft.Win32.SafeHandles; using System; using System.Collections.Generic; using System.Linq; @@ -42,5 +43,7 @@ public IEnumerable GetValueNames() { return _key.GetValueNames(); } + + public SafeRegistryHandle Handle => _key.Handle; } } diff --git a/src/DebugEngineHost/HostConfigurationStore.cs b/src/DebugEngineHost/HostConfigurationStore.cs index 258dc59ec..1f491a3d9 100644 --- a/src/DebugEngineHost/HostConfigurationStore.cs +++ b/src/DebugEngineHost/HostConfigurationStore.cs @@ -8,8 +8,10 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using static Microsoft.VisualStudio.Shell.RegistrationAttribute; namespace Microsoft.DebugEngineHost { @@ -17,9 +19,12 @@ public sealed class HostConfigurationStore { private const string DebuggerSectionName = "Debugger"; private const string LaunchersSectionName = "MILaunchers"; + private const string NatvisDiagnosticsSectionName = "NatvisDiagnostics"; private string _engineId; private string _registryRoot; + + // HKLM RegistryKey private RegistryKey _configKey; public HostConfigurationStore(string registryRoot) @@ -81,38 +86,6 @@ public void GetExceptionCategorySettings(Guid categoryId, out HostConfigurationS categoryName = categoryKey.GetSubKeyNames().Single(); } - /// - /// Checks if logging is enabled, and if so returns a logger object. - /// - /// [Optional] In VS, the name of the settings key to check if logging is enabled. If not specified, this will check 'Logging' in the AD7 Metrics. - /// [Required] name of the log file to open if logging is enabled. - /// If no error then logging object. If file cannot be openened then throw an exception. Otherwise return an empty logger - the user can explictly reconfigure it later - public HostLogger GetLogger(string enableLoggingSettingName, string logFileName) - { - if (string.IsNullOrEmpty(logFileName)) - { - throw new ArgumentNullException(nameof(logFileName)); - } - object enableLoggingValue; - if (!string.IsNullOrEmpty(enableLoggingSettingName)) - { - enableLoggingValue = GetOptionalValue(DebuggerSectionName, enableLoggingSettingName); - } - else - { - enableLoggingValue = GetEngineMetric("EnableLogging"); - } - - if (enableLoggingValue == null || - !(enableLoggingValue is int) || - ((int)enableLoggingValue) == 0) - { - return null; - } - - return new HostLogger(HostLogger.GetStreamForName(logFileName, throwInUseError:false)); - } - public T GetDebuggerConfigurationSetting(string settingName, T defaultValue) { return GetDebuggerConfigurationSetting(DebuggerSectionName, settingName, defaultValue); @@ -163,5 +136,45 @@ private object GetOptionalValue(string section, string valueName) return key.GetValue(valueName); } } + + /// + /// This method grabs the Debugger Subkey in HKCU + /// + /// The subkey of Debugger if it exists. Returns null otherwise. + public HostConfigurationSection GetCurrentUserDebuggerSection() + { + using (RegistryKey hkcuRoot = Registry.CurrentUser.OpenSubKey(_registryRoot)) + { + RegistryKey debuggerSection = hkcuRoot.OpenSubKey(DebuggerSectionName); + if (debuggerSection != null) + { + return new HostConfigurationSection(debuggerSection); + } + return null; + } + } + + /// + /// Grabs the Debugger/NatvisDiagnostic subkey in HKCU + /// + /// The NatvisDiagnostic subkey if it exists. Returns null otherwise. + public HostConfigurationSection GetNatvisDiagnosticSection() + { + using (RegistryKey hkcuRoot = Registry.CurrentUser.OpenSubKey(_registryRoot)) + { + using (RegistryKey debuggerSection = hkcuRoot.OpenSubKey(DebuggerSectionName)) + { + if (debuggerSection != null) + { + RegistryKey natvisDiagnosticKey = debuggerSection.OpenSubKey(NatvisDiagnosticsSectionName); + if (natvisDiagnosticKey != null) + { + return new HostConfigurationSection(natvisDiagnosticKey); + } + } + } + } + return null; + } } } diff --git a/src/DebugEngineHost/HostLogger.cs b/src/DebugEngineHost/HostLogger.cs index 808a24394..9ce19cf22 100644 --- a/src/DebugEngineHost/HostLogger.cs +++ b/src/DebugEngineHost/HostLogger.cs @@ -3,101 +3,59 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Microsoft.DebugEngineHost { - public sealed class HostLogger + public static class HostLogger { - /// - /// Callback for programmatic display of log messages - /// - /// - public delegate void OutputCallback(string outputString); + private static ILogChannel s_natvisLogChannel; + private static ILogChannel s_engineLogChannel; - private StreamWriter _streamWriter; - private OutputCallback _callback; - private readonly object _locker = new object(); + private static string s_engineLogFile; - internal HostLogger(StreamWriter streamWriter = null, OutputCallback callback = null) + public static void EnableHostLogging(Action callback, LogLevel level = LogLevel.Verbose) { - _streamWriter = streamWriter; - _callback = callback; + if (s_engineLogChannel == null) + { + s_engineLogChannel = new HostLogChannel(callback, s_engineLogFile, level); + } } - public void WriteLine(string line) + public static void EnableNatvisDiagnostics(Action callback, LogLevel level = LogLevel.Verbose) { - lock (_locker) + if (s_natvisLogChannel== null) { - if (_streamWriter != null) - _streamWriter.WriteLine(line); - _callback?.Invoke(line); + s_natvisLogChannel = new HostLogChannel(callback, null, level); } } - public void Flush() + public static void DisableNatvisDiagnostics() { - lock (_locker) - { - if (_streamWriter != null) - _streamWriter.Flush(); - } + s_natvisLogChannel = null; } - public void Close() + public static void SetEngineLogFile(string logFile) { - lock (_locker) - { - if (_streamWriter != null) - _streamWriter.Close(); - _streamWriter = null; - } + s_engineLogFile = logFile; } - internal static StreamWriter GetStreamForName(string logFileName, bool throwInUseError) + public static ILogChannel GetEngineLogChannel() { - if (string.IsNullOrEmpty(logFileName)) - { - return null; - } - string tempDirectory = Path.GetTempPath(); - StreamWriter writer = null; - if (Path.IsPathRooted(logFileName) || (!string.IsNullOrEmpty(tempDirectory) && Directory.Exists(tempDirectory))) - { - string filePath = Path.Combine(tempDirectory, logFileName); + return s_engineLogChannel; + } - try - { - FileStream stream = File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.Read); - writer = new StreamWriter(stream); - } - catch (IOException) - { - if (throwInUseError) - throw; - // ignore failures from the log being in use by another process - } - } - else - { - throw new ArgumentOutOfRangeException(nameof(logFileName)); - } - return writer; + public static ILogChannel GetNatvisLogChannel() + { + return s_natvisLogChannel; } - /// - /// Get a logger after the user has explicitly configured a log file/callback - /// - /// - /// - /// The host logger object - public static HostLogger GetLoggerFromCmd(string logFileName, HostLogger.OutputCallback callback) + public static void Reset() { - StreamWriter writer = HostLogger.GetStreamForName(logFileName, throwInUseError: true); - return new HostLogger(writer, callback); + s_natvisLogChannel = null; + s_engineLogChannel = null; } } } diff --git a/src/DebugEngineHost/HostNatvisProject.cs b/src/DebugEngineHost/HostNatvisProject.cs index 5ccf3974d..4c969b652 100644 --- a/src/DebugEngineHost/HostNatvisProject.cs +++ b/src/DebugEngineHost/HostNatvisProject.cs @@ -18,9 +18,26 @@ using Microsoft.VisualStudio.Workspace; using Microsoft.VisualStudio.Workspace.Indexing; using Microsoft.VisualStudio.Workspace.VSIntegration.Contracts; +using Microsoft.Win32; namespace Microsoft.DebugEngineHost { + internal class RegisterMonitorWrapper : IDisposable + { + public RegistryMonitor CurrentMonitor { get; set; } + + internal RegisterMonitorWrapper(RegistryMonitor currentMonitor) + { + CurrentMonitor = currentMonitor; + } + + public void Dispose() + { + CurrentMonitor.Dispose(); + CurrentMonitor = null; + } + } + /// /// Provides interactions with the host's source workspace to locate and load any natvis files /// in the project. @@ -49,6 +66,113 @@ public static void FindNatvis(NatvisLoader loader) paths.ForEach((s) => loader(s)); } + public static IDisposable WatchNatvisOptionSetting(HostConfigurationStore configStore, ILogChannel natvisLogger) + { + RegisterMonitorWrapper rmw = null; + + HostConfigurationSection natvisDiagnosticSection = configStore.GetNatvisDiagnosticSection(); + if (natvisDiagnosticSection != null) + { + // DiagnosticSection exists, set current log level and watch for changes. + SetNatvisLogLevel(natvisDiagnosticSection); + + rmw = new RegisterMonitorWrapper(CreateAndStartNatvisDiagnosticMonitor(natvisDiagnosticSection, natvisLogger)); + } + else + { + // NatvisDiagnostic section has not been created, we need to watch for the creation. + HostConfigurationSection debuggerSection = configStore.GetCurrentUserDebuggerSection(); + + if (debuggerSection != null) + { + // We only care about the debugger subkey's keys since we are waiting for the NatvisDiagnostics + // section to be created. + RegistryMonitor rm = new RegistryMonitor(debuggerSection, false, natvisLogger); + + rmw = new RegisterMonitorWrapper(rm); + + rm.RegChanged += (sender, e) => + { + HostConfigurationSection checkForSection = configStore.GetNatvisDiagnosticSection(); + + if (checkForSection != null) + { + // NatvisDiagnostic section found. Update the logger + SetNatvisLogLevel(checkForSection); + + // Remove debugger section tracking + IDisposable disposable = rmw.CurrentMonitor; + + // Watch NatvisDiagnostic section + rmw = new RegisterMonitorWrapper(CreateAndStartNatvisDiagnosticMonitor(natvisDiagnosticSection, natvisLogger)); + + disposable.Dispose(); + } + }; + + rm.Start(); + } + } + + + return rmw; + } + + private static RegistryMonitor CreateAndStartNatvisDiagnosticMonitor(HostConfigurationSection natvisDiagnosticSection, ILogChannel natvisLogger) + { + RegistryMonitor rm = new RegistryMonitor(natvisDiagnosticSection, true, natvisLogger); + + rm.RegChanged += (sender, e) => + { + SetNatvisLogLevel(natvisDiagnosticSection); + }; + + rm.Start(); + + return rm; + } + + private static void SetNatvisLogLevel(HostConfigurationSection natvisDiagnosticSection) + { + string level = natvisDiagnosticSection.GetValue("Level") as string; + if (level != null) + { + level = level.ToLower(CultureInfo.InvariantCulture); + } + LogLevel logLevel; + switch (level) + { + case "off": + logLevel = LogLevel.None; + break; + case "error": + logLevel = LogLevel.Error; + break; + case "warning": + logLevel = LogLevel.Warning; + break; + case "verbose": + logLevel = LogLevel.Verbose; + break; + default: // Unknown, default to Warning + logLevel = LogLevel.Warning; + break; + } + + if (logLevel == LogLevel.None) + { + HostLogger.DisableNatvisDiagnostics(); + } + else + { + HostLogger.EnableNatvisDiagnostics((message) => { + string formattedMessage = string.Format(CultureInfo.InvariantCulture, "Natvis: {0}", message); + HostOutputWindow.WriteLaunchError(formattedMessage); + }, logLevel); + HostLogger.GetNatvisLogChannel().SetLogLevel(logLevel); + } + } + public static string FindSolutionRoot() { string path = null; diff --git a/src/DebugEngineHost/HostOutputWindow.cs b/src/DebugEngineHost/HostOutputWindow.cs index e1136f04b..1d2b63632 100644 --- a/src/DebugEngineHost/HostOutputWindow.cs +++ b/src/DebugEngineHost/HostOutputWindow.cs @@ -2,13 +2,139 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; - +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.DebugEngineHost { + internal static class VsOutputWindowWrapper + { + private static Lazy outputWindowLazy = new Lazy(() => + { + IVsOutputWindow outputWindow = null; + try + { + ThreadHelper.ThrowIfNotOnUIThread(); + outputWindow = Package.GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow; + } + catch (Exception) + { + Debug.Fail("Could not get OutputWindow service."); + } + return outputWindow; + }, LazyThreadSafetyMode.PublicationOnly); + + private static Lazy shellLazy = new Lazy(() => + { + IVsUIShell shell = null; + try + { + ThreadHelper.ThrowIfNotOnUIThread(); + shell = Package.GetGlobalService(typeof(SVsUIShell)) as IVsUIShell; + } + catch (Exception) + { + Debug.Fail("Could not get VSShell service."); + } + return shell; + // Use "PublicationOnly", because the implementation of GetService does its own locking + }, LazyThreadSafetyMode.PublicationOnly); + + private class PaneInfo + { + public PaneInfo(Guid paneId) + { + this.paneId = paneId; + this.Shown = false; + } + + internal Guid paneId; + internal bool Shown { get; set; } + } + + private const string DefaultOutputPane = "Debug"; + + private static Dictionary panes = new Dictionary() + { + // The 'Debug' pane exists by default + { DefaultOutputPane, new PaneInfo(VSConstants.GUID_OutWindowDebugPane) } + }; + + /// + /// Writes text directly to the VS Output window. + /// + public static void Write(string message, string pane = DefaultOutputPane) + { + ThreadHelper.JoinableTaskFactory.RunAsync(async delegate + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + try + { + // Get the Output window + IVsOutputWindow outputWindow = outputWindowLazy.Value; + if (outputWindow == null) + { + return; + } + + // Get the pane guid + PaneInfo paneInfo; + if (!panes.TryGetValue(pane, out paneInfo)) + { + // Pane didn't exist, create it + paneInfo = new PaneInfo(Guid.NewGuid()); + panes.Add(pane, paneInfo); + } + + // Get the pane + IVsOutputWindowPane outputPane; + if (outputWindow.GetPane(ref paneInfo.paneId, out outputPane) != VSConstants.S_OK) + { + // Failed to get the pane - might need to create it first + outputWindow.CreatePane(ref paneInfo.paneId, pane, fInitVisible: 1, fClearWithSolution: 1); + outputWindow.GetPane(ref paneInfo.paneId, out outputPane); + } + + // The first time we output text to a pane, ensure it's visible + if (!paneInfo.Shown) + { + paneInfo.Shown = true; + + // Switch to the pane of the Output window + outputPane.Activate(); + + // Show the output window + IVsUIShell shell = shellLazy.Value; + if (shell != null) + { + object inputVariant = null; + shell.PostExecCommand(VSConstants.GUID_VSStandardCommandSet97, (uint)VSConstants.VSStd97CmdID.OutputWindow, 0, ref inputVariant); + } + } + + // Output the text + outputPane.OutputString(message); + } + catch (Exception) + { + Debug.Fail("Failed to write to output pane."); + } + }).FileAndForget("VS/Diagnostics/Debugger/DebugEngineHost/VsOutputWindowWrapper/Write"); + } + + /// + /// Writes text directly to the VS Output window, appending a newline. + /// + public static void WriteLine(string message, string pane = DefaultOutputPane) + { + Write(string.Concat(message, Environment.NewLine), pane); + } + } + /// /// Provides direct access to the underlying output window without going through debug events /// @@ -19,32 +145,7 @@ private static class VsImpl { internal static void SetText(string outputMessage) { - int hr; - - var outputWindow = (IVsOutputWindow)Package.GetGlobalService(typeof(SVsOutputWindow)); - if (outputWindow == null) - return; - - IVsOutputWindowPane pane; - Guid guidDebugOutputPane = VSConstants.GUID_OutWindowDebugPane; - hr = outputWindow.GetPane(ref guidDebugOutputPane, out pane); - if (hr < 0) - return; - - pane.Clear(); - pane.Activate(); - - hr = pane.OutputString(outputMessage); - if (hr < 0) - return; - - var shell = (IVsUIShell)Package.GetGlobalService(typeof(SVsUIShell)); - if (shell == null) - return; - - Guid commandSet = VSConstants.GUID_VSStandardCommandSet97; - object inputVariant = null; - shell.PostExecCommand(commandSet, (uint)VSConstants.VSStd97CmdID.OutputWindow, 0, ref inputVariant); + VsOutputWindowWrapper.WriteLine(outputMessage); } } @@ -63,4 +164,4 @@ public static void WriteLaunchError(string outputMessage) } } } -} +} \ No newline at end of file diff --git a/src/DebugEngineHost/RegistryMonitor.cs b/src/DebugEngineHost/RegistryMonitor.cs new file mode 100644 index 000000000..6c6f1dcc0 --- /dev/null +++ b/src/DebugEngineHost/RegistryMonitor.cs @@ -0,0 +1,145 @@ +// // Copyright (c) Microsoft. All rights reserved. +// // Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Win32; +using Microsoft.Win32.SafeHandles; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.Remoting.Messaging; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using static System.Collections.Specialized.BitVector32; + +namespace Microsoft.DebugEngineHost +{ + internal class RegistryMonitor : IDisposable + { + #region Native Methods + + /// + /// Filter for notifications reported by . + /// + [Flags] + public enum RegChangeNotifyFilter + { + /// Notify the caller if a subkey is added or deleted. + REG_NOTIFY_CHANGE_NAME = 1, + /// Notify the caller of changes to the attributes of the key, + /// such as the security descriptor information. + REG_NOTIFY_CHANGE_ATTRIBUTES = 2, + /// Notify the caller of changes to a value of the key. This can + /// include adding or deleting a value, or changing an existing value. + REG_NOTIFY_CHANGE_LAST_SET = 4, + /// Notify the caller of changes to the security descriptor + /// of the key. + REG_NOTIFY_CHANGE_SECURITY = 8, + } + + [DllImport("advapi32.dll", SetLastError = true)] + private static extern int RegNotifyChangeKeyValue(SafeRegistryHandle hKey, bool bWatchSubtree, + RegChangeNotifyFilter dwNotifyFilter, IntPtr hEvent, + bool fAsynchronous); + + #endregion + + private HostConfigurationSection _section; + private readonly bool _watchSubtree; + + // Set when registry value is changed + private AutoResetEvent m_changeEvent; + + // Set when monitoring is stopped + private AutoResetEvent m_stoppedEvent; + + /// + /// Occurs when the specified registry key has changed. + /// + public event EventHandler RegChanged; + + private readonly ILogChannel _nativsLogger; + + public RegistryMonitor(HostConfigurationSection section, bool watchSubtree, ILogChannel nativsLogger) + { + _section = section; + _watchSubtree = watchSubtree; + _nativsLogger = nativsLogger; + } + + public void Start() + { + Thread registryMonitor = new Thread(Monitor); + registryMonitor.IsBackground = true; + registryMonitor.Name = "Microsoft.DebugEngineHost.RegistryMonitor"; + registryMonitor.Start(); + } + + public void Stop() + { + if (m_stoppedEvent != null) + { + m_stoppedEvent.Set(); + } + } + + // The handle is owned by change event instance which lives while we use the handle. + [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Runtime.InteropServices.SafeHandle.DangerousGetHandle")] + private void Monitor() + { + bool stopped = false; + try + { + m_stoppedEvent = new AutoResetEvent(false); + m_changeEvent = new AutoResetEvent(false); + + IntPtr handle = m_changeEvent.SafeWaitHandle.DangerousGetHandle(); + + int errorCode = RegNotifyChangeKeyValue(_section.Handle, _watchSubtree, RegChangeNotifyFilter.REG_NOTIFY_CHANGE_NAME | RegChangeNotifyFilter.REG_NOTIFY_CHANGE_LAST_SET, handle, true); + if (errorCode != 0) // 0 is ERROR_SUCCESS + { + _nativsLogger?.WriteLine(LogLevel.Error, Resource.Error_WatchRegistry, errorCode); + } + else + { + while (!stopped) + { + int waitResult = WaitHandle.WaitAny(new WaitHandle[] { m_stoppedEvent, m_changeEvent }); + + if (waitResult == 0) + { + stopped = true; + } + else + { + errorCode = RegNotifyChangeKeyValue(_section.Handle, _watchSubtree, RegChangeNotifyFilter.REG_NOTIFY_CHANGE_NAME | RegChangeNotifyFilter.REG_NOTIFY_CHANGE_LAST_SET, handle, true); + if (errorCode != 0) // 0 is ERROR_SUCCESS + { + _nativsLogger?.WriteLine(LogLevel.Error, Resource.Error_WatchRegistry, errorCode); + break; + } + RegChanged?.Invoke(this, null); + } + } + } + } + finally + { + _section.Dispose(); + m_stoppedEvent?.Dispose(); + m_changeEvent?.Dispose(); + + m_stoppedEvent = null; + m_changeEvent = null; + } + } + + public void Dispose() + { + m_stoppedEvent?.Dispose(); + } + } +} diff --git a/src/DebugEngineHost/Resource.Designer.cs b/src/DebugEngineHost/Resource.Designer.cs index a6ce3bf9e..4671d7f72 100644 --- a/src/DebugEngineHost/Resource.Designer.cs +++ b/src/DebugEngineHost/Resource.Designer.cs @@ -19,7 +19,7 @@ namespace Microsoft.DebugEngineHost { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resource { @@ -60,6 +60,15 @@ internal Resource() { } } + /// + /// Looks up a localized string similar to Failed to watch RegistryKey with error code '{0}'.. + /// + internal static string Error_WatchRegistry { + get { + return ResourceManager.GetString("Error_WatchRegistry", resourceCulture); + } + } + /// /// Looks up a localized string similar to Workspace index is incomplete; some .natvis files may not have been found.. /// diff --git a/src/DebugEngineHost/Resource.resx b/src/DebugEngineHost/Resource.resx index a9188fcdb..d4f310d72 100644 --- a/src/DebugEngineHost/Resource.resx +++ b/src/DebugEngineHost/Resource.resx @@ -117,6 +117,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Failed to watch RegistryKey with error code '{0}'. + {0} is an error code + Workspace index is incomplete; some .natvis files may not have been found. diff --git a/src/MICore/CommandFactories/gdb.cs b/src/MICore/CommandFactories/gdb.cs index 92c28938b..35d256e45 100644 --- a/src/MICore/CommandFactories/gdb.cs +++ b/src/MICore/CommandFactories/gdb.cs @@ -10,6 +10,7 @@ using System.Collections.ObjectModel; using System.Linq; using System.Globalization; +using Microsoft.DebugEngineHost; namespace MICore { @@ -298,7 +299,7 @@ public override async Task AutoComplete(string command, int threadId, var matchlist = res.Find("matches"); if (int.Parse(res.FindString("max_completions_reached"), CultureInfo.InvariantCulture) != 0) - _debugger.Logger.WriteLine("We reached max-completions!"); + _debugger.Logger.WriteLine(LogLevel.Verbose, "We reached max-completions!"); return matchlist?.AsStrings; } diff --git a/src/MICore/Debugger.cs b/src/MICore/Debugger.cs index de2796da2..cd26301f6 100755 --- a/src/MICore/Debugger.cs +++ b/src/MICore/Debugger.cs @@ -172,7 +172,7 @@ public Debugger(LaunchOptions launchOptions, Logger logger) protected void SetDebuggerPid(int debuggerPid) { // Used for testing - Logger.WriteLine(string.Concat("DebuggerPid=", debuggerPid)); + Logger.WriteLine(LogLevel.Verbose, string.Concat("DebuggerPid=", debuggerPid)); _localDebuggerPid = debuggerPid; } @@ -201,7 +201,7 @@ private void RetryBreak(object o) { if (_waitingToStop && _retryCount < BREAK_RETRY_MAX) { - Logger.WriteLine("Debugger failed to break. Trying again."); + Logger.WriteLine(LogLevel.Verbose, "Debugger failed to break. Trying again."); CmdBreak(BreakRequest.Internal); _retryCount++; } @@ -935,7 +935,7 @@ void ITransportCallback.OnStdOutLine(string line) void ITransportCallback.OnStdErrorLine(string line) { - Logger.WriteLine("STDERR: " + line); + Logger.WriteLine(LogLevel.Warning, "STDERR: " + line); if (_initialErrors != null) { @@ -1047,7 +1047,7 @@ void SendUnsupportedWindowsGdbEvent(string version) void ITransportCallback.AppendToInitializationLog(string line) { - Logger.WriteLine(line); + Logger.WriteLine(LogLevel.Verbose, line); if (_initializationLog != null) { @@ -1240,7 +1240,7 @@ public void ProcessStdOutLine(string line) if (waitingOperation != null) { Results results = _miResults.ParseCommandOutput(noprefix); - Logger.WriteLine(id.ToString(CultureInfo.InvariantCulture) + ": elapsed time " + ((int)(DateTime.Now - waitingOperation.StartTime).TotalMilliseconds).ToString(CultureInfo.InvariantCulture)); + Logger.WriteLine(LogLevel.Verbose, id.ToString(CultureInfo.InvariantCulture) + ": elapsed time " + ((int)(DateTime.Now - waitingOperation.StartTime).TotalMilliseconds).ToString(CultureInfo.InvariantCulture)); waitingOperation.OnComplete(results, this.MICommandFactory); return; } diff --git a/src/MICore/ExceptionHelper.cs b/src/MICore/ExceptionHelper.cs index 5563ff4cb..7295142b8 100644 --- a/src/MICore/ExceptionHelper.cs +++ b/src/MICore/ExceptionHelper.cs @@ -32,8 +32,8 @@ public static bool BeforeCatch(Exception currentException, Logger logger, bool r { HostTelemetry.ReportCurrentException(currentException, "Microsoft.MIDebugEngine"); - logger?.WriteLine("EXCEPTION: " + currentException.GetType()); - logger?.WriteTextBlock("EXCEPTION: ", currentException.StackTrace); + logger?.WriteLine(LogLevel.Error, "EXCEPTION: ", currentException.GetType()); + logger?.WriteTextBlock(LogLevel.Error, "EXCEPTION: ", currentException.StackTrace); } catch { diff --git a/src/MICore/LaunchOptions.cs b/src/MICore/LaunchOptions.cs index 835005b83..6ba4b10c1 100644 --- a/src/MICore/LaunchOptions.cs +++ b/src/MICore/LaunchOptions.cs @@ -1260,7 +1260,7 @@ public static LaunchOptions GetInstance(HostConfigurationStore configStore, stri if (string.IsNullOrEmpty(options)) throw new InvalidLaunchOptionsException(MICoreResources.Error_StringIsNullOrEmpty); - logger?.WriteTextBlock("LaunchOptions", options); + logger?.WriteTextBlock(LogLevel.Verbose, "LaunchOptions", options); LaunchOptions launchOptions = null; Guid clsidLauncher = Guid.Empty; @@ -1500,7 +1500,7 @@ internal static SupplementalLaunchOptions GetOptionsFromFile(Logger logger) { try { - logger?.WriteTextBlock("SupplementalOptions", suppOptions); + logger?.WriteTextBlock(LogLevel.Verbose, "SupplementalOptions", suppOptions); XmlReader xmlRrd = OpenXml(suppOptions); XmlSerializer serializer = GetXmlSerializer(typeof(Xml.LaunchOptions.SupplementalLaunchOptions)); return (Xml.LaunchOptions.SupplementalLaunchOptions)Deserialize(serializer, xmlRrd); diff --git a/src/MICore/Logger.cs b/src/MICore/Logger.cs index 05e8e9ca9..86e2db72f 100644 --- a/src/MICore/Logger.cs +++ b/src/MICore/Logger.cs @@ -15,41 +15,45 @@ namespace MICore { - public interface ILogger - { - void WriteLine(string line); - - void WriteLine(string format, params object[] args); - } - /// - /// Class which implements logging. The logging is control by a registry key. If enabled, logging goes to %TMP%\Microsoft.MIDebug.log + /// Class which implements logging. /// - public class Logger : ILogger + public class Logger { private static bool s_isInitialized; private static bool s_isEnabled; private static DateTime s_initTime; - // NOTE: We never clean this up - private static HostLogger s_logger; + /// + /// Optional logger to get engine diagnostics logs + /// + private ILogChannel EngineLogger => HostLogger.GetEngineLogChannel(); + /// + /// Optional logger to get natvis diagnostics logs + /// + public ILogChannel NatvisLogger => HostLogger.GetNatvisLogChannel(); private static int s_count; - private int _id; + private readonly int _id; + + #region Command Window + public class LogInfo { public string logFile; - public HostLogger.OutputCallback logToOutput; + public Action logToOutput; public bool enabled; }; - private static LogInfo s_cmdLogInfo = new LogInfo(); + + private readonly static LogInfo s_cmdLogInfo = new LogInfo(); public static LogInfo CmdLogInfo { get { return s_cmdLogInfo; } } + #endregion private Logger() { _id = Interlocked.Increment(ref s_count); } - public static Logger EnsureInitialized(HostConfigurationStore configStore) + public static Logger EnsureInitialized() { Logger res = new Logger(); if (!s_isInitialized) @@ -57,8 +61,8 @@ public static Logger EnsureInitialized(HostConfigurationStore configStore) s_isInitialized = true; s_initTime = DateTime.Now; - LoadMIDebugLogger(configStore); - res.WriteLine("Initialized log at: " + s_initTime.ToString(CultureInfo.InvariantCulture)); + LoadMIDebugLogger(); + res.WriteLine(LogLevel.Verbose, "Initialized log at: " + s_initTime.ToString(CultureInfo.InvariantCulture)); } #if DEBUG @@ -70,75 +74,70 @@ public static Logger EnsureInitialized(HostConfigurationStore configStore) return res; } - public static void LoadMIDebugLogger(HostConfigurationStore configStore) + public static void EnableNatvisDiagnostics(LogLevel level) { - if (s_logger == null) - { - if (CmdLogInfo.enabled) - { // command configured log file - s_logger = HostLogger.GetLoggerFromCmd(CmdLogInfo.logFile, CmdLogInfo.logToOutput); - } - else - { // use default logging - s_logger = configStore.GetLogger("EnableMIDebugLogger", "Microsoft.MIDebug.log"); - } - if (s_logger != null) - { - s_isEnabled = true; - } + HostLogger.EnableNatvisDiagnostics(HostOutputWindow.WriteLaunchError, level); + } + + public static void LoadMIDebugLogger() + { + if (CmdLogInfo.enabled) + { // command configured log file + HostLogger.Reset(); + HostLogger.SetEngineLogFile(CmdLogInfo.logFile); + HostLogger.EnableHostLogging(CmdLogInfo.logToOutput); } + + s_isEnabled = true; } public static void Reset() { - HostLogger logger; if (CmdLogInfo.enabled) { - logger = HostLogger.GetLoggerFromCmd(CmdLogInfo.logFile, CmdLogInfo.logToOutput); - logger = Interlocked.Exchange(ref s_logger, logger); - logger?.Close(); - if (s_logger != null) - { - s_isEnabled = true; - } + HostLogger.Reset(); + s_isEnabled = false; } } /// /// If logging is enabled, writes a line of text to the log /// + /// [Required] The level of the log. /// [Required] line to write - public void WriteLine(string line) + public void WriteLine(LogLevel level, string line) { if (s_isEnabled) { - WriteLineImpl(line); + WriteLineImpl(level, line); } } /// /// If logging is enabled, writes a line of text to the log /// + /// [Required] The level of the log. /// [Required] format string /// arguments to use in the format string - public void WriteLine(string format, params object[] args) + public void WriteLine(LogLevel level, string format, params object[] args) { if (s_isEnabled) { - WriteLineImpl(format, args); + WriteLineImpl(level, format, args); } } /// /// If logging is enabled, writes a block of text which may contain newlines to the log /// + /// [Required] The level of the log. /// [Optional] Prefix to put on the front of each line /// Block of text to write - public void WriteTextBlock(string prefix, string textBlock) + public void WriteTextBlock(LogLevel level, string prefix, string textBlock) { if (s_isEnabled) { - WriteTextBlockImpl(prefix, textBlock); + WriteTextBlockImpl(level, prefix, textBlock); } } @@ -159,10 +158,10 @@ public static bool IsEnabled } [MethodImpl(MethodImplOptions.NoInlining)] // Disable inlining since logging is off by default, and we want to allow the public method to be inlined - private void WriteLineImpl(string line) + private void WriteLineImpl(LogLevel level, string line) { string fullLine = String.Format(CultureInfo.CurrentCulture, "{2}: ({0}) {1}", (int)(DateTime.Now - s_initTime).TotalMilliseconds, line, _id); - s_logger?.WriteLine(fullLine); + HostLogger.GetEngineLogChannel()?.WriteLine(level, fullLine); #if DEBUG Debug.WriteLine("MS_MIDebug: " + fullLine); #endif @@ -171,17 +170,17 @@ private void WriteLineImpl(string line) [MethodImpl(MethodImplOptions.NoInlining)] // Disable inlining since logging is off by default, and we want to allow the public method to be inlined private static void FlushImpl() { - s_logger?.Flush(); + HostLogger.GetEngineLogChannel()?.Flush(); } [MethodImpl(MethodImplOptions.NoInlining)] // Disable inlining since logging is off by default, and we want to allow the public method to be inlined - private void WriteLineImpl(string format, object[] args) + private void WriteLineImpl(LogLevel level, string format, object[] args) { - WriteLineImpl(string.Format(CultureInfo.CurrentCulture, format, args)); + WriteLineImpl(level, string.Format(CultureInfo.CurrentCulture, format, args)); } [MethodImpl(MethodImplOptions.NoInlining)] // Disable inlining since logging is off by default, and we want to allow the public method to be inlined - private void WriteTextBlockImpl(string prefix, string textBlock) + private void WriteTextBlockImpl(LogLevel level, string prefix, string textBlock) { using (var reader = new StringReader(textBlock)) { @@ -192,9 +191,9 @@ private void WriteTextBlockImpl(string prefix, string textBlock) break; if (!string.IsNullOrEmpty(prefix)) - WriteLineImpl(prefix + line); + WriteLineImpl(level, prefix + line); else - WriteLineImpl(line); + WriteLineImpl(level, line); } } } diff --git a/src/MICore/MICore.csproj b/src/MICore/MICore.csproj index 149488eba..abdc47283 100755 --- a/src/MICore/MICore.csproj +++ b/src/MICore/MICore.csproj @@ -65,7 +65,6 @@ - PreserveNewest vscode\osxlaunchhelper.scpt diff --git a/src/MICore/MIResults.cs b/src/MICore/MIResults.cs index 0e3a45704..0b8e4b9a2 100644 --- a/src/MICore/MIResults.cs +++ b/src/MICore/MIResults.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Collections; using System.Globalization; +using Microsoft.DebugEngineHost; namespace MICore { @@ -1199,7 +1200,7 @@ private void ParseError(string message, Span input) string result = CreateErrorMessageFromSpan(input); Debug.Fail(message + ": " + result); - Logger?.WriteLine(String.Format(CultureInfo.CurrentCulture, "MI parsing error: {0}: \"{1}\"", message, result)); + Logger?.WriteLine(LogLevel.Error, String.Format(CultureInfo.CurrentCulture, "MI parsing error: {0}: \"{1}\"", message, result)); } diff --git a/src/MICore/SetMIDebugLogging.cmd b/src/MICore/SetMIDebugLogging.cmd deleted file mode 100644 index 23396aa7c..000000000 --- a/src/MICore/SetMIDebugLogging.cmd +++ /dev/null @@ -1,130 +0,0 @@ -@echo off -setlocal - -if /i "%~1"=="" goto Help -if /i "%~1"=="-?" goto Help -if /i "%~1"=="/?" goto Help - -set LoggingValue= -set Exp=0 -set ServerLogging= -set VSRootDir= -set MIEngineRelativeDir=Common7\IDE\CommonExtensions\Microsoft\MDD\Debugger -set MIEngineRelativePath=%MIEngineRelativeDir%\Microsoft.MIDebugEngine.dll - -:ArgLoop -if /i "%~1"=="on" set LoggingValue=1& goto ArgLoopCondition -if /i "%~1"=="off" set LoggingValue=0& goto ArgLoopCondition -if /i "%~1"=="/VSRootDir" goto SetVSRoot -if /i "%~1"=="-VSRootDir" goto SetVSRoot -if /i "%~1"=="-serverlogging" goto SetServerLogging -if /i "%~1"=="/serverlogging" goto SetServerLogging -echo ERROR: Unknown argument '%~1'& exit /b -1 - -:SetVSRoot -shift -if "%~1"=="" echo ERROR: Expected version number. -set VSRootDir=%~1 -if not exist "%VSRootDir%" echo ERROR: '/VSRootDir' value '%VSRootDir%' does not exist & exit /b -1 -if not exist "%VSRootDir%\%MIEngineRelativePath%" echo ERROR: '/VSRootDir' value '%VSRootDir%' does not contain MIEngine (%VSRootDir%\%MIEngineRelativePath%) & exit /b -1 -goto ArgLoopCondition - -:SetServerLogging -REM Documentation on GDBServer command line arguments: http://www.sourceware.org/gdb/onlinedocs/gdb/Server.html -set ServerLogging=--debug -if /i "%~2"=="full" shift & set ServerLogging=--debug --remote-debug -goto ArgLoopCondition - -:ArgLoopCondition -shift -if NOT "%~1"=="" goto :ArgLoop - -if "%LoggingValue%"=="" echo ERROR: 'on' or 'off' must be specified.& exit /b -1 -if /i NOT "%LoggingValue%"=="1" if NOT "%ServerLogging%"=="" echo ERROR: '/serverlogging' can only be used with 'on'& exit /b -1 - -set SetLoggingError= -if NOT "%VSRootDir%"=="" call :SetLogging "%VSRootDir%" & goto Done - -REM If '/VSRootDir' is NOT specified, try ALL the default locations -set ProgRoot=%ProgramFiles(x86)% -if "%ProgRoot%"=="" set ProgRoot=%ProgramFiles% -set VSVersionFound= -set MIEngineFound= -call :TryVSPath "%ProgRoot%\Microsoft Visual Studio 14.0" -call :TryVSPaths "%ProgRoot%\Microsoft Visual Studio\2017\*" -if "%VSVersionFound%"=="" echo ERROR: Visual Studio 2015+ is not installed, or not installed to the default location. Use '/VSRootDir' to specify the directory. & exit /b -1 -if "%MIEngineFound%"=="" echo ERROR: The found version(s) of Visual Studio do not have the MIEngine installed. & exit /b -1 -goto Done - -:Done - echo. - if NOT "%SetLoggingError%"=="" exit /b -1 - echo SetMIDebugLogging.cmd succeeded. Restart Visual Studio to take effect. - if "%LoggingValue%"=="1" echo Logging will be saved to %TMP%\Microsoft.MIDebug.log. - exit /b 0 - -:TryVSPaths - for /d %%d in (%1) do call :TryVSPath "%%d" - goto :EOF - -:TryVSPath - REM Arg1: path to VS Root - - if NOT "%SetLoggingError%"=="" goto :EOF - if not exist "%~1" goto :EOF - set VSVersionFound=1 - if not exist "%~1\%MIEngineRelativePath%" goto :EOF - set MIEngineFound=1 - goto SetLogging - -:SetLogging - REM Arg1: path to VS Root - set PkgDefFile=%~1\%MIEngineRelativeDir%\logging.pkgdef - - if NOT exist "%PkgDefFile%" goto SetLogging_NoPkgDef - del "%PkgDefFile%" - if NOT exist "%PkgDefFile%" goto SetLogging_NoPkgDef - echo ERROR: Failed to remove "%PkgDefFile%". Ensure this script is run as an administrator. - set SetLoggingError=1 - goto :EOF - :SetLogging_NoPkgDef - - if "%LoggingValue%"=="0" goto UpdateConfiguration - - :EnableLogging - echo [$RootKey$\Debugger]> "%PkgDefFile%" - if exist "%PkgDefFile%" goto EnableLogging_PkgDefCreated - echo ERROR: Failed to create "%PkgDefFile%". Ensure this script is run as an administrator. - set SetLoggingError=1 - goto :EOF - :EnableLogging_PkgDefCreated - - echo "EnableMIDebugLogger"=dword:00000001>> "%PkgDefFile%" - if NOT "%ServerLogging%"=="" echo "GDBServerLoggingArguments"="%ServerLogging%">> "%PkgDefFile%" - - :UpdateConfiguration - echo Setting logging for %1 - call "%~1\Common7\IDE\devenv.com" /updateconfiguration - if "%ERRORLEVEL%"=="0" goto :EOF - echo ERROR: '"%~1\Common7\IDE\devenv.com" /updateconfiguration' failed with error %ERRORLEVEL%. - set SetLoggingError=1 - goto :EOF - -:Help -echo SetMIDebugLogging.cmd ^ [/serverlogging [full]] [/VSRootDir ^] -echo. -echo SetMIDebugLogging.cmd is used to enable/disable logging for the Microsoft -echo MI debug engine. -echo. -echo Logging will be saved to %TMP%\Microsoft.MIDebug.log. -echo. -echo /serverlogging [full] Enables logging from gdbserver. This option is only -echo supported when enabling logging ('on'). 'full' logging will -echo turn on packet logging in addition to normal logging. -echo. -echo /VSRootDir ^ sets the path to the root of Visual Studio -echo (ex: C:\Program Files (x86)\Microsoft Visual Studio 14.0). If not -echo specified, the default install directories for all versions of VS -echo 2015+ will be used. -echo. -:eof diff --git a/src/MICore/Transports/PipeTransport.cs b/src/MICore/Transports/PipeTransport.cs index b71e53b26..e19b6b137 100644 --- a/src/MICore/Transports/PipeTransport.cs +++ b/src/MICore/Transports/PipeTransport.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using System.Globalization; using System.Runtime.InteropServices; +using Microsoft.DebugEngineHost; namespace MICore { @@ -369,7 +370,7 @@ public override int ExecuteSyncCommand(string commandDescription, string command Process proc = new Process(); proc.StartInfo.FileName = _pipePath; proc.StartInfo.Arguments = PipeLaunchOptions.ReplaceDebuggerCommandToken(_cmdArgs, commandText, true); - Logger.WriteLine("Running process {0} {1}", proc.StartInfo.FileName, proc.StartInfo.Arguments); + Logger.WriteLine(LogLevel.Verbose, "Running process {0} {1}", proc.StartInfo.FileName, proc.StartInfo.Arguments); proc.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(_pipePath); proc.EnableRaisingEvents = false; proc.StartInfo.RedirectStandardInput = false; diff --git a/src/MICore/Transports/RunInTerminalTransport.cs b/src/MICore/Transports/RunInTerminalTransport.cs index f4fce7818..4418f105d 100644 --- a/src/MICore/Transports/RunInTerminalTransport.cs +++ b/src/MICore/Transports/RunInTerminalTransport.cs @@ -132,7 +132,7 @@ public override async void Init(ITransportCallback transportCallback, LaunchOpti debuggerCmd, localOptions.GetMiDebuggerArgs()); - logger?.WriteTextBlock("DbgCmd:", launchDebuggerCommand); + logger?.WriteTextBlock(LogLevel.Verbose, "DbgCmd:", launchDebuggerCommand); using (FileStream dbgCmdStream = new FileStream(dbgCmdScript, FileMode.CreateNew)) using (StreamWriter dbgCmdWriter = new StreamWriter(dbgCmdStream, encNoBom) { AutoFlush = true }) @@ -175,7 +175,7 @@ public override async void Init(ITransportCallback transportCallback, LaunchOpti throw new InvalidOperationException(error); }, logger); - logger?.WriteLine("Wait for connection completion."); + logger?.WriteLine(LogLevel.Verbose, "Wait for connection completion."); if (_waitForConnection != null) { @@ -216,7 +216,7 @@ private void LogDebuggerErrors() string line = this.GetLineFromStream(_errorStream, _streamReadPidCancellationTokenSource.Token); if (line == null) break; - Logger?.WriteTextBlock("dbgerr:", line); + Logger?.WriteTextBlock(LogLevel.Error, "dbgerr:", line); } } } @@ -244,7 +244,7 @@ private void LaunchSuccess(int? pid) { shellPid = int.Parse(readShellPidTask.Result, CultureInfo.InvariantCulture); // Used for testing - Logger?.WriteLine(string.Concat("ShellPid=", shellPid)); + Logger?.WriteLine(LogLevel.Verbose, string.Concat("ShellPid=", shellPid)); } else { @@ -300,7 +300,7 @@ private void ShellExited(object sender, EventArgs e) ((Process)sender).Exited -= ShellExited; } - Logger?.WriteLine("Shell exited, stop debugging"); + Logger?.WriteLine(LogLevel.Verbose, "Shell exited, stop debugging"); this.Callback.OnDebuggerProcessExit(null); Close(); diff --git a/src/MICore/Transports/StreamTransport.cs b/src/MICore/Transports/StreamTransport.cs index 53618e5ab..9de2f3c32 100644 --- a/src/MICore/Transports/StreamTransport.cs +++ b/src/MICore/Transports/StreamTransport.cs @@ -71,7 +71,7 @@ private void TransportLoop() break; line = line.TrimEnd(); - Logger?.WriteLine("->" + line); + Logger?.WriteLine(LogLevel.Verbose, "->" + line); Logger?.Flush(); try @@ -144,7 +144,7 @@ protected void Echo(string cmd) { if (!String.IsNullOrWhiteSpace(cmd)) { - Logger?.WriteLine("<-" + cmd); + Logger?.WriteLine(LogLevel.Verbose, "<-" + cmd); Logger?.Flush(); } diff --git a/src/MICore/Transports/UnixShellPortTransport.cs b/src/MICore/Transports/UnixShellPortTransport.cs index c1801a13c..95cd72933 100644 --- a/src/MICore/Transports/UnixShellPortTransport.cs +++ b/src/MICore/Transports/UnixShellPortTransport.cs @@ -37,7 +37,7 @@ public KillCommandCallback(Logger logger) public void OnOutputLine(string line) { - _logger?.WriteLine("[kill] ->" + line); + _logger?.WriteLine(LogLevel.Verbose, "[kill] ->" + line); } public void OnExit(string exitCode) @@ -74,7 +74,7 @@ public void Close() public void Send(string cmd) { - _logger?.WriteLine("<-" + cmd); + _logger?.WriteLine(LogLevel.Verbose, "<-" + cmd); _logger?.Flush(); _asyncCommand.WriteLine(cmd); } @@ -104,7 +104,7 @@ void IDebugUnixShellCommandCallback.OnOutputLine(string line) _callback.OnStdOutLine(line); } - _logger?.WriteLine("->" + line); + _logger?.WriteLine(LogLevel.Verbose, "->" + line); _logger?.Flush(); } diff --git a/src/MICore/UnixUtilities.cs b/src/MICore/UnixUtilities.cs index 6de8664f9..0496723aa 100644 --- a/src/MICore/UnixUtilities.cs +++ b/src/MICore/UnixUtilities.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using Microsoft.DebugEngineHost; using System; using System.Collections.Generic; using System.Diagnostics; @@ -116,7 +117,7 @@ internal static string MakeFifo(string identifier = null, Logger logger = null) if (result != 0) { // Failed to create the fifo. Bail. - logger?.WriteLine("Failed to create fifo"); + logger?.WriteLine(LogLevel.Error, "Failed to create fifo"); throw new ArgumentException("MakeFifo failed to create fifo at path {0}", path); } @@ -203,7 +204,7 @@ internal static void OutputNonEmptyString(string str, string prefix, Logger logg { if (!String.IsNullOrWhiteSpace(str) && logger != null) { - logger.WriteLine(prefix + str); + logger.WriteLine(LogLevel.Verbose, prefix + str); } } diff --git a/src/MIDebugEngine/AD7.Impl/AD7Engine.cs b/src/MIDebugEngine/AD7.Impl/AD7Engine.cs index 0535572a6..ad050bd4f 100755 --- a/src/MIDebugEngine/AD7.Impl/AD7Engine.cs +++ b/src/MIDebugEngine/AD7.Impl/AD7Engine.cs @@ -189,7 +189,7 @@ public int Attach(IDebugProgram2[] portProgramArray, IDebugProgramNode2[] progra { Debug.Assert(_ad7ProgramId == Guid.Empty); - Logger.LoadMIDebugLogger(_configStore); + Logger.LoadMIDebugLogger(); if (celtPrograms != 1) { @@ -498,7 +498,7 @@ public int SetMetric(string pszMetric, object varValue) public int SetRegistryRoot(string registryRoot) { _configStore = new HostConfigurationStore(registryRoot); - Logger = Logger.EnsureInitialized(_configStore); + Logger = Logger.EnsureInitialized(); return Constants.S_OK; } @@ -539,7 +539,7 @@ int IDebugEngineLaunch2.LaunchSuspended(string pszServer, IDebugPort2 port, stri Debug.Assert(_ad7ProgramId == Guid.Empty); // Check if the logger was enabled late. - Logger.LoadMIDebugLogger(_configStore); + Logger.LoadMIDebugLogger(); process = null; diff --git a/src/MIDebugEngine/Engine.Impl/DebugUnixChildProcess.cs b/src/MIDebugEngine/Engine.Impl/DebugUnixChildProcess.cs index b883c5d98..6c50d58f6 100644 --- a/src/MIDebugEngine/Engine.Impl/DebugUnixChildProcess.cs +++ b/src/MIDebugEngine/Engine.Impl/DebugUnixChildProcess.cs @@ -232,7 +232,7 @@ public async Task Stopped(Results results, int tid) else { // sometimes gdb misses the breakpoint at exec and execution will proceed to a breakpoint in the child - _process.Logger.WriteLine("Missed catching the exec after vfork. Spawning the child's debugger."); + _process.Logger.WriteLine(LogLevel.Verbose, "Missed catching the exec after vfork. Spawning the child's debugger."); s.State = State.AtExec; goto missedExec; } diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs index 5debd856c..24141685c 100755 --- a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs +++ b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs @@ -65,7 +65,7 @@ public DebuggedProcess(bool bLaunched, LaunchOptions launchOptions, ISampleEngin _deleteEntryPointBreakpoint = false; MICommandFactory = MICommandFactory.GetInstance(launchOptions.DebuggerMIMode, this); _waitDialog = (MICommandFactory.SupportsStopOnDynamicLibLoad() && launchOptions.WaitDynamicLibLoad) ? new HostWaitDialog(ResourceStrings.LoadingSymbolMessage, ResourceStrings.LoadingSymbolCaption) : null; - Natvis = new Natvis.Natvis(this, launchOptions.ShowDisplayString); + Natvis = new Natvis.Natvis(this, launchOptions.ShowDisplayString, configStore); // we do NOT have real Win32 process IDs, so we use a guid AD_PROCESS_ID pid = new AD_PROCESS_ID(); @@ -1033,6 +1033,8 @@ private void Dispose() _waitDialog.Dispose(); } + Natvis?.Dispose(); + Logger.Flush(); } diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs b/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs index 4d9362de8..ff739b61e 100644 --- a/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs +++ b/src/MIDebugEngine/Engine.Impl/DebuggedThread.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using MICore; +using Microsoft.DebugEngineHost; using System; using System.Collections.Generic; using System.Diagnostics; @@ -136,7 +137,7 @@ internal async Task> StackFrames(DebuggedThread thread) } catch (UnexpectedMIResultException) { - _debugger.Logger.WriteLine("Stack walk failed on thread: " + thread.TargetId); + _debugger.Logger.WriteLine(LogLevel.Error, "Stack walk failed on thread: " + thread.TargetId); _stateChange = true; // thread may have been deleted. Force a resync } lock (_threadList) @@ -291,7 +292,7 @@ private async Task> WalkStack(DebuggedThread thread) TupleValue[] frameinfo = await _debugger.MICommandFactory.StackListFrames(thread.Id, 0, 1000); if (frameinfo == null) { - _debugger.Logger.WriteLine("Failed to get frame info"); + _debugger.Logger.WriteLine(LogLevel.Error, "Failed to get frame info"); } else { diff --git a/src/MIDebugEngine/Engine.Impl/EngineCallback.cs b/src/MIDebugEngine/Engine.Impl/EngineCallback.cs index d87a541e8..e009a3fa6 100644 --- a/src/MIDebugEngine/Engine.Impl/EngineCallback.cs +++ b/src/MIDebugEngine/Engine.Impl/EngineCallback.cs @@ -31,7 +31,7 @@ public void Send(IDebugEvent2 eventObject, string iidEvent, IDebugProgram2 progr Guid riidEvent = new Guid(iidEvent); if (!(eventObject is AD7OutputDebugStringEvent)) { - _engine.Logger.WriteLine("Send Event {0}", eventObject.GetType().Name); + _engine.Logger.WriteLine(LogLevel.Verbose, "Send Event {0}", eventObject.GetType().Name); } EngineUtils.RequireOk(eventObject.GetAttributes(out attributes)); EngineUtils.RequireOk(_eventCallback.Event(_engine, null, program, thread, eventObject, ref riidEvent, attributes)); diff --git a/src/MIDebugEngine/Natvis.Impl/Natvis.cs b/src/MIDebugEngine/Natvis.Impl/Natvis.cs index 9927883c9..81b52ee84 100755 --- a/src/MIDebugEngine/Natvis.Impl/Natvis.cs +++ b/src/MIDebugEngine/Natvis.Impl/Natvis.cs @@ -157,7 +157,7 @@ public VisualizerId(string name,int id) } }; - public class Natvis + public class Natvis : IDisposable { private class AliasInfo { @@ -227,6 +227,7 @@ public VisualizerInfo(VisualizerType viz, TypeName name) private static Regex s_expression = new Regex(@"^\{[^\}]*\}"); private List _typeVisualizers; private DebuggedProcess _process; + private HostConfigurationStore _configStore; private Dictionary _vizCache; private uint _depth; public HostWaitDialog WaitDialog { get; private set; } @@ -237,6 +238,8 @@ public VisualizerInfo(VisualizerType viz, TypeName name) private const int MAX_FORMAT_DEPTH = 10; private const int MAX_ALIAS_CHAIN = 10; + private IDisposable _natvisSettingWatcher; + public enum DisplayStringsState { On, @@ -245,7 +248,7 @@ public enum DisplayStringsState } public DisplayStringsState ShowDisplayStrings { get; set; } - internal Natvis(DebuggedProcess process, bool showDisplayString) + internal Natvis(DebuggedProcess process, bool showDisplayString, HostConfigurationStore configStore) { _typeVisualizers = new List(); _process = process; @@ -254,12 +257,14 @@ internal Natvis(DebuggedProcess process, bool showDisplayString) ShowDisplayStrings = showDisplayString ? DisplayStringsState.On : DisplayStringsState.ForVisualizedItems; // don't compute display strings unless explicitly requested _depth = 0; Cache = new VisualizationCache(); + _configStore = configStore; } public void Initialize(string fileName) { try { + _natvisSettingWatcher = HostNatvisProject.WatchNatvisOptionSetting(_configStore, _process.Logger.NatvisLogger); HostNatvisProject.FindNatvis((s) => LoadFile(s)); } catch (FileNotFoundException) @@ -325,7 +330,7 @@ private bool LoadFile(string path) XmlSerializer serializer = new XmlSerializer(typeof(AutoVisualizer)); if (!File.Exists(path)) { - _process.WriteOutput(String.Format(CultureInfo.CurrentCulture, ResourceStrings.FileNotFound, path)); + _process.Logger.NatvisLogger?.WriteLine(LogLevel.Error, ResourceStrings.FileNotFound, path); return false; } XmlReaderSettings settings = new XmlReaderSettings(); @@ -355,7 +360,7 @@ private bool LoadFile(string path) if (o is VisualizerType) { VisualizerType v = (VisualizerType)o; - TypeName t = TypeName.Parse(v.Name, _process.Logger); + TypeName t = TypeName.Parse(v.Name, _process.Logger.NatvisLogger); if (t != null) { lock (_typeVisualizers) @@ -368,7 +373,7 @@ private bool LoadFile(string path) { foreach (var a in v.AlternativeType) { - t = TypeName.Parse(a.Name, _process.Logger); + t = TypeName.Parse(a.Name, _process.Logger.NatvisLogger); if (t != null) { lock (_typeVisualizers) @@ -382,7 +387,7 @@ private bool LoadFile(string path) else if (o is AliasType) { AliasType a = (AliasType)o; - TypeName t = TypeName.Parse(a.Name, _process.Logger); + TypeName t = TypeName.Parse(a.Name, _process.Logger.NatvisLogger); if (t != null) { lock (_typeVisualizers) @@ -406,7 +411,7 @@ private bool LoadFile(string path) catch (Exception exception) { // don't allow natvis failures to stop debugging - _process.WriteOutput(String.Format(CultureInfo.CurrentCulture, ResourceStrings.ErrorReadingFile, exception.Message, path)); + _process.Logger.NatvisLogger?.WriteLine(LogLevel.Error, ResourceStrings.ErrorReadingFile, exception.Message, path); return false; } } @@ -451,7 +456,7 @@ private bool LoadFile(string path) { // don't allow natvis to mess up debugging // eat any exceptions and return the variable's value - _process.Logger.WriteLine("natvis FormatDisplayString: " + e.Message); + _process.Logger.NatvisLogger?.WriteLine(LogLevel.Error, "FormatDisplayString: " + e.Message); } finally { @@ -502,7 +507,7 @@ internal IVariableInformation[] Expand(IVariableInformation variable) } catch (Exception e) { - _process.Logger.WriteLine("natvis Expand: " + e.Message); // TODO: add telemetry + _process.Logger.WriteLine(LogLevel.Error, "natvis Expand: " + e.Message); // TODO: add telemetry return variable.Children; } } @@ -1135,7 +1140,7 @@ private VisualizerInfo Scan(TypeName name, IVariableInformation variable) } string newName = ReplaceNamesInExpression(alias.Alias.Value, null, scopedNames); - name = TypeName.Parse(newName, _process.Logger); + name = TypeName.Parse(newName, _process.Logger.NatvisLogger); aliasChain++; if (aliasChain > MAX_ALIAS_CHAIN) { @@ -1157,7 +1162,7 @@ private VisualizerInfo FindType(IVariableInformation variable) { return _vizCache[variable.TypeName]; } - TypeName parsedName = TypeName.Parse(variable.TypeName, _process.Logger); + TypeName parsedName = TypeName.Parse(variable.TypeName, _process.Logger.NatvisLogger); IVariableInformation var = variable; while (parsedName != null) { @@ -1176,7 +1181,7 @@ private VisualizerInfo FindType(IVariableInformation variable) { break; } - parsedName = TypeName.Parse(var.TypeName, _process.Logger); + parsedName = TypeName.Parse(var.TypeName, _process.Logger.NatvisLogger); } return null; } @@ -1365,5 +1370,12 @@ private string GetDisplayNameFromArrayIndex(uint arrayIndex, int rank, uint[] di return displayName.ToString(); } + public void Dispose() + { + GC.SuppressFinalize(this); + + _natvisSettingWatcher?.Dispose(); + _natvisSettingWatcher = null; + } } } diff --git a/src/MIDebugEngine/Natvis.Impl/NatvisNames.cs b/src/MIDebugEngine/Natvis.Impl/NatvisNames.cs index 7c145a6d8..85972bdd8 100644 --- a/src/MIDebugEngine/Natvis.Impl/NatvisNames.cs +++ b/src/MIDebugEngine/Natvis.Impl/NatvisNames.cs @@ -9,6 +9,7 @@ using System.Text.RegularExpressions; using MICore; using System.Globalization; +using Microsoft.DebugEngineHost; namespace Microsoft.MIDebugEngine.Natvis { @@ -138,7 +139,7 @@ public bool Match(TypeName t) /// /// /// - public static TypeName Parse(string fullyQualifiedName, ILogger logger) + public static TypeName Parse(string fullyQualifiedName, ILogChannel logger) { if (String.IsNullOrEmpty(fullyQualifiedName)) return null; @@ -146,7 +147,7 @@ public static TypeName Parse(string fullyQualifiedName, ILogger logger) TypeName t = MatchTypeName(fullyQualifiedName.Trim(), out rest); if (!String.IsNullOrWhiteSpace(rest)) { - logger.WriteLine("Natvis failed to parse typename: {0}", fullyQualifiedName); + logger.WriteLine(LogLevel.Error, "Natvis failed to parse typename: {0}", fullyQualifiedName); return null; } return t; diff --git a/src/MIDebugEngineUnitTests/NatvisNamesTest.cs b/src/MIDebugEngineUnitTests/NatvisNamesTest.cs index a59dfc1bd..3de8665d6 100644 --- a/src/MIDebugEngineUnitTests/NatvisNamesTest.cs +++ b/src/MIDebugEngineUnitTests/NatvisNamesTest.cs @@ -4,10 +4,11 @@ using MICore; using Xunit.Abstractions; using System.Globalization; +using Microsoft.DebugEngineHost; namespace MIDebugEngineUnitTests { - class TestLogger : ILogger + class TestLogger : ILogChannel { private static TestLogger s_instance; @@ -26,22 +27,29 @@ public static TestLogger Instance private ITestOutputHelper _output; - private TestLogger() {} - internal void RegisterTestOutputHelper(ITestOutputHelper output) { _output = output; } - public void WriteLine(string line) + public void WriteLine(LogLevel level, string line) { _output?.WriteLine(line); } - public void WriteLine(string format, params object[] args) + public void WriteLine(LogLevel level, string format, params object[] args) { _output?.WriteLine(string.Format(CultureInfo.InvariantCulture, format, args)); } + + // Unused as ITestOutputHelper does not have a Flush implementation + public void Flush() { } + + // Unused as ITestOutputHelper does not have a Close implementation + public void Close() { } + + // Unused as ITestOutputHelper does not have LogLevels + public void SetLogLevel(LogLevel level) { } } public class NatvisNamesTest diff --git a/src/OpenDebugAD7/AD7DebugSession.cs b/src/OpenDebugAD7/AD7DebugSession.cs index 425061684..dcea56bc0 100644 --- a/src/OpenDebugAD7/AD7DebugSession.cs +++ b/src/OpenDebugAD7/AD7DebugSession.cs @@ -215,17 +215,38 @@ private void SetCommonDebugSettings(Dictionary args) if (logging != null) { + HostLogger.Reset(); + m_logger.SetLoggingConfiguration(LoggingCategory.Exception, logging.GetValueAsBool("exceptions").GetValueOrDefault(true)); m_logger.SetLoggingConfiguration(LoggingCategory.Module, logging.GetValueAsBool("moduleLoad").GetValueOrDefault(true)); m_logger.SetLoggingConfiguration(LoggingCategory.StdOut, logging.GetValueAsBool("programOutput").GetValueOrDefault(true)); m_logger.SetLoggingConfiguration(LoggingCategory.StdErr, logging.GetValueAsBool("programOutput").GetValueOrDefault(true)); - bool? engineLogging = logging.GetValueAsBool("engineLogging"); - if (engineLogging.HasValue) + JToken engineLogging = logging.GetValue("engineLogging", StringComparison.OrdinalIgnoreCase); + if (engineLogging != null) { - m_logger.SetLoggingConfiguration(LoggingCategory.EngineLogging, engineLogging.Value); - HostLogger.EnableHostLogging(); - HostLogger.Instance.LogCallback = s => m_logger.WriteLine(LoggingCategory.EngineLogging, s); + if (engineLogging.Type == JTokenType.Boolean) + { + bool engineLoggingBool = engineLogging.Value(); + if (engineLoggingBool) + { + m_logger.SetLoggingConfiguration(LoggingCategory.EngineLogging, true); + HostLogger.EnableHostLogging((message) => m_logger.WriteLine(LoggingCategory.EngineLogging, message), LogLevel.Verbose); + } + } + else if (engineLogging.Type == JTokenType.String) + { + string engineLoggingString = engineLogging.Value(); + if (Enum.TryParse(engineLoggingString, ignoreCase: true, out LogLevel level)) + { + m_logger.SetLoggingConfiguration(LoggingCategory.EngineLogging, true); + HostLogger.EnableHostLogging((message) => m_logger.WriteLine(LoggingCategory.EngineLogging, message), level); + } + } + else + { + m_logger.WriteLine(LoggingCategory.EngineLogging, string.Format(CultureInfo.CurrentCulture, AD7Resources.Warning_EngineLoggingParse, engineLogging.ToString())); + } } bool? trace = logging.GetValueAsBool("trace"); @@ -239,6 +260,33 @@ private void SetCommonDebugSettings(Dictionary args) { m_logger.SetLoggingConfiguration(LoggingCategory.AdapterResponse, traceResponse.Value); } + + JToken natvisDiagnostics = logging.GetValue("natvisDiagnostics", StringComparison.OrdinalIgnoreCase); + if (natvisDiagnostics != null) + { + if (natvisDiagnostics.Type == JTokenType.Boolean) + { + bool natvisDiagnosticsBool = natvisDiagnostics.Value(); + if (natvisDiagnosticsBool) + { + m_logger.SetLoggingConfiguration(LoggingCategory.NatvisDiagnostics, true); + HostLogger.EnableNatvisDiagnostics((message) => m_logger.WriteLine(LoggingCategory.NatvisDiagnostics, message), LogLevel.Verbose); + } + } + else if (natvisDiagnostics.Type == JTokenType.String) + { + string natvisDiagnosticsString = natvisDiagnostics.Value(); + if (Enum.TryParse(natvisDiagnosticsString, ignoreCase: true, out LogLevel level)) + { + m_logger.SetLoggingConfiguration(LoggingCategory.NatvisDiagnostics, true); + HostLogger.EnableNatvisDiagnostics((message) => m_logger.WriteLine(LoggingCategory.NatvisDiagnostics, string.Concat("[Natvis] ", message)), level); + } + } + else + { + m_logger.WriteLine(LoggingCategory.EngineLogging, string.Format(CultureInfo.CurrentCulture, AD7Resources.Warning_NatvisLoggingParse, natvisDiagnostics.ToString())); + } + } } } diff --git a/src/OpenDebugAD7/AD7Resources.Designer.cs b/src/OpenDebugAD7/AD7Resources.Designer.cs index eea617771..abb7d62bf 100644 --- a/src/OpenDebugAD7/AD7Resources.Designer.cs +++ b/src/OpenDebugAD7/AD7Resources.Designer.cs @@ -632,6 +632,15 @@ internal static string Registers_Scope_Name { } } + /// + /// Looks up a localized string similar to Failed to parse engine logging setting: '{0}'. + /// + internal static string Warning_EngineLoggingParse { + get { + return ResourceManager.GetString("Warning_EngineLoggingParse", resourceCulture); + } + } + /// /// Looks up a localized string similar to Could not detect launch url.. /// @@ -650,6 +659,15 @@ internal static string Warning_LaunchBrowserFailed { } } + /// + /// Looks up a localized string similar to Failed to parse Nativs logging setting: '{0}'. + /// + internal static string Warning_NatvisLoggingParse { + get { + return ResourceManager.GetString("Warning_NatvisLoggingParse", resourceCulture); + } + } + /// /// Looks up a localized string similar to WARNING: Unable to terminate '{0}' process. {1} ///. diff --git a/src/OpenDebugAD7/AD7Resources.resx b/src/OpenDebugAD7/AD7Resources.resx index 60be8f97f..e50d73343 100644 --- a/src/OpenDebugAD7/AD7Resources.resx +++ b/src/OpenDebugAD7/AD7Resources.resx @@ -338,4 +338,12 @@ Cannot evaluate expression on the specified stack frame. + + Failed to parse engine logging setting: '{0}' + {0} is the string passed in by the user + + + Failed to parse Nativs logging setting: '{0}' + {0} is the string passed in by the user + \ No newline at end of file diff --git a/src/OpenDebugAD7/DebugEventLogger.cs b/src/OpenDebugAD7/DebugEventLogger.cs index 45ef9c66d..612039b97 100644 --- a/src/OpenDebugAD7/DebugEventLogger.cs +++ b/src/OpenDebugAD7/DebugEventLogger.cs @@ -35,6 +35,8 @@ internal enum LoggingCategory Module, /// Process exit message. ProcessExit, + /// Natvis Diagnostic Messages + NatvisDiagnostics } /// Logging class to handle when and how various classes of output should be logged. diff --git a/src/OpenDebugAD7/OpenDebug/Program.cs b/src/OpenDebugAD7/OpenDebug/Program.cs index 875a91fd4..21eb7c36a 100644 --- a/src/OpenDebugAD7/OpenDebug/Program.cs +++ b/src/OpenDebugAD7/OpenDebug/Program.cs @@ -27,6 +27,9 @@ private static int Main(string[] argv) { int port = -1; List loggingCategories = new List(); + bool enableEngineLogger = false; + LogLevel level = LogLevel.Verbose; + string logFilePath = string.Empty; // parse command line arguments foreach (var a in argv) @@ -49,6 +52,10 @@ private static int Main(string[] argv) Console.WriteLine("--trace=response: print requests and response from VS Code to the console."); Console.WriteLine("--engineLogging[=filePath]: Enable logging from the debug engine. If not"); Console.WriteLine(" specified, the log will go to the console."); + Console.WriteLine("--engineLogLevel=: Set's the log level for engine logging."); + Console.WriteLine(" If not specified, default log level is LogLevel.Verbose"); + Console.WriteLine("--natvisDiagnostics[=logLevel]: Enable logging for natvis. If not"); + Console.WriteLine(" specified, the log will go to the console. Default logging level is LogLevel.Verbose."); Console.WriteLine("--server[=port_num] : Start the debug adapter listening for requests on the"); Console.WriteLine(" specified TCP/IP port instead of stdin/out. If port is not specified"); Console.WriteLine(" TCP {0} will be used.", DEFAULT_PORT); @@ -64,8 +71,14 @@ private static int Main(string[] argv) loggingCategories.Add(LoggingCategory.AdapterResponse); break; case "--engineLogging": - loggingCategories.Add(LoggingCategory.EngineLogging); - HostLogger.EnableHostLogging(); + enableEngineLogger = true; + break; + case "--natvisDiagnostics": + loggingCategories.Add(LoggingCategory.NatvisDiagnostics); + HostLogger.EnableNatvisDiagnostics((msg) => + { + Console.Error.WriteLine(msg); + }, LogLevel.Verbose); break; case "--server": port = DEFAULT_PORT; @@ -87,17 +100,28 @@ private static int Main(string[] argv) return -1; } } - else if (a.StartsWith("--engineLogging=", StringComparison.Ordinal)) + else if (a.StartsWith("--natvisDiagnostics=", StringComparison.Ordinal)) { - HostLogger.EnableHostLogging(); - try + if (!Enum.TryParse(a.Substring("--natvisDiagnostics=".Length), out LogLevel natvisLogLevel)) { - HostLogger.Instance.LogFilePath = a.Substring("--engineLogging=".Length); + loggingCategories.Add(LoggingCategory.NatvisDiagnostics); + HostLogger.EnableNatvisDiagnostics((msg) => + { + Console.Error.WriteLine(msg); + }, natvisLogLevel); } - catch (Exception e) + } + else if (a.StartsWith("--engineLogging=", StringComparison.Ordinal)) + { + enableEngineLogger = true; + logFilePath = a.Substring("--engineLogging=".Length); + HostLogger.SetEngineLogFile(logFilePath); + } + else if (a.StartsWith("--engineLogLevel=", StringComparison.Ordinal)) + { + if (!Enum.TryParse(a.Substring("--engineLogLevel=".Length), out level)) { - Console.Error.WriteLine("OpenDebugAD7: ERROR: Unable to open log file. " + e.Message); - return -1; + level = LogLevel.Verbose; } } else if (a.StartsWith("--adapterDirectory=", StringComparison.Ordinal)) @@ -119,6 +143,23 @@ private static int Main(string[] argv) } } + if (enableEngineLogger) + { + loggingCategories.Add(LoggingCategory.EngineLogging); + try + { + HostLogger.EnableHostLogging((msg) => + { + Console.Error.WriteLine(msg); + }, level); + } + catch (Exception e) + { + Console.Error.WriteLine("OpenDebugAD7: ERROR: Unable to open log file. " + e.Message); + return -1; + } + } + if (port > 0) { // TCP/IP server diff --git a/src/OpenDebugAD7/OpenDebug/Utilities.cs b/src/OpenDebugAD7/OpenDebug/Utilities.cs index 39f44b116..d2d0d39a6 100644 --- a/src/OpenDebugAD7/OpenDebug/Utilities.cs +++ b/src/OpenDebugAD7/OpenDebug/Utilities.cs @@ -336,7 +336,7 @@ public static void ReportException(Exception e) try { HostTelemetry.ReportCurrentException(e, null); - Logger.WriteLine("EXCEPTION: " + e.ToString()); + HostLogger.GetEngineLogChannel()?.WriteLine(LogLevel.Error, "EXCEPTION: " + e.ToString()); } catch { diff --git a/test/CppTests/Tests/NatvisTests.cs b/test/CppTests/Tests/NatvisTests.cs index 37bc46b8f..69b2526c4 100644 --- a/test/CppTests/Tests/NatvisTests.cs +++ b/test/CppTests/Tests/NatvisTests.cs @@ -36,6 +36,9 @@ public NatvisTests(ITestOutputHelper outputHelper) : base(outputHelper) private const string NatvisName = "natvis"; private const string NatvisSourceName = "main.cpp"; + + // These line numbers will need to change if src/natvis/main.cpp changes + private const int SimpleClassAssignmentLine = 47; private const int ReturnSourceLine = 51; [Theory] @@ -369,7 +372,7 @@ public void TestThisConditional(ITestSettings settings) runner.RunCommand(launch); this.Comment("Set Breakpoint before assigning 'simpleClass' and end of method."); - SourceBreakpoints writerBreakpoints = debuggee.Breakpoints(NatvisSourceName, new int[] { 46, ReturnSourceLine }); + SourceBreakpoints writerBreakpoints = debuggee.Breakpoints(NatvisSourceName, new int[] { SimpleClassAssignmentLine, ReturnSourceLine }); runner.SetBreakpoints(writerBreakpoints); runner.Expects.StoppedEvent(StoppedReason.Breakpoint).AfterConfigurationDone();