mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-06 01:25:43 -05:00
Move unused forked code to external directory (#1192)
* Move unused forked code to external directory * Fix SLN build errors * Add back resource provider core since it's referenced by main resource provider project * Update PackageProjects step of pipeline
This commit is contained in:
98
external/Microsoft.SqlTools.Hosting.v2/Utility/AsyncLock.cs
vendored
Normal file
98
external/Microsoft.SqlTools.Hosting.v2/Utility/AsyncLock.cs
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
//
|
||||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a simple wrapper over a SemaphoreSlim to allow
|
||||
/// synchronization locking inside of async calls. Cannot be
|
||||
/// used recursively.
|
||||
/// </summary>
|
||||
public class AsyncLock
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private readonly Task<IDisposable> lockReleaseTask;
|
||||
private readonly SemaphoreSlim lockSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the AsyncLock class.
|
||||
/// </summary>
|
||||
public AsyncLock()
|
||||
{
|
||||
lockReleaseTask = Task.FromResult((IDisposable)new LockReleaser(this));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Locks
|
||||
/// </summary>
|
||||
/// <returns>A task which has an IDisposable</returns>
|
||||
public Task<IDisposable> LockAsync()
|
||||
{
|
||||
return LockAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtains or waits for a lock which can be used to synchronize
|
||||
/// access to a resource. The wait may be cancelled with the
|
||||
/// given CancellationToken.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">
|
||||
/// A CancellationToken which can be used to cancel the lock.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public Task<IDisposable> LockAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Task waitTask = lockSemaphore.WaitAsync(cancellationToken);
|
||||
|
||||
return waitTask.IsCompleted
|
||||
? lockReleaseTask
|
||||
: waitTask.ContinueWith(
|
||||
(t, releaser) => (IDisposable)releaser,
|
||||
lockReleaseTask.Result,
|
||||
cancellationToken,
|
||||
TaskContinuationOptions.ExecuteSynchronously,
|
||||
TaskScheduler.Default);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Classes
|
||||
|
||||
/// <summary>
|
||||
/// Provides an IDisposable wrapper around an AsyncLock so
|
||||
/// that it can easily be used inside of a 'using' block.
|
||||
/// </summary>
|
||||
private class LockReleaser : IDisposable
|
||||
{
|
||||
private readonly AsyncLock lockToRelease;
|
||||
|
||||
internal LockReleaser(AsyncLock lockToRelease)
|
||||
{
|
||||
this.lockToRelease = lockToRelease;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lockToRelease.lockSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
158
external/Microsoft.SqlTools.Hosting.v2/Utility/CommandOptions.cs
vendored
Normal file
158
external/Microsoft.SqlTools.Hosting.v2/Utility/CommandOptions.cs
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// 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.Globalization;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// The command-line options helper class.
|
||||
/// </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>
|
||||
public CommandOptions(string[] args, string serviceName)
|
||||
{
|
||||
ServiceName = serviceName;
|
||||
ErrorMessage = string.Empty;
|
||||
Locale = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < args.Length; ++i)
|
||||
{
|
||||
string arg = args[i];
|
||||
if (arg != null && (arg.StartsWith("--") || arg.StartsWith("-")))
|
||||
{
|
||||
// Extracting arguments and properties
|
||||
arg = arg.Substring(1).ToLowerInvariant();
|
||||
string argName = arg;
|
||||
|
||||
switch (argName)
|
||||
{
|
||||
case "-autoflush-log":
|
||||
AutoFlushLog = true;
|
||||
break;
|
||||
case "-tracing-level":
|
||||
TracingLevel = args[++i];
|
||||
break;
|
||||
case "-log-file":
|
||||
LogFilePath = args[++i];
|
||||
break;
|
||||
case "-locale":
|
||||
SetLocale(args[++i]);
|
||||
break;
|
||||
case "h":
|
||||
case "-help":
|
||||
ShouldExit = true;
|
||||
return;
|
||||
default:
|
||||
ErrorMessage += string.Format("Unknown argument \"{0}\"" + Environment.NewLine, argName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ErrorMessage += ex.ToString();
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ErrorMessage) || ShouldExit)
|
||||
{
|
||||
Console.WriteLine(Usage);
|
||||
ShouldExit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains any error messages during execution
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the program should exit immediately. Set to true when the usage is printed.
|
||||
/// </summary>
|
||||
public bool ShouldExit { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The locale our we should instantiate this service in
|
||||
/// </summary>
|
||||
public string Locale { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of service that is receiving command options
|
||||
/// </summary>
|
||||
public string ServiceName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the usage string describing command-line arguments for the program
|
||||
/// </summary>
|
||||
public string Usage
|
||||
{
|
||||
get
|
||||
{
|
||||
var str = string.Format("{0}" + Environment.NewLine +
|
||||
ServiceName + " " + Environment.NewLine +
|
||||
" Options:" + Environment.NewLine +
|
||||
" [--autoflush-log] (If passed in auto flushing of log files is enabled., Verbose. Default is to not auto-flush log files)" + Environment.NewLine +
|
||||
" [--locale **] (default: 'en')" + Environment.NewLine,
|
||||
" [--log-file **]" + Environment.NewLine +
|
||||
" [--tracing-level **] (** can be any of: All, Off, Critical, Error, Warning, Information, Verbose. Default is Critical)" + Environment.NewLine +
|
||||
" [--help]" + Environment.NewLine +
|
||||
ErrorMessage);
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
public string TracingLevel { get; private set; }
|
||||
|
||||
public string LogFilePath { get; private set; }
|
||||
|
||||
public bool AutoFlushLog { get; private set; } = false;
|
||||
|
||||
public virtual void SetLocale(string locale)
|
||||
{
|
||||
try
|
||||
{
|
||||
LocaleSetter(locale);
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
// Ignore CultureNotFoundException since it only is thrown before Windows 10. Windows 10,
|
||||
// along with macOS and Linux, pick up the default culture if an invalid locale is passed
|
||||
// into the CultureInfo constructor.
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the Locale field used for testing and also sets the global CultureInfo used for
|
||||
/// culture-specific messages
|
||||
/// </summary>
|
||||
/// <param name="locale"></param>
|
||||
internal void LocaleSetter(string locale)
|
||||
{
|
||||
// Creating cultureInfo from our given locale
|
||||
CultureInfo language = new CultureInfo(locale);
|
||||
Locale = locale;
|
||||
|
||||
// Setting our language globally
|
||||
CultureInfo.CurrentCulture = language;
|
||||
CultureInfo.CurrentUICulture = language;
|
||||
}
|
||||
}
|
||||
}
|
||||
100
external/Microsoft.SqlTools.Hosting.v2/Utility/Extensions.cs
vendored
Normal file
100
external/Microsoft.SqlTools.Hosting.v2/Utility/Extensions.cs
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Utility
|
||||
{
|
||||
public static class ObjectExtensions
|
||||
{
|
||||
|
||||
public static IEnumerable<T> AsSingleItemEnumerable<T>(this T obj)
|
||||
{
|
||||
yield return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension to evaluate an object's ToString() method in an exception safe way. This will
|
||||
/// extension method will not throw.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object on which to call ToString()</param>
|
||||
/// <returns>The ToString() return value or a suitable error message is that throws.</returns>
|
||||
public static string SafeToString(this object obj)
|
||||
{
|
||||
string str;
|
||||
|
||||
try
|
||||
{
|
||||
str = obj.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
str = $"<Error converting poperty value to string - {ex.Message}>";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a boolean to a "1" or "0" string. Particularly helpful when sending telemetry
|
||||
/// </summary>
|
||||
public static string ToOneOrZeroString(this bool isTrue)
|
||||
{
|
||||
return isTrue ? "1" : "0";
|
||||
}
|
||||
}
|
||||
|
||||
public static class NullableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension method to evaluate a bool? and determine if it has the value and is true.
|
||||
/// This way we avoid throwing if the bool? doesn't have a value.
|
||||
/// </summary>
|
||||
/// <param name="obj">The <c>bool?</c> to process</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="obj"/> has a value and it is <c>true</c>
|
||||
/// <c>false</c> otherwise.
|
||||
/// </returns>
|
||||
public static bool HasTrue(this bool? obj)
|
||||
{
|
||||
return obj.HasValue && obj.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ExceptionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns true if the passed exception or any inner exception is an OperationCanceledException instance.
|
||||
/// </summary>
|
||||
public static bool IsOperationCanceledException(this Exception e)
|
||||
{
|
||||
Exception current = e;
|
||||
while (current != null)
|
||||
{
|
||||
if (current is OperationCanceledException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
current = current.InnerException;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CollectionExtensions {
|
||||
|
||||
public static TValue GetValueOrSpecifiedDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> map, TKey key)
|
||||
{
|
||||
if (map != null && map.ContainsKey(key))
|
||||
{
|
||||
return map[key];
|
||||
}
|
||||
return default(TValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
451
external/Microsoft.SqlTools.Hosting.v2/Utility/Logger.cs
vendored
Normal file
451
external/Microsoft.SqlTools.Hosting.v2/Utility/Logger.cs
vendored
Normal file
@@ -0,0 +1,451 @@
|
||||
//
|
||||
// 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.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// 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 LogEvent : ushort
|
||||
{
|
||||
Default = 0,
|
||||
IoFileSystem = 1,
|
||||
OsSubSystem = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a simple logging interface built on top of .Net tracing frameworks
|
||||
/// </summary>
|
||||
public static class Logger
|
||||
{
|
||||
internal const SourceLevels defaultTracingLevel = SourceLevels.Critical;
|
||||
internal const string defaultTraceSource = "sqltools";
|
||||
private static SourceLevels tracingLevel = defaultTracingLevel;
|
||||
private static string logFileFullPath;
|
||||
|
||||
internal static TraceSource TraceSource { get; set; }
|
||||
internal static string LogFileFullPath
|
||||
{
|
||||
get => logFileFullPath;
|
||||
private set
|
||||
{
|
||||
//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>
|
||||
/// Calling this method will turn on inclusion CallStack in the log for all future traces
|
||||
/// </summary>
|
||||
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" class instead of "TraceSource" object
|
||||
TraceSource.Switch = new SourceSwitch(TraceSource.Name, value.ToString());
|
||||
// configure the listener level filter
|
||||
tracingLevel = value;
|
||||
Listener.Filter = new EventTypeFilter(tracingLevel);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool AutoFlush { get; set; } = false;
|
||||
|
||||
/// <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>
|
||||
/// <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,
|
||||
bool autoFlush = false)
|
||||
{
|
||||
Logger.tracingLevel = tracingLevel;
|
||||
Logger.AutoFlush = autoFlush;
|
||||
TraceSource = new TraceSource(traceSource, Logger.tracingLevel);
|
||||
if (string.IsNullOrWhiteSpace(logFilePath))
|
||||
{
|
||||
logFilePath = GenerateLogFilePath(traceSource);
|
||||
}
|
||||
|
||||
LogFileFullPath = logFilePath;
|
||||
Write(TraceEventType.Information, $"Initialized the {traceSource} logger. Log file is: {LogFileFullPath}");
|
||||
}
|
||||
|
||||
/// <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(logFilePrefix);
|
||||
if (!string.IsNullOrWhiteSpace(logDir))
|
||||
{
|
||||
if (!Directory.Exists(logDir))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(logDir);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
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 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);
|
||||
}
|
||||
|
||||
// make the log path unique
|
||||
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";
|
||||
}
|
||||
|
||||
private static void ConfigureListener()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(LogFileFullPath))
|
||||
{
|
||||
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="eventType">The level at which the message will be written.</param>
|
||||
/// <param name="logMessage">The message text to be written.</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (AutoFlush)
|
||||
{
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
WriteHeader(eventCache, source, eventType, id);
|
||||
WriteLine(message);
|
||||
WriteFooter(eventCache);
|
||||
}
|
||||
|
||||
public override void TraceEvent(TraceEventCache eventCache, String source, TraceEventType eventType, int id, string format, params object[] args)
|
||||
{
|
||||
if (Filter != null && !Filter.ShouldTrace(eventCache, source, eventType, id, format, args, null, null))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WriteHeader(eventCache, source, eventType, id);
|
||||
if (args != null)
|
||||
{
|
||||
WriteLine(String.Format(CultureInfo.InvariantCulture, format, args));
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLine(format);
|
||||
}
|
||||
WriteFooter(eventCache);
|
||||
}
|
||||
|
||||
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 (eventCache == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IndentLevel++;
|
||||
if (TraceOutputOptions.HasFlag(TraceOptions.LogicalOperationStack))
|
||||
{
|
||||
WriteLine("LogicalOperationStack=" + eventCache.LogicalOperationStack);
|
||||
}
|
||||
|
||||
if (TraceOutputOptions.HasFlag(TraceOptions.Callstack))
|
||||
{
|
||||
WriteLine("Callstack=" + eventCache.Callstack);
|
||||
}
|
||||
|
||||
IndentLevel--;
|
||||
}
|
||||
|
||||
private string FormatHeader(TraceEventCache eventCache, string message)
|
||||
{
|
||||
if (eventCache == null)
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
return $"{(IsEnabled(TraceOptions.DateTime) ? string.Format(CultureInfo.InvariantCulture, "{0} ", eventCache.DateTime.ToLocalTime().ToString("yy-MM-dd H:mm:ss.fffffff", 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 IsEnabled(TraceOptions opt) => TraceOutputOptions.HasFlag(opt);
|
||||
|
||||
}
|
||||
}
|
||||
17
external/Microsoft.SqlTools.Hosting.v2/Utility/ServiceProviderNotSetException.cs
vendored
Normal file
17
external/Microsoft.SqlTools.Hosting.v2/Utility/ServiceProviderNotSetException.cs
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// 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 Microsoft.SqlTools.Hosting.v2;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Utility
|
||||
{
|
||||
public class ServiceProviderNotSetException : InvalidOperationException {
|
||||
|
||||
public ServiceProviderNotSetException()
|
||||
: base(SR.ServiceProviderNotSet) {
|
||||
}
|
||||
}
|
||||
}
|
||||
100
external/Microsoft.SqlTools.Hosting.v2/Utility/TaskExtensions.cs
vendored
Normal file
100
external/Microsoft.SqlTools.Hosting.v2/Utility/TaskExtensions.cs
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// 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.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Utility
|
||||
{
|
||||
public static class TaskExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds handling to check the Exception field of a task and log it if the task faulted
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will effectively swallow exceptions in the task chain.
|
||||
/// </remarks>
|
||||
/// <param name="antecedent">The task to continue</param>
|
||||
/// <param name="continuationAction">
|
||||
/// An optional operation to perform after exception handling has occurred
|
||||
/// </param>
|
||||
/// <returns>Task with exception handling on continuation</returns>
|
||||
public static Task ContinueWithOnFaulted(this Task antecedent, Action<Task> continuationAction)
|
||||
{
|
||||
return antecedent.ContinueWith(task =>
|
||||
{
|
||||
// If the task hasn't faulted or has an exception, skip processing
|
||||
if (!task.IsFaulted || task.Exception == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LogTaskExceptions(task.Exception);
|
||||
|
||||
// Run the continuation task that was provided
|
||||
try
|
||||
{
|
||||
continuationAction?.Invoke(task);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Write(TraceEventType.Error, $"Exception in exception handling continuation: {e}");
|
||||
Logger.Write(TraceEventType.Error, e.StackTrace);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds handling to check the Exception field of a task and log it if the task faulted.
|
||||
/// This version allows for async code to be ran in the continuation function.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will effectively swallow exceptions in the task chain.
|
||||
/// </remarks>
|
||||
/// <param name="antecedent">The task to continue</param>
|
||||
/// <param name="continuationFunc">
|
||||
/// An optional operation to perform after exception handling has occurred
|
||||
/// </param>
|
||||
/// <returns>Task with exception handling on continuation</returns>
|
||||
public static Task ContinueWithOnFaulted(this Task antecedent, Func<Task, Task> continuationFunc)
|
||||
{
|
||||
return antecedent.ContinueWith(task =>
|
||||
{
|
||||
// If the task hasn't faulted or doesn't have an exception, skip processing
|
||||
if (!task.IsFaulted || task.Exception == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LogTaskExceptions(task.Exception);
|
||||
|
||||
// Run the continuation task that was provided
|
||||
try
|
||||
{
|
||||
continuationFunc?.Invoke(antecedent).Wait();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Write(TraceEventType.Error, $"Exception in exception handling continuation: {e}");
|
||||
Logger.Write(TraceEventType.Error, e.StackTrace);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void LogTaskExceptions(AggregateException exception)
|
||||
{
|
||||
// Construct an error message for an aggregate exception and log it
|
||||
StringBuilder sb = new StringBuilder("Unhandled exception(s) in async task:");
|
||||
foreach (Exception e in exception.InnerExceptions)
|
||||
{
|
||||
sb.AppendLine($"{e.GetType().Name}: {e.Message}");
|
||||
sb.AppendLine(e.StackTrace);
|
||||
}
|
||||
Logger.Write(TraceEventType.Error, sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
158
external/Microsoft.SqlTools.Hosting.v2/Utility/Validate.cs
vendored
Normal file
158
external/Microsoft.SqlTools.Hosting.v2/Utility/Validate.cs
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.SqlTools.Hosting.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides common validation methods to simplify method
|
||||
/// parameter checks.
|
||||
/// </summary>
|
||||
public static class Validate
|
||||
{
|
||||
/// <summary>
|
||||
/// Throws ArgumentNullException if value is null.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
public static void IsNotNull(string parameterName, object valueToCheck)
|
||||
{
|
||||
if (valueToCheck == null)
|
||||
{
|
||||
throw new ArgumentNullException(parameterName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws ArgumentOutOfRangeException if the value is outside
|
||||
/// of the given lower and upper limits.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
/// <param name="lowerLimit">The lower limit which the value should not be less than.</param>
|
||||
/// <param name="upperLimit">The upper limit which the value should not be greater than.</param>
|
||||
public static void IsWithinRange(
|
||||
string parameterName,
|
||||
long valueToCheck,
|
||||
long lowerLimit,
|
||||
long upperLimit)
|
||||
{
|
||||
// TODO: Debug assert here if lowerLimit >= upperLimit
|
||||
|
||||
if (valueToCheck < lowerLimit || valueToCheck > upperLimit)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
parameterName,
|
||||
valueToCheck,
|
||||
string.Format(
|
||||
"Value is not between {0} and {1}",
|
||||
lowerLimit,
|
||||
upperLimit));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws ArgumentOutOfRangeException if the value is greater than or equal
|
||||
/// to the given upper limit.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
/// <param name="upperLimit">The upper limit which the value should be less than.</param>
|
||||
public static void IsLessThan(
|
||||
string parameterName,
|
||||
long valueToCheck,
|
||||
long upperLimit)
|
||||
{
|
||||
if (valueToCheck >= upperLimit)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
parameterName,
|
||||
valueToCheck,
|
||||
string.Format(
|
||||
"Value is greater than or equal to {0}",
|
||||
upperLimit));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws ArgumentOutOfRangeException if the value is less than or equal
|
||||
/// to the given lower limit.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
/// <param name="lowerLimit">The lower limit which the value should be greater than.</param>
|
||||
public static void IsGreaterThan(
|
||||
string parameterName,
|
||||
long valueToCheck,
|
||||
long lowerLimit)
|
||||
{
|
||||
if (valueToCheck < lowerLimit)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
parameterName,
|
||||
valueToCheck,
|
||||
string.Format(
|
||||
"Value is less than or equal to {0}",
|
||||
lowerLimit));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws ArgumentException if the value is equal to the undesired value.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The type of value to be validated.</typeparam>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="undesiredValue">The value that valueToCheck should not equal.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
public static void IsNotEqual<TValue>(
|
||||
string parameterName,
|
||||
TValue valueToCheck,
|
||||
TValue undesiredValue)
|
||||
{
|
||||
if (EqualityComparer<TValue>.Default.Equals(valueToCheck, undesiredValue))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
string.Format(
|
||||
"The given value '{0}' should not equal '{1}'",
|
||||
valueToCheck,
|
||||
undesiredValue),
|
||||
parameterName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws ArgumentException if the value is null or an empty string.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
public static void IsNotNullOrEmptyString(string parameterName, string valueToCheck)
|
||||
{
|
||||
if (string.IsNullOrEmpty(valueToCheck))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Parameter contains a null, empty, or whitespace string.",
|
||||
parameterName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws ArgumentException if the value is null, an empty string,
|
||||
/// or a string containing only whitespace.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter being validated.</param>
|
||||
/// <param name="valueToCheck">The value of the parameter being validated.</param>
|
||||
public static void IsNotNullOrWhitespaceString(string parameterName, string valueToCheck)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(valueToCheck))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Parameter contains a null, empty, or whitespace string.",
|
||||
parameterName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user