private void StartLogging() { _logger = new HostLogger(LogLevel); // We need to not write log messages to Stdio // if it's being used as a protocol transport if (!Stdio) { var hostLogger = new PSHostLogger(Host.UI); _loggerUnsubscribers.Add(_logger.Subscribe(hostLogger)); } string logDirPath = GetLogDirPath(); string logPath = Path.Combine(logDirPath, "StartEditorServices.log"); // Temp debugging sessions may try to reuse this same path, // so we ensure they have a unique path if (File.Exists(logPath)) { int randomInt = new Random().Next(); logPath = Path.Combine(logDirPath, $"StartEditorServices-temp{randomInt.ToString("X", CultureInfo.InvariantCulture.NumberFormat)}.log"); } var fileLogger = StreamLogger.CreateWithNewFile(logPath); _disposableResources.Add(fileLogger); IDisposable fileLoggerUnsubscriber = _logger.Subscribe(fileLogger); fileLogger.AddUnsubscriber(fileLoggerUnsubscriber); _loggerUnsubscribers.Add(fileLoggerUnsubscriber); _logger.Log(PsesLogLevel.Diagnostic, "Logging started"); }
/// <summary> /// Write a startup failure to the session file. /// </summary> /// <param name="reason">The reason for the startup failure.</param> /// <param name="details">Any extra details, which will be serialized as JSON.</param> public void WriteSessionFailure(string reason, object details) { _logger.Log(PsesLogLevel.Diagnostic, "Writing session failure"); var sessionObject = new Dictionary <string, object> { { "status", "failed" }, { "reason", reason }, }; if (details != null) { sessionObject["details"] = details; } WriteSessionObject(sessionObject); }
/// <summary> /// Write a startup failure to the session file. /// </summary> /// <param name="reason">The reason for the startup failure.</param> /// <param name="details">Any extra details, which will be serialized as JSON.</param> public void WriteSessionFailure(string reason, object details) { _logger.Log(PsesLogLevel.Diagnostic, "Writing session failure"); Dictionary <string, object> sessionObject = new() { { "status", "failed" }, { "reason", reason }, };
private void StartLogging() { _logger = new HostLogger(LogLevel); // We need to not write log messages to Stdio // if it's being used as a protocol transport if (!Stdio) { var hostLogger = new PSHostLogger(Host.UI); _loggerUnsubscribers.Add(_logger.Subscribe(hostLogger)); } string logPath = Path.Combine(GetLogDirPath(), "StartEditorServices.log"); var fileLogger = StreamLogger.CreateWithNewFile(logPath); _disposableResources.Add(fileLogger); IDisposable fileLoggerUnsubscriber = _logger.Subscribe(fileLogger); fileLogger.AddUnsubscriber(fileLoggerUnsubscriber); _loggerUnsubscribers.Add(fileLoggerUnsubscriber); _logger.Log(PsesLogLevel.Diagnostic, "Logging started"); }
/// <summary> /// Create a new Editor Services loader. /// </summary> /// <param name="logger">The host logger to use.</param> /// <param name="hostConfig">The host configuration to start editor services with.</param> /// <param name="sessionFileWriter">The session file writer to write the session file with.</param> /// <returns></returns> public static EditorServicesLoader Create( HostLogger logger, EditorServicesConfig hostConfig, ISessionFileWriter sessionFileWriter, IReadOnlyCollection <IDisposable> loggersToUnsubscribe) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (hostConfig == null) { throw new ArgumentNullException(nameof(hostConfig)); } #if CoreCLR // In .NET Core, we add an event here to redirect dependency loading to the new AssemblyLoadContext we load PSES' dependencies into logger.Log(PsesLogLevel.Verbose, "Adding AssemblyResolve event handler for new AssemblyLoadContext dependency loading"); var psesLoadContext = new PsesLoadContext(s_psesDependencyDirPath); if (hostConfig.LogLevel == PsesLogLevel.Diagnostic) { AppDomain.CurrentDomain.AssemblyLoad += (object sender, AssemblyLoadEventArgs args) => { logger.Log( PsesLogLevel.Diagnostic, $"Loaded into load context {AssemblyLoadContext.GetLoadContext(args.LoadedAssembly)}: {args.LoadedAssembly}"); }; } AssemblyLoadContext.Default.Resolving += (AssemblyLoadContext defaultLoadContext, AssemblyName asmName) => { logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {asmName}"); // We only want the Editor Services DLL; the new ALC will lazily load its dependencies automatically if (!string.Equals(asmName.Name, "Microsoft.PowerShell.EditorServices", StringComparison.Ordinal)) { return(null); } string asmPath = Path.Combine(s_psesDependencyDirPath, $"{asmName.Name}.dll"); logger.Log(PsesLogLevel.Verbose, "Loading PSES DLL using new assembly load context"); return(psesLoadContext.LoadFromAssemblyPath(asmPath)); }; #else // In .NET Framework we add an event here to redirect dependency loading in the current AppDomain for PSES' dependencies logger.Log(PsesLogLevel.Verbose, "Adding AssemblyResolve event handler for dependency loading"); if (hostConfig.LogLevel == PsesLogLevel.Diagnostic) { AppDomain.CurrentDomain.AssemblyLoad += (object sender, AssemblyLoadEventArgs args) => { logger.Log( PsesLogLevel.Diagnostic, $"Loaded {args.LoadedAssembly.GetName()}"); }; } // Unlike in .NET Core, we need to be look for all dependencies in .NET Framework, not just PSES.dll AppDomain.CurrentDomain.AssemblyResolve += (object sender, ResolveEventArgs args) => { logger.Log(PsesLogLevel.Diagnostic, $"Assembly resolve event fired for {args.Name}"); var asmName = new AssemblyName(args.Name); var dllName = $"{asmName.Name}.dll"; // First look for the required assembly in the .NET Framework DLL dir string baseDirAsmPath = Path.Combine(s_psesBaseDirPath, dllName); if (File.Exists(baseDirAsmPath)) { logger.Log(PsesLogLevel.Diagnostic, $"Loading {args.Name} from PSES base dir into LoadFrom context"); return(Assembly.LoadFrom(baseDirAsmPath)); } // Then look in the shared .NET Standard directory string asmPath = Path.Combine(s_psesDependencyDirPath, dllName); if (File.Exists(asmPath)) { logger.Log(PsesLogLevel.Diagnostic, $"Loading {args.Name} from PSES dependency dir into LoadFrom context"); return(Assembly.LoadFrom(asmPath)); } return(null); }; #endif return(new EditorServicesLoader(logger, hostConfig, sessionFileWriter, loggersToUnsubscribe)); }
/// <summary> /// Load Editor Services and its dependencies in an isolated way and start it. /// This method's returned task will end when Editor Services shuts down. /// </summary> /// <returns></returns> public async Task LoadAndRunEditorServicesAsync() { // Log important host information here LogHostInformation(); #if !CoreCLR // Make sure the .NET Framework version supports .NET Standard 2.0 CheckNetFxVersion(); #endif // Ensure the language mode allows us to run CheckLanguageMode(); // Add the bundled modules to the PSModulePath UpdatePSModulePath(); // Check to see if the configuration we have is valid ValidateConfiguration(); // Method with no implementation that forces the PSES assembly to load, triggering an AssemblyResolve event _logger.Log(PsesLogLevel.Verbose, "Loading PowerShell Editor Services"); LoadEditorServices(); _logger.Log(PsesLogLevel.Verbose, "Starting EditorServices"); using (var editorServicesRunner = new EditorServicesRunner(_logger, _hostConfig, _sessionFileWriter, _loggersToUnsubscribe)) { // The trigger method for Editor Services // We will wait here until Editor Services shuts down await editorServicesRunner.RunUntilShutdown().ConfigureAwait(false); } }
/// <summary> /// Start and run Editor Services and then wait for shutdown. /// </summary> /// <returns>A task that ends when Editor Services shuts down.</returns> public async Task RunUntilShutdown() { // Start Editor Services Task runAndAwaitShutdown = CreateEditorServicesAndRunUntilShutdown(); // Now write the session file _logger.Log(PsesLogLevel.Diagnostic, "Writing session file"); _sessionFileWriter.WriteSessionStarted(_config.LanguageServiceTransport, _config.DebugServiceTransport); // Finally, wait for Editor Services to shut down await runAndAwaitShutdown.ConfigureAwait(false); }
/// <summary> /// Start and run Editor Services and then wait for shutdown. /// </summary> /// <returns>A task that ends when Editor Services shuts down.</returns> public Task RunUntilShutdown() { // Start Editor Services Task runAndAwaitShutdown = CreateEditorServicesAndRunUntilShutdown(); // Now write the session file _logger.Log(PsesLogLevel.Diagnostic, "Writing session file"); _sessionFileWriter.WriteSessionStarted(_config.LanguageServiceTransport, _config.DebugServiceTransport); // Finally, wait for Editor Services to shut down _logger.Log(PsesLogLevel.Diagnostic, "Waiting on PSES run/shutdown"); return(runAndAwaitShutdown); }
protected override void EndProcessing() { _logger.Log(PsesLogLevel.Diagnostic, "Beginning EndProcessing block"); try { // First try to remove PSReadLine to decomplicate startup // If PSReadLine is enabled, it will be re-imported later RemovePSReadLineForStartup(); // Create the configuration from parameters EditorServicesConfig editorServicesConfig = CreateConfigObject(); var sessionFileWriter = new SessionFileWriter(_logger, SessionDetailsPath); _logger.Log(PsesLogLevel.Diagnostic, "Session file writer created"); using (var psesLoader = EditorServicesLoader.Create(_logger, editorServicesConfig, sessionFileWriter, _loggerUnsubscribers)) { _logger.Log(PsesLogLevel.Verbose, "Loading EditorServices"); psesLoader.LoadAndRunEditorServicesAsync().Wait(); } } catch (Exception e) { _logger.LogException("Exception encountered starting EditorServices", e); // Give the user a chance to read the message if they have a console if (!Stdio) { Host.UI.WriteLine("\n== Press any key to close terminal =="); Console.ReadKey(); } ThrowTerminatingError(new ErrorRecord(e, "PowerShellEditorServicesError", ErrorCategory.NotSpecified, this)); } finally { foreach (IDisposable disposableResource in _disposableResources) { disposableResource.Dispose(); } } }