Skip to content

Commit 88e3d5d

Browse files
Merge pull request #1474 from PowerShell/andschwa/comments
Add more logging and internal documentation
2 parents af3cffa + 14352c5 commit 88e3d5d

File tree

14 files changed

+243
-79
lines changed

14 files changed

+243
-79
lines changed

src/PowerShellEditorServices.Hosting/BuildInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace Microsoft.PowerShell.EditorServices.Hosting
44
{
55
public static class BuildInfo
66
{
7+
// TODO: Include a Git commit hash in this.
78
public static readonly string BuildVersion = "<development-build>";
89
public static readonly string BuildOrigin = "<development>";
910
public static readonly System.DateTime? BuildTime = System.DateTime.Parse("2019-12-06T21:43:41", CultureInfo.InvariantCulture.DateTimeFormat);

src/PowerShellEditorServices.Hosting/Configuration/HostLogger.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ namespace Microsoft.PowerShell.EditorServices.Hosting
1414
{
1515
/// <summary>
1616
/// User-facing log level for editor services configuration.
17-
/// The underlying values of this enum align to both Microsoft.Logging.Extensions.LogLevel
18-
/// and Serilog.Events.LogEventLevel.
1917
/// </summary>
18+
/// <remarks>
19+
/// The underlying values of this enum attempt to align to both <see
20+
/// cref="Microsoft.Logging.Extensions.LogLevel"</see> and <see
21+
/// cref="Serilog.Events.LogEventLevel"</see>.
22+
/// </remarks>
2023
public enum PsesLogLevel
2124
{
2225
Diagnostic = 0,
@@ -27,9 +30,14 @@ public enum PsesLogLevel
2730
}
2831

2932
/// <summary>
30-
/// A logging front-end for host startup allowing handover to the backend
31-
/// and decoupling from the host's particular logging sink.
33+
/// A logging front-end for host startup allowing handover to the backend and decoupling from
34+
/// the host's particular logging sink.
3235
/// </summary>
36+
/// <remarks>
37+
/// This custom logger exists to allow us to log during startup, which is vital information for
38+
/// debugging, but happens before we can load any logger library. This is because startup
39+
/// happens in our isolated assembly environment. See #2292 for more information.
40+
/// </remarks>
3341
public sealed class HostLogger :
3442
IObservable<(PsesLogLevel logLevel, string message)>,
3543
IObservable<(int logLevel, string message)>

src/PowerShellEditorServices.Hosting/EditorServicesLoader.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,7 @@ private void UpdatePSModulePath()
263263

264264
private void LogHostInformation()
265265
{
266-
_logger.Log(PsesLogLevel.Diagnostic, "Logging host information");
267-
268-
_logger.Log(PsesLogLevel.Diagnostic, $@"
269-
PID: {System.Diagnostics.Process.GetCurrentProcess().Id}
270-
");
266+
_logger.Log(PsesLogLevel.Verbose, $"PID: {System.Diagnostics.Process.GetCurrentProcess().Id}");
271267

272268
_logger.Log(PsesLogLevel.Verbose, $@"
273269
== Build Details ==

src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
using Microsoft.PowerShell.EditorServices.Server;
54
using System;
65
using System.Collections.Generic;
76
using System.IO;
87
using System.Threading.Tasks;
8+
using Microsoft.PowerShell.EditorServices.Server;
99

1010
namespace Microsoft.PowerShell.EditorServices.Hosting
1111
{
1212
/// <summary>
1313
/// Class to manage the startup of PowerShell Editor Services.
14-
/// This should be called by <see cref="EditorServicesLoader"/> only after Editor Services has been loaded.
1514
/// </summary>
15+
/// <remarks>
16+
/// This should be called by <see cref="EditorServicesLoader"/> only after Editor Services has
17+
/// been loaded. It relies on <see cref="EditorServicesServerFactory"/> to indirectly load <see
18+
/// cref="Microsoft.Extensions.Logging"/> and <see
19+
/// cref="Microsoft.Extensions.DependencyInjection"/>.
20+
/// </remarks>
1621
internal class EditorServicesRunner : IDisposable
1722
{
1823
private readonly HostLogger _logger;
@@ -36,6 +41,7 @@ public EditorServicesRunner(
3641
_logger = logger;
3742
_config = config;
3843
_sessionFileWriter = sessionFileWriter;
44+
// NOTE: This factory helps to isolate `Microsoft.Extensions.Logging/DependencyInjection`.
3945
_serverFactory = EditorServicesServerFactory.Create(_config.LogPath, (int)_config.LogLevel, logger);
4046
_alreadySubscribedDebug = false;
4147
_loggersToUnsubscribe = loggersToUnsubscribe;
@@ -44,10 +50,13 @@ public EditorServicesRunner(
4450
/// <summary>
4551
/// Start and run Editor Services and then wait for shutdown.
4652
/// </summary>
53+
/// <remarks>
54+
/// TODO: Use "Async" suffix in names of methods that return an awaitable type.
55+
/// </remarks>
4756
/// <returns>A task that ends when Editor Services shuts down.</returns>
4857
public Task RunUntilShutdown()
4958
{
50-
// Start Editor Services
59+
// Start Editor Services (see function below)
5160
Task runAndAwaitShutdown = CreateEditorServicesAndRunUntilShutdown();
5261

5362
// Now write the session file
@@ -59,14 +68,60 @@ public Task RunUntilShutdown()
5968
return runAndAwaitShutdown;
6069
}
6170

71+
/// <remarks>
72+
/// TODO: This class probably should not be <see cref="IDisposable"/> as the primary
73+
/// intention of that interface is to provide cleanup of unmanaged resources, which the
74+
/// logger certainly is not. Nor is this class used with a <see langword="using"/>. It is
75+
/// only because of the use of <see cref="_serverFactory"/> that this class is also
76+
/// disposable, and instead that class should be fixed.
77+
/// </remarks>
6278
public void Dispose()
6379
{
6480
_serverFactory.Dispose();
6581
}
6682

6783
/// <summary>
68-
/// Master method for instantiating, running and waiting for the LSP and debug servers at the heart of Editor Services.
84+
/// This is the servers' entry point, e.g. <c>main</c>, as it instantiates, runs and waits
85+
/// for the LSP and debug servers at the heart of Editor Services. Uses <see
86+
/// cref="HostStartupInfo"/>.
6987
/// </summary>
88+
/// <remarks>
89+
/// The logical stack of the program is:
90+
/// <list type="number">
91+
/// <listheader>
92+
/// <term>Symbol</term>
93+
/// <description>Description</description>
94+
/// </listheader>
95+
/// <item>
96+
/// <term><see cref="Microsoft.PowerShell.EditorServices.Commands.StartEditorServicesCommand"/></term>
97+
/// <description>
98+
/// The StartEditorServicesCommand PSCmdlet, our PowerShell cmdlet written in C# and
99+
/// shipped in the module.
100+
/// </description>
101+
/// </item>
102+
/// <item>
103+
/// <term><see cref="Microsoft.PowerShell.EditorServices.Commands.StartEditorServicesCommand.EndProcessing"/></term>
104+
/// <description>
105+
/// As a cmdlet, this is the end of its "process" block, and it instantiates <see
106+
/// cref="EditorServicesLoader"/>.
107+
/// </description>
108+
/// </item>
109+
/// <item>
110+
/// <term><see cref="EditorServicesLoader.LoadAndRunEditorServicesAsync"></term>
111+
/// <description>
112+
/// Loads isolated dependencies then runs and returns the next task.
113+
/// </description>
114+
/// </item>
115+
/// <item>
116+
/// <term><see cref="RunUntilShutdown"></term>
117+
/// <description>Task which opens a logfile then returns this task.</description>
118+
/// </item>
119+
/// <item>
120+
/// <term><see cref="CreateEditorServicesAndRunUntilShutdown"></term>
121+
/// <description>This task!</description>
122+
/// </item>
123+
/// </list>
124+
/// </remarks>
70125
/// <returns>A task that ends when Editor Services shuts down.</returns>
71126
private async Task CreateEditorServicesAndRunUntilShutdown()
72127
{

src/PowerShellEditorServices/Hosting/EditorServicesLoading.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ internal static class EditorServicesLoading
1111
{
1212
internal static void LoadEditorServicesForHost()
1313
{
14-
// No op that forces loading this assembly
14+
// No-op that forces loading this assembly
1515
}
1616
}
1717
}

src/PowerShellEditorServices/Hosting/EditorServicesServerFactory.cs

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,25 @@ namespace Microsoft.PowerShell.EditorServices.Hosting
2121
{
2222
/// <summary>
2323
/// Factory class for hiding dependencies of Editor Services.
24-
/// In particular, dependency injection and logging are wrapped by factory methods on this class
25-
/// so that the host assembly can construct the LSP and debug servers
26-
/// without taking logging or dependency injection dependencies directly.
2724
/// </summary>
25+
/// <remarks>
26+
/// Dependency injection and logging are wrapped by factory methods on this class so that the
27+
/// host assembly can construct the LSP and debug servers without directly depending on <see
28+
/// cref="Microsoft.Extensions.Logging"/> and <see
29+
/// cref="Microsoft.Extensions.DependencyInjection"/>.
30+
/// </remarks>
2831
internal class EditorServicesServerFactory : IDisposable
2932
{
3033
/// <summary>
31-
/// Create a new Editor Services factory.
32-
/// This method will instantiate logging.
34+
/// Create a new Editor Services factory. This method will instantiate logging.
3335
/// </summary>
36+
/// <remarks>
37+
/// This can only be called once because it sets global state (the logger) and that call is
38+
/// in <see cref="EditorServicesRunner"/>.
39+
///
40+
/// TODO: Why is this a static function wrapping a constructor instead of just a
41+
/// constructor? In the end it returns an instance (albeit a "singleton").
42+
/// </remarks>
3443
/// <param name="logPath">The path of the log file to use.</param>
3544
/// <param name="minimumLogLevel">The minimum log level to use.</param>
3645
/// <returns></returns>
@@ -54,6 +63,9 @@ public static EditorServicesServerFactory Create(string logPath, int minimumLogL
5463
return new EditorServicesServerFactory(loggerFactory, (LogLevel)minimumLogLevel);
5564
}
5665

66+
// TODO: Can we somehow refactor this member so the language and debug servers can be
67+
// instantiated using their constructors instead of tying them to this factory with `Create`
68+
// methods?
5769
private readonly ILoggerFactory _loggerFactory;
5870

5971
private readonly Microsoft.Extensions.Logging.ILogger _logger;
@@ -70,9 +82,13 @@ private EditorServicesServerFactory(ILoggerFactory loggerFactory, LogLevel minim
7082
/// <summary>
7183
/// Create the LSP server.
7284
/// </summary>
85+
/// <remarks>
86+
/// This is only called once and that's in <see cref="EditorServicesRunner"/>.
87+
/// </remarks>
7388
/// <param name="inputStream">The protocol transport input stream.</param>
7489
/// <param name="outputStream">The protocol transport output stream.</param>
75-
/// <param name="hostStartupInfo">The host details configuration for Editor Services instantation.</param>
90+
/// <param name="hostStartupInfo">The host details configuration for Editor Services
91+
/// instantation.</param>
7692
/// <returns>A new, unstarted language server instance.</returns>
7793
public PsesLanguageServer CreateLanguageServer(
7894
Stream inputStream,
@@ -85,25 +101,51 @@ public PsesLanguageServer CreateLanguageServer(
85101
/// <summary>
86102
/// Create the debug server given a language server instance.
87103
/// </summary>
104+
/// <remarks>
105+
/// This is only called once and that's in <see cref="EditorServicesRunner"/>.
106+
/// </remarks>
88107
/// <param name="inputStream">The protocol transport input stream.</param>
89108
/// <param name="outputStream">The protocol transport output stream.</param>
90109
/// <param name="languageServer"></param>
91110
/// <returns>A new, unstarted debug server instance.</returns>
92-
public PsesDebugServer CreateDebugServerWithLanguageServer(Stream inputStream, Stream outputStream, PsesLanguageServer languageServer, bool usePSReadLine)
111+
public PsesDebugServer CreateDebugServerWithLanguageServer(
112+
Stream inputStream,
113+
Stream outputStream,
114+
PsesLanguageServer languageServer,
115+
bool usePSReadLine)
93116
{
94-
return new PsesDebugServer(_loggerFactory, inputStream, outputStream, languageServer.LanguageServer.Services, useTempSession: false, usePSReadLine);
117+
return new PsesDebugServer(
118+
_loggerFactory,
119+
inputStream,
120+
outputStream,
121+
languageServer.LanguageServer.Services,
122+
useTempSession: false,
123+
usePSReadLine);
95124
}
96125

97126
/// <summary>
98127
/// Create a new debug server based on an old one in an ended session.
99128
/// </summary>
129+
/// <remarks>
130+
/// This is only called once and that's in <see cref="EditorServicesRunner"/>.
131+
/// </remarks>
100132
/// <param name="inputStream">The protocol transport input stream.</param>
101133
/// <param name="outputStream">The protocol transport output stream.</param>
102134
/// <param name="debugServer">The old debug server to recreate.</param>
103135
/// <returns></returns>
104-
public PsesDebugServer RecreateDebugServer(Stream inputStream, Stream outputStream, PsesDebugServer debugServer, bool usePSReadLine)
136+
public PsesDebugServer RecreateDebugServer(
137+
Stream inputStream,
138+
Stream outputStream,
139+
PsesDebugServer debugServer,
140+
bool usePSReadLine)
105141
{
106-
return new PsesDebugServer(_loggerFactory, inputStream, outputStream, debugServer.ServiceProvider, useTempSession: false, usePSReadLine);
142+
return new PsesDebugServer(
143+
_loggerFactory,
144+
inputStream,
145+
outputStream,
146+
debugServer.ServiceProvider,
147+
useTempSession: false,
148+
usePSReadLine);
107149
}
108150

109151
/// <summary>
@@ -113,13 +155,16 @@ public PsesDebugServer RecreateDebugServer(Stream inputStream, Stream outputStre
113155
/// <param name="outputStream">The protocol transport output stream.</param>
114156
/// <param name="hostStartupInfo">The host startup configuration to create the server session with.</param>
115157
/// <returns></returns>
116-
public PsesDebugServer CreateDebugServerForTempSession(Stream inputStream, Stream outputStream, HostStartupInfo hostStartupInfo)
158+
public PsesDebugServer CreateDebugServerForTempSession(
159+
Stream inputStream,
160+
Stream outputStream,
161+
HostStartupInfo hostStartupInfo)
117162
{
118163
var serviceProvider = new ServiceCollection()
119164
.AddLogging(builder => builder
120165
.ClearProviders()
121166
.AddSerilog()
122-
.SetMinimumLevel(LogLevel.Trace))
167+
.SetMinimumLevel(LogLevel.Trace)) // TODO: Why randomly set to trace?
123168
.AddSingleton<ILanguageServerFacade>(provider => null)
124169
.AddPsesLanguageServices(hostStartupInfo)
125170
// For a Temp session, there is no LanguageServer so just set it to null
@@ -143,6 +188,14 @@ public PsesDebugServer CreateDebugServerForTempSession(Stream inputStream, Strea
143188
usePSReadLine: hostStartupInfo.ConsoleReplEnabled && !hostStartupInfo.UsesLegacyReadLine);
144189
}
145190

191+
/// <remarks>
192+
/// TODO: This class probably should not be <see cref="IDisposable"/> as the primary
193+
/// intention of that interface is to provide cleanup of unmanaged resources, which the
194+
/// logger certainly is not. Nor is this class used with a <see langword="using"/>. Instead,
195+
/// this class should call <see cref="Serilog.Log.CloseAndFlush()"/> in a finalizer. This
196+
/// could potentially even be done with <see
197+
/// cref="SerilogLoggerFactoryExtensions.AddSerilog"</> by passing <c>dispose=true</c>.
198+
/// </remarks>
146199
public void Dispose()
147200
{
148201
Log.CloseAndFlush();

src/PowerShellEditorServices/Hosting/HostStartupInfo.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
99
namespace Microsoft.PowerShell.EditorServices.Hosting
1010
{
1111
/// <summary>
12-
/// Contains details about the host as well as any other information
13-
/// needed by Editor Services at startup time.
12+
/// Contains details about the host as well as any other information needed by Editor Services
13+
/// at startup time.
1414
/// </summary>
15+
/// <remarks>
16+
/// TODO: Simplify this as a <see langword="record"/>.
17+
/// </remarks>
1518
public sealed class HostStartupInfo
1619
{
1720
#region Constants
@@ -97,6 +100,11 @@ public sealed class HostStartupInfo
97100
/// <summary>
98101
/// The minimum log level of log events to be logged.
99102
/// </summary>
103+
/// <remarks>
104+
/// This is cast to all of <see cref="PsesLogLevel"/>, <see
105+
/// cref="Microsoft.Extensions.Logging.LogLevel"/>, and <see
106+
/// cref="Serilog.Events.LogEventLevel"/>, hence it is an <c>int</c>.
107+
/// </remarks>
100108
public int LogLevel { get; }
101109

102110
#endregion
@@ -158,6 +166,13 @@ public HostStartupInfo(
158166
#endregion
159167
}
160168

169+
/// <summary>
170+
/// This is a strange class that is generally <c>null</c> or otherwise just has a single path
171+
/// set. It is eventually parsed one-by-one when setting up the PowerShell runspace.
172+
/// </summary>
173+
/// <remarks>
174+
/// TODO: Simplify this as a <see langword="record"/>.
175+
/// </remarks>
161176
public sealed class ProfilePathInfo
162177
{
163178
public ProfilePathInfo(

src/PowerShellEditorServices/Logging/LoggerExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public static void LogHandledException(
2828
[CallerFilePath] string callerSourceFile = null,
2929
[CallerLineNumber] int callerLineNumber = -1)
3030
{
31-
logger.LogError(message, exception);
31+
logger.LogWarning(message, exception);
3232
}
3333
}
3434
}

src/PowerShellEditorServices/Logging/PsesTelemetryEvent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
namespace Microsoft.PowerShell.EditorServices.Logging
88
{
9-
// This inheirits from Dictionary so that it can be passed in to SendTelemetryEvent()
9+
// This inherits from Dictionary so that it can be passed in to SendTelemetryEvent()
1010
// which takes in an IDictionary<string, object>
1111
// However, I wanted creation to be easy so you can do
1212
// new PsesTelemetryEvent { EventName = "eventName", Data = data }

src/PowerShellEditorServices/Server/PsesDebugServer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ public async Task StartAsync()
9999
.AddLogging()
100100
.AddOptions()
101101
.AddPsesDebugServices(ServiceProvider, this, _useTempSession))
102+
// TODO: Consider replacing all WithHandler with AddSingleton
102103
.WithHandler<LaunchAndAttachHandler>()
103104
.WithHandler<DisconnectHandler>()
104105
.WithHandler<BreakpointHandlers>()

0 commit comments

Comments
 (0)