Enhanced Logging for sqltoolsservice (#695)

This change modifies the logging framework within sqltoolservice.
Moves away from custom Logger object to start using .Net tracing framework. It supports for the static Trace and TraceSource way of logging. For all new code it is recommend that we log the log messages using the existing static Logger class, while the code changes will continue to route the older Trace.Write* calls from the process to same log listeners (and thus the log targets) as used by the Logger class. Thus tracing in SMO code that uses Trace.Write* methods gets routed to the same file as the messages from rest of SQLTools Service code.
Make changes to start using .Net Frameworks codebase for all logging to unify our logging story.
Allows parameter to set tracingLevel filters that controls what kinds of message make it to the log file.
Allows a parameter to set a specific log file name so if these gets set by external code (the UI code using the tools service for example) then the external code is aware of the current log file in use.
Adding unittests to test out the existing and improved logging capabilities.


Sequences of checkins in development branch:
* Saving v1 of logging to prepare for code review. Minor cleanup and some end to end testing still remains
* Removing local launchSettings.json files
* added support for lazy listener to sqltoolsloglistener and removed incorrect changes to comments across files in previous checkin
* Converting time to local time when writing entries to the log
* move the hosting.v2 to new .net based logging code
* removing *.dgml files and addding them to .gitignore
* fixing typo of defaultTraceSource
* Addressing pull request feedback
* Adding a test to verify logging from SMO codebase
* propogating changes to v1 sqltools.hosting commandoptions.cs to the v2 version
* Fixing comments on start and stop callstack methods and whitespaces
* Commenting a test that got uncommented by mistake
* addding .gitattributes file as .sql file was observed to be misconstrued as a binary file
This commit is contained in:
ranasaria
2018-09-24 23:55:59 -07:00
committed by GitHub
parent 0c1648b1c4
commit 09652cccd1
112 changed files with 2262 additions and 831 deletions

View File

@@ -14,6 +14,11 @@ namespace Microsoft.SqlTools.Hosting.Utility
/// </summary>
public class CommandOptions
{
// set default log directory
// refer to https://jimrich.sk/environment-specialfolder-on-windows-linux-and-os-x/ && https://stackoverflow.com/questions/895723/environment-getfolderpath-commonapplicationdata-is-still-returning-c-docum
// for cross platform locations
internal readonly string DefaultLogRoot = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
/// <summary>
/// Construct and parse command line options from the arguments array
/// </summary>
@@ -23,6 +28,13 @@ namespace Microsoft.SqlTools.Hosting.Utility
ErrorMessage = string.Empty;
Locale = string.Empty;
//set default log directory
LoggingDirectory = DefaultLogRoot;
if (!string.IsNullOrWhiteSpace(ServiceName))
{
LoggingDirectory = Path.Combine(LoggingDirectory, ServiceName);
}
try
{
for (int i = 0; i < args.Length; ++i)
@@ -37,7 +49,12 @@ namespace Microsoft.SqlTools.Hosting.Utility
switch (argName)
{
case "-enable-logging":
EnableLogging = true;
break; //ignore this old option for now for backward compat with older opsstudio code - to be removed in a future checkin
case "-tracing-level":
TracingLevel = args[++i];
break;
case "-log-file":
LogFilePath = args[++i];
break;
case "-log-dir":
SetLoggingDirectory(args[++i]);
@@ -76,12 +93,6 @@ namespace Microsoft.SqlTools.Hosting.Utility
/// </summary>
public string ErrorMessage { get; private set; }
/// <summary>
/// Whether diagnostic logging is enabled
/// </summary>
public bool EnableLogging { get; private set; }
/// <summary>
/// Gets the directory where log files are output.
/// </summary>
@@ -112,15 +123,21 @@ namespace Microsoft.SqlTools.Hosting.Utility
var str = string.Format("{0}" + Environment.NewLine +
ServiceName + " " + Environment.NewLine +
" Options:" + Environment.NewLine +
" [--enable-logging]" + Environment.NewLine +
" [--log-dir **] (default: current directory)" + Environment.NewLine +
" [--help]" + Environment.NewLine +
" [--enable-logging ] (obsolete - present for backward compat. Logging is always on, except -tracing-level Off has the effect of disabling logging)" + Environment.NewLine +
" [--tracing-level **] (** can be any of: All, Off, Critical, Error, Warning, Information, Verbose. Default is Critical)" + Environment.NewLine +
" [--log-file **]" + Environment.NewLine +
" [--log-dir **] (default: %APPDATA%\\<service name>)" + Environment.NewLine +
" [--locale **] (default: 'en')" + Environment.NewLine,
" [--help]" + Environment.NewLine +
ErrorMessage);
return str;
}
}
public string TracingLevel { get; private set; }
public string LogFilePath { get; private set; }
private void SetLoggingDirectory(string loggingDirectory)
{
if (string.IsNullOrWhiteSpace(loggingDirectory))

View File

@@ -5,86 +5,157 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
namespace Microsoft.SqlTools.Hosting.Utility
{
/// <summary>
/// Defines the level indicators for log messages.
/// Ordinal value of each LogEvent value corresponds to a unique event id to be used in trace.
/// By convention explicitly specify the integer value so that when this list grows large it is easy to figure out
/// enumeration corresponding to a numeric value. We could be reserving ranges of values for specific areas or logEvents.
/// Maximum value assignable to LogEvent enum value is 65,535.
/// </summary>
public enum LogLevel
public enum LogEvent : ushort
{
/// <summary>
/// Indicates a verbose log message.
/// </summary>
Verbose,
/// <summary>
/// Indicates a normal, non-verbose log message.
/// </summary>
Normal,
/// <summary>
/// Indicates a warning message.
/// </summary>
Warning,
/// <summary>
/// Indicates an error message.
/// </summary>
Error
Default = 0,
IoFileSystem = 1,
OsSubSystem = 2,
}
/// <summary>
/// Provides a simple logging interface. May be replaced with a
/// more robust solution at a later date.
/// Provides a simple logging interface built on top of .Net tracing frameworks
/// </summary>
public class Logger
public static class Logger
{
private static LogWriter logWriter;
internal const SourceLevels defaultTracingLevel = SourceLevels.Critical;
internal const string defaultTraceSource = "sqltools";
private static SourceLevels tracingLevel = defaultTracingLevel;
private static string logFileFullPath;
private static bool isEnabled;
private static bool isInitialized = false;
private static Lazy<Logger> lazyInstance = new Lazy<Logger>(() => new Logger());
public static Logger Instance
internal static TraceSource TraceSource { get; set; }
internal static string LogFileFullPath
{
get
get => logFileFullPath;
private set
{
return lazyInstance.Value;
//If the log file path has a directory component then ensure that the directory exists.
if (!string.IsNullOrEmpty(Path.GetDirectoryName(value)) && !Directory.Exists(Path.GetDirectoryName(value)))
{
Directory.CreateDirectory(Path.GetDirectoryName(value));
}
logFileFullPath = value;
ConfigureListener();
}
}
private static SqlToolsTraceListener Listener { get; set; }
private static void ConfigureLogFile(string logFilePrefix) => LogFileFullPath = GenerateLogFilePath(logFilePrefix);
/// <summary>
/// Initializes the Logger for the current session.
/// Calling this method will turn on inclusion CallStack in the log for all future traces
/// </summary>
/// <param name="logFilePath">
/// Optional. Specifies the path at which log messages will be written.
/// </param>
/// <param name="minimumLogLevel">
public static void StartCallStack() => Listener.TraceOutputOptions |= TraceOptions.Callstack;
/// <summary>
/// Calling this method will turn off inclusion of CallStack in the log for all future traces
/// </summary>
public static void StopCallStack() => Listener.TraceOutputOptions &= ~TraceOptions.Callstack;
/// <summary>
/// Calls flush on defaultTracingLevel configured listeners.
/// </summary>
public static void Flush()
{
TraceSource.Flush();
Trace.Flush();
}
public static void Close()
{
Flush();
TraceSource.Close();
Trace.Close();
Listener = null; // Since we have closed the listener, set listener to null.
}
public static SourceLevels TracingLevel
{
get => tracingLevel;
set
{
// configures the source level filter. This alone is not enough for tracing that is done via "Trace" object instead of "TraceSource" object
TraceSource.Switch = new SourceSwitch(TraceSource.Name, value.ToString());
// configure the listener level filter
tracingLevel = value;
Listener.Filter = new EventTypeFilter(tracingLevel);
}
}
/// <summary>
/// Initializes the Logger for the current process.
/// </summary>
/// <param name="tracingLevel">
/// Optional. Specifies the minimum log message level to write to the log file.
/// </param>
public void Initialize(
string logFilePath = "sqltools",
LogLevel minimumLogLevel = LogLevel.Normal,
bool isEnabled = true)
/// <param name="logFilePath">
/// Optional. Specifies the log name prefix for the log file name at which log messages will be written.
/// <param name="traceSource">
/// Optional. Specifies the tracesource name.
/// </param>
public static void Initialize(
SourceLevels tracingLevel = defaultTracingLevel,
string logFilePath = null,
string traceSource = defaultTraceSource)
{
Logger.isEnabled = isEnabled;
// return if the logger is not enabled or already initialized
if (!Logger.isEnabled || Logger.isInitialized)
Logger.tracingLevel = tracingLevel;
TraceSource = new TraceSource(traceSource, Logger.tracingLevel);
if (string.IsNullOrWhiteSpace(logFilePath))
{
return;
logFilePath = GenerateLogFilePath(traceSource);
}
Logger.isInitialized = true;
LogFileFullPath = logFilePath;
Write(TraceEventType.Information, $"Initialized the {traceSource} logger");
}
/// <summary>
/// Initializes the Logger for the current process.
/// </summary>
/// </param>
/// <param name="tracingLevel">
/// Optional. Specifies the minimum log message level to write to the log file.
/// </param>
/// <param name="logFilePath">
/// Optional. Specifies the log name prefix for the log file name at which log messages will be written.
/// <param name="traceSource">
/// Optional. Specifies the tracesource name.
/// </param>
public static void Initialize(string tracingLevel, string logFilePath = null, string traceSource = defaultTraceSource)
{
Initialize(Enum.TryParse<SourceLevels>(tracingLevel, out SourceLevels sourceTracingLevel)
? sourceTracingLevel
: defaultTracingLevel
, logFilePath
, traceSource);
}
/// <summary>
/// Configures the LogfilePath for the tracelistener in use for this process.
/// </summary>
/// <returns>
/// Returns the log file path corresponding to logfilePrefix
/// </returns>
public static string GenerateLogFilePath(string logFilePrefix = defaultTraceSource)
{
if (string.IsNullOrWhiteSpace(logFilePrefix))
{
throw new ArgumentOutOfRangeException(nameof(logFilePrefix), $"LogfilePath cannot be configured if argument {nameof(logFilePrefix)} has not been set");
}
// Create the log directory
string logDir = Path.GetDirectoryName(logFilePath);
string logDir = Path.GetDirectoryName(logFilePrefix);
if (!string.IsNullOrWhiteSpace(logDir))
{
if (!Directory.Exists(logDir))
@@ -93,207 +164,280 @@ namespace Microsoft.SqlTools.Hosting.Utility
{
Directory.CreateDirectory(logDir);
}
catch (Exception)
catch (Exception ex)
{
// Creating the log directory is a best effort operation, so ignore any failures.
Write(TraceEventType.Error, LogEvent.IoFileSystem, $"Unable to create directory:{logDir}\nException encountered:{ex}");
}
}
}
// get a unique number to prevent conflicts of two process launching at the same time
int uniqueId;
try
{
uniqueId = Process.GetCurrentProcess().Id;
}
catch (Exception)
catch (Exception ex)
{
Write(TraceEventType.Information, LogEvent.OsSubSystem, $"Unable to get process id of current running process\nException encountered:{ex}");
// if the pid look up fails for any reason, just use a random number
uniqueId = new Random().Next(1000, 9999);
uniqueId = new Random().Next(1000, 9999);
}
// make the log path unique
string fullFileName = string.Format(
"{0}_{1,4:D4}{2,2:D2}{3,2:D2}{4,2:D2}{5,2:D2}{6,2:D2}{7}.log",
logFilePath,
DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day,
DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second,
uniqueId);
if (logWriter != null)
{
logWriter.Dispose();
}
// TODO: Parameterize this
logWriter =
new LogWriter(
minimumLogLevel,
fullFileName,
true);
Write(LogLevel.Normal, "Initializing SQL Tools Service Host logger");
return $"{logFilePrefix}_{DateTime.Now.Year,4:D4}{DateTime.Now.Month,2:D2}{DateTime.Now.Day,2:D2}{DateTime.Now.Hour,2:D2}{DateTime.Now.Minute,2:D2}{DateTime.Now.Second,2:D2}{uniqueId}.log";
}
/// <summary>
/// Closes the Logger.
/// </summary>
public void Close()
private static void ConfigureListener()
{
if (logWriter != null)
if (string.IsNullOrWhiteSpace(LogFileFullPath))
{
logWriter.Dispose();
throw new InvalidOperationException("Listeners cannot be configured if LogFileFullPath has not been set");
}
Listener = new SqlToolsTraceListener(LogFileFullPath)
{
TraceOutputOptions = TraceOptions.DateTime | TraceOptions.ProcessId | TraceOptions.ThreadId,
Filter = new EventTypeFilter(TracingLevel),
};
TraceSource.Listeners.Clear();
TraceSource.Listeners.Add(Listener);
Trace.Listeners.Clear();
Trace.Listeners.Add(Listener);
}
/// <summary>
/// Writes a message to the log file.
/// </summary>
/// <param name="logLevel">The level at which the message will be written.</param>
/// <param name="eventType">The level at which the message will be written.</param>
/// <param name="logMessage">The message text to be written.</param>
/// <param name="callerName">The name of the calling method.</param>
/// <param name="callerSourceFile">The source file path where the calling method exists.</param>
/// <param name="callerLineNumber">The line number of the calling method.</param>
public void Write(
LogLevel logLevel,
string logMessage,
[CallerMemberName] string callerName = null,
[CallerFilePath] string callerSourceFile = null,
[CallerLineNumber] int callerLineNumber = 0)
{
// return if the logger is not enabled or not initialized
if (!Logger.isEnabled || !Logger.isInitialized)
public static void Write(TraceEventType eventType, string logMessage) => Write(eventType, LogEvent.Default, logMessage);
/// <summary>
/// Writes a message to the log file with accompanying callstack.
/// </summary>
/// <param name="eventType">The level at which the message will be written.</param>
/// <param name="logMessage">The message text to be written.</param>
/// <remarks>
/// The callstack logging gets turned on globally and any other log writes that happens in the time window
/// while this log write is happening will also get callstack information logged. This is not considered
/// and trying to isolate the callstack logging to be turned of for just one call is unnecessarily complex.
/// </remarks>
public static void WriteWithCallstack(TraceEventType eventType, string logMessage) => WriteWithCallstack(eventType, LogEvent.Default, logMessage);
/// <summary>
/// Writes a message to the log file with accompanying callstack.
/// </summary>
/// <param name="eventType">The level at which the message will be written.</param>
/// <param name="logEvent">The event id enumeration for the log event.</param>
/// <param name="logMessage">The message text to be written.</param>
/// <remarks>
/// The callstack logging gets turned on globally and any other log writes that happens in the time window
/// while this log write is happening will also get callstack information logged. This is not considered
/// and trying to isolate the callstack logging to be turned of for just one call is unnecessarily complex.
/// </remarks>
public static void WriteWithCallstack(TraceEventType eventType, LogEvent logEvent, string logMessage)
{
Logger.StartCallStack();
Write(eventType, logEvent, logMessage);
Logger.StopCallStack();
}
/// <summary>
/// Writes a message to the log file.
/// </summary>
/// <param name="eventType">The level at which the message will be written.</param>
/// <param name="logEvent">The event id enumeration for the log event.</param>
/// <param name="logMessage">The message text to be written.</param>
public static void Write(
TraceEventType eventType,
LogEvent logEvent,
string logMessage)
{
// If logger is initialized then use TraceSource else use Trace
if (TraceSource != null)
{
TraceSource.TraceEvent(eventType, (ushort)logEvent, logMessage);
}
else
{
switch (eventType)
{
case TraceEventType.Critical:
case TraceEventType.Error:
if (eventType == TraceEventType.Critical)
{
logMessage = $@"event={eventType}: {logMessage}";
}
Trace.TraceError(logMessage);
break;
case TraceEventType.Warning:
Trace.TraceWarning(logMessage);
break;
case TraceEventType.Information:
case TraceEventType.Resume:
case TraceEventType.Start:
case TraceEventType.Stop:
case TraceEventType.Suspend:
case TraceEventType.Transfer:
case TraceEventType.Verbose:
if (eventType != TraceEventType.Information)
{
logMessage = $@"event={eventType}: {logMessage}";
}
Trace.TraceInformation(logMessage);
break;
}
}
}
}
/// <summary>
/// This listener has the same behavior as TextWriterTraceListener except it controls how the
/// options: TraceOptions.DateTime, TraceOptions.ProcessId and TraceOptions.ThreadId is written to the output stream.
/// This listener writes the above options, if turned on, inline with the message
/// instead of writing them to indented fields as is the case with TextWriterTraceListener.
/// This implementation also lazily initializes the underlying tracelistener
/// </summary>
/// <remarks>
/// Implementation of this is a lazily initialize trace listener that is partly inspired
/// by: https://stackoverflow.com/questions/30664527/how-to-stop-streamwriter-to-not-to-create-file-if-nothing-to-write
/// </remarks>
internal sealed class SqlToolsTraceListener : TraceListener
{
Lazy<TextWriterTraceListener> _lazyListener;
private TextWriterTraceListener Listener => _lazyListener.Value;
private bool IsListenerCreated => _lazyListener.IsValueCreated;
public SqlToolsTraceListener(string file, string listenerName = "") : base(listenerName)
{
// Wrapping around lazy to make sure that we do not create file if the log.Write events are getting filtered out. i.e. the log file actually gets created the first time an actual write to log file happens.
_lazyListener = new Lazy<TextWriterTraceListener>(
valueFactory: () => new TextWriterTraceListener(new StreamWriter(file, append: true), listenerName),
// LazyThreadSafetyMode.PublicationOnly mode ensures that we keep trying to create the listener (especially the file that write) on all future log.write events even if previous attempt(s) have failed
mode: LazyThreadSafetyMode.PublicationOnly
);
}
#region forward actual write/close/flush/dispose calls to the underlying listener.
public override void Write(string message) => Listener.Write(message);
public override void WriteLine(string message) => Listener.WriteLine(message);
/// <Summary>
/// Closes the <see cref="System.Diagnostics.TextWriterTraceListener.Writer"> so that it no longer
/// receives tracing or debugging output.</see>
/// Make sure that we do not Close if the lazy listener never got created.
/// </Summary>
public override void Close()
{
if (IsListenerCreated)
{
Listener.Close();
}
}
/// <summary>
/// Releases all resources used by the <see cref="SqlToolsTraceListener"/>
/// No unmanaged resources in this class, and it is sealed.
/// No finalizer needed. See http://stackoverflow.com/a/3882819/613130
/// We skip disposing if the lazy listener never got created.
/// </summary>
public new void Dispose()
{
if (IsListenerCreated)
{
Listener.Dispose();
}
}
/// <summary>
/// Flushes the output buffer for the <see cref="System.Diagnostics.TextWriterTraceListener.Writer">.
/// Make sure that we do not Flush if the lazy listener never got created.
/// </summary>
public override void Flush()
{
if (IsListenerCreated)
{
Listener.Flush();
}
}
#endregion
public override void TraceEvent(TraceEventCache eventCache, String source, TraceEventType eventType, int id)
{
TraceEvent(eventCache, source, eventType, id, String.Empty);
}
// All other TraceEvent methods come through this one.
public override void TraceEvent(TraceEventCache eventCache, String source, TraceEventType eventType, int id, string message)
{
if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, message, null, null, null))
{
return;
}
if (logWriter != null)
{
logWriter.Write(
logLevel,
logMessage,
callerName,
callerSourceFile,
callerLineNumber);
}
WriteHeader(eventCache, source, eventType, id);
WriteLine(message);
WriteFooter(eventCache);
}
}
internal class LogWriter : IDisposable
{
private object logLock = new object();
private TextWriter textWriter;
private LogLevel minimumLogLevel = LogLevel.Verbose;
public LogWriter(LogLevel minimumLogLevel, string logFilePath, bool deleteExisting)
public override void TraceEvent(TraceEventCache eventCache, String source, TraceEventType eventType, int id, string format, params object[] args)
{
this.minimumLogLevel = minimumLogLevel;
if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, format, args, null, null))
{
return;
}
// Ensure that we have a usable log file path
if (!Path.IsPathRooted(logFilePath))
{
logFilePath =
Path.Combine(
AppContext.BaseDirectory,
logFilePath);
}
if (!this.TryOpenLogFile(logFilePath, deleteExisting))
{
// If the log file couldn't be opened at this location,
// try opening it in a more reliable path
this.TryOpenLogFile(
Path.Combine(
Environment.GetEnvironmentVariable("TEMP"),
Path.GetFileName(logFilePath)),
deleteExisting);
}
WriteHeader(eventCache, source, eventType, id);
if (args != null)
{
WriteLine(String.Format(CultureInfo.InvariantCulture, format, args));
}
else
{
WriteLine(format);
}
WriteFooter(eventCache);
}
public void Write(
LogLevel logLevel,
string logMessage,
string callerName = null,
string callerSourceFile = null,
int callerLineNumber = 0)
private void WriteHeader(TraceEventCache eventCache, String source, TraceEventType eventType, int id)
=> Write(FormatHeader(eventCache, String.Format(CultureInfo.InvariantCulture, "{0} {1}: {2} : ", source, eventType.ToString(), id.ToString(CultureInfo.InvariantCulture))));
private void WriteFooter(TraceEventCache eventCache)
{
if (this.textWriter != null &&
logLevel >= this.minimumLogLevel)
if (eventCache == null)
{
// System.IO is not thread safe
lock (this.logLock)
{
// Print the timestamp and log level
this.textWriter.WriteLine(
"{0} [{1}] - Method \"{2}\" at line {3} of {4}\r\n",
DateTime.Now,
logLevel.ToString().ToUpper(),
callerName,
callerLineNumber,
callerSourceFile);
// Print out indented message lines
foreach (var messageLine in logMessage.Split('\n'))
{
this.textWriter.WriteLine(" " + messageLine.TrimEnd());
}
// Finish with a newline and flush the writer
this.textWriter.WriteLine();
this.textWriter.Flush();
}
return;
}
IndentLevel++;
if (TraceOutputOptions.HasFlag(TraceOptions.LogicalOperationStack))
{
WriteLine("LogicalOperationStack=" + eventCache.LogicalOperationStack);
}
if (TraceOutputOptions.HasFlag(TraceOptions.Callstack))
{
WriteLine("Callstack=" + eventCache.Callstack);
}
IndentLevel--;
}
public void Dispose()
private string FormatHeader(TraceEventCache eventCache, string message)
{
if (this.textWriter != null)
if (eventCache == null)
{
this.textWriter.Flush();
this.textWriter.Dispose();
this.textWriter = null;
return message;
}
return $"{(IsEnabled(TraceOptions.DateTime) ? string.Format(CultureInfo.InvariantCulture, "{0} ", eventCache.DateTime.ToLocalTime().ToString("u", CultureInfo.InvariantCulture)) : string.Empty)}"
+ $"{(IsEnabled(TraceOptions.ProcessId) ? string.Format(CultureInfo.InvariantCulture, "pid:{0} ", eventCache.ProcessId.ToString(CultureInfo.InvariantCulture)) : string.Empty)}"
+ $"{(IsEnabled(TraceOptions.ThreadId) ? string.Format(CultureInfo.InvariantCulture, "tid:{0} ", eventCache.ThreadId.ToString(CultureInfo.InvariantCulture)) : string.Empty)}"
+ message;
}
private bool TryOpenLogFile(
string logFilePath,
bool deleteExisting)
{
try
{
// Make sure the log directory exists
Directory.CreateDirectory(
Path.GetDirectoryName(
logFilePath));
private bool IsEnabled(TraceOptions opt) => TraceOutputOptions.HasFlag(opt);
// Open the log file for writing with UTF8 encoding
this.textWriter =
new StreamWriter(
new FileStream(
logFilePath,
deleteExisting ?
FileMode.Create :
FileMode.Append),
Encoding.UTF8);
return true;
}
catch (Exception e)
{
if (e is UnauthorizedAccessException ||
e is IOException)
{
// This exception is thrown when we can't open the file
// at the path in logFilePath. Return false to indicate
// that the log file couldn't be created.
return false;
}
// Unexpected exception, rethrow it
throw;
}
}
}
}

View File

@@ -4,6 +4,7 @@
//
using System;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
@@ -41,8 +42,8 @@ namespace Microsoft.SqlTools.Hosting.Utility
}
catch (Exception e)
{
Logger.Instance.Write(LogLevel.Error, $"Exception in exception handling continuation: {e}");
Logger.Instance.Write(LogLevel.Error, e.StackTrace);
Logger.Write(TraceEventType.Error, $"Exception in exception handling continuation: {e}");
Logger.Write(TraceEventType.Error, e.StackTrace);
}
});
}
@@ -78,8 +79,8 @@ namespace Microsoft.SqlTools.Hosting.Utility
}
catch (Exception e)
{
Logger.Instance.Write(LogLevel.Error, $"Exception in exception handling continuation: {e}");
Logger.Instance.Write(LogLevel.Error, e.StackTrace);
Logger.Write(TraceEventType.Error, $"Exception in exception handling continuation: {e}");
Logger.Write(TraceEventType.Error, e.StackTrace);
}
});
}
@@ -93,7 +94,7 @@ namespace Microsoft.SqlTools.Hosting.Utility
sb.AppendLine($"{e.GetType().Name}: {e.Message}");
sb.AppendLine(e.StackTrace);
}
Logger.Instance.Write(LogLevel.Error, sb.ToString());
Logger.Write(TraceEventType.Error, sb.ToString());
}
}
}