Skip to content

Commit b230746

Browse files
committed
Add basic diagnostic logging support
This change adds basic logging support to the Editor Services console host for diagnostic output. It also includes some basic tests to verify the logging capabilities. This logging framework may be switched out in the future for a more robust solution.
1 parent 9de1f3a commit b230746

File tree

7 files changed

+348
-1
lines changed

7 files changed

+348
-1
lines changed

src/PowerShellEditorServices.Host/Program.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//
55

66
using Microsoft.PowerShell.EditorServices.Transport.Stdio;
7+
using Microsoft.PowerShell.EditorServices.Utility;
78
using System;
89
using System.Diagnostics;
910
using System.Linq;
@@ -38,11 +39,30 @@ static void Main(string[] args)
3839
}
3940
}
4041
#endif
42+
// Catch unhandled exceptions for logging purposes
43+
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
44+
45+
// Initialize the logger
46+
// TODO: Set the level based on command line parameter
47+
Logger.Initialize(minimumLogLevel: LogLevel.Verbose);
48+
Logger.Write(LogLevel.Normal, "PowerShell Editor Services Host started!");
4149

4250
// TODO: Select host, console host, and transport based on command line arguments
4351

4452
IHost host = new StdioHost();
4553
host.Start();
4654
}
55+
56+
static void CurrentDomain_UnhandledException(
57+
object sender,
58+
UnhandledExceptionEventArgs e)
59+
{
60+
// Log the exception
61+
Logger.Write(
62+
LogLevel.Error,
63+
string.Format(
64+
"FATAL UNHANDLED EXCEPTION:\r\n\r\n{0}",
65+
e.ExceptionObject.ToString()));
66+
}
4767
}
4868
}

src/PowerShellEditorServices.Transport.Stdio/Message/MessageParser.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ public MessageBase ParseMessage(string messageJson)
4545
// Parse the JSON string to a JObject
4646
JObject messageObject = JObject.Parse(messageJson);
4747

48+
// Log the message
49+
Logger.Write(
50+
LogLevel.Verbose,
51+
string.Format(
52+
"PARSE MESSAGE:\r\n\r\n{0}",
53+
messageObject.ToString(Formatting.Indented)));
54+
4855
// Get the message type and name from the JSON object
4956
if (!this.TryGetMessageTypeAndName(
5057
messageObject,

src/PowerShellEditorServices.Transport.Stdio/Message/MessageWriter.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
using Microsoft.PowerShell.EditorServices.Utility;
77
using Newtonsoft.Json;
8+
using Newtonsoft.Json.Linq;
89
using System.IO;
910
using System.Text;
1011

@@ -18,6 +19,10 @@ public class MessageWriter
1819
private bool includeContentLength;
1920
private MessageTypeResolver messageTypeResolver;
2021

22+
private JsonSerializer loggingSerializer =
23+
JsonSerializer.Create(
24+
Constants.JsonSerializerSettings);
25+
2126
#endregion
2227

2328
#region Constructors
@@ -57,8 +62,18 @@ public void WriteMessage(MessageBase messageToWrite)
5762
// Insert the message's type name before serializing
5863
messageToWrite.PayloadType = messageTypeName;
5964

65+
// Log the JSON representation of the message
66+
Logger.Write(
67+
LogLevel.Verbose,
68+
string.Format(
69+
"WRITE MESSAGE:\r\n\r\n{0}",
70+
JsonConvert.SerializeObject(
71+
messageToWrite,
72+
Formatting.Indented,
73+
Constants.JsonSerializerSettings)));
74+
6075
// Serialize the message
61-
string serializedMessage =
76+
string serializedMessage =
6277
JsonConvert.SerializeObject(
6378
messageToWrite,
6479
Constants.JsonSerializerSettings);

src/PowerShellEditorServices/PowerShellEditorServices.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
<Compile Include="Session\ScriptFileMarker.cs" />
9191
<Compile Include="Session\ScriptRegion.cs" />
9292
<Compile Include="Session\Workspace.cs" />
93+
<Compile Include="Utility\Logger.cs" />
9394
<Compile Include="Utility\Validate.cs" />
9495
</ItemGroup>
9596
<ItemGroup>
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using System;
7+
using System.IO;
8+
using System.Runtime.CompilerServices;
9+
using System.Text;
10+
11+
namespace Microsoft.PowerShell.EditorServices.Utility
12+
{
13+
/// <summary>
14+
/// Defines the level indicators for log messages.
15+
/// </summary>
16+
public enum LogLevel
17+
{
18+
/// <summary>
19+
/// Indicates a verbose log message.
20+
/// </summary>
21+
Verbose,
22+
23+
/// <summary>
24+
/// Indicates a normal, non-verbose log message.
25+
/// </summary>
26+
Normal,
27+
28+
/// <summary>
29+
/// Indicates a warning message.
30+
/// </summary>
31+
Warning,
32+
33+
/// <summary>
34+
/// Indicates an error message.
35+
/// </summary>
36+
Error
37+
}
38+
39+
/// <summary>
40+
/// Provides a simple logging interface. May be replaced with a
41+
/// more robust solution at a later date.
42+
/// </summary>
43+
public static class Logger
44+
{
45+
private static LogWriter logWriter;
46+
47+
/// <summary>
48+
/// Initializes the Logger for the current session.
49+
/// </summary>
50+
/// <param name="logFilePath">
51+
/// Optional. Specifies the path at which log messages will be written.
52+
/// </param>
53+
/// <param name="minimumLogLevel">
54+
/// Optional. Specifies the minimum log message level to write to the log file.
55+
/// </param>
56+
public static void Initialize(
57+
string logFilePath = "EditorServices.log",
58+
LogLevel minimumLogLevel = LogLevel.Normal)
59+
{
60+
if (logWriter != null)
61+
{
62+
logWriter.Dispose();
63+
}
64+
65+
// TODO: Parameterize this
66+
logWriter =
67+
new LogWriter(
68+
minimumLogLevel,
69+
logFilePath,
70+
true);
71+
}
72+
73+
/// <summary>
74+
/// Closes the Logger.
75+
/// </summary>
76+
public static void Close()
77+
{
78+
if (logWriter != null)
79+
{
80+
logWriter.Dispose();
81+
}
82+
}
83+
84+
/// <summary>
85+
/// Writes a message to the log file.
86+
/// </summary>
87+
/// <param name="logLevel">The level at which the message will be written.</param>
88+
/// <param name="logMessage">The message text to be written.</param>
89+
/// <param name="callerName">The name of the calling method.</param>
90+
/// <param name="callerSourceFile">The source file path where the calling method exists.</param>
91+
/// <param name="callerLineNumber">The line number of the calling method.</param>
92+
public static void Write(
93+
LogLevel logLevel,
94+
string logMessage,
95+
[CallerMemberName] string callerName = null,
96+
[CallerFilePath] string callerSourceFile = null,
97+
[CallerLineNumber] int callerLineNumber = 0)
98+
{
99+
if (logWriter != null)
100+
{
101+
logWriter.Write(
102+
logLevel,
103+
logMessage,
104+
callerName,
105+
callerSourceFile,
106+
callerLineNumber);
107+
}
108+
}
109+
}
110+
111+
internal class LogWriter : IDisposable
112+
{
113+
private TextWriter textWriter;
114+
private LogLevel minimumLogLevel = LogLevel.Verbose;
115+
116+
public LogWriter(LogLevel minimumLogLevel, string logFilePath, bool deleteExisting)
117+
{
118+
this.minimumLogLevel = minimumLogLevel;
119+
120+
// Ensure that we have a usable log file path
121+
if (!Path.IsPathRooted(logFilePath))
122+
{
123+
logFilePath =
124+
Path.Combine(
125+
AppDomain.CurrentDomain.BaseDirectory,
126+
logFilePath);
127+
}
128+
129+
// Open the log file for writing with UTF8 encoding
130+
this.textWriter =
131+
new StreamWriter(
132+
new FileStream(
133+
logFilePath,
134+
deleteExisting ?
135+
FileMode.Create :
136+
FileMode.Append),
137+
Encoding.UTF8);
138+
}
139+
140+
public void Write(
141+
LogLevel logLevel,
142+
string logMessage,
143+
string callerName = null,
144+
string callerSourceFile = null,
145+
int callerLineNumber = 0)
146+
{
147+
if (logLevel >= this.minimumLogLevel)
148+
{
149+
// Print the timestamp and log level
150+
this.textWriter.WriteLine(
151+
"{0} [{1}] - Method \"{2}\" at line {3} of {4}\r\n",
152+
DateTime.Now,
153+
logLevel.ToString().ToUpper(),
154+
callerName,
155+
callerLineNumber,
156+
callerSourceFile);
157+
158+
// Print out indented message lines
159+
foreach (var messageLine in logMessage.Split('\n'))
160+
{
161+
this.textWriter.WriteLine(" " + messageLine.TrimEnd());
162+
}
163+
164+
// Finish with a newline and flush the writer
165+
this.textWriter.WriteLine();
166+
this.textWriter.Flush();
167+
}
168+
}
169+
170+
public void Dispose()
171+
{
172+
if (this.textWriter != null)
173+
{
174+
this.textWriter.Flush();
175+
this.textWriter.Dispose();
176+
this.textWriter = null;
177+
}
178+
}
179+
}
180+
}

test/PowerShellEditorServices.Test/PowerShellEditorServices.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
<Compile Include="Properties\AssemblyInfo.cs" />
6565
<Compile Include="Console\ConsoleServiceTests.cs" />
6666
<Compile Include="Session\ScriptFileTests.cs" />
67+
<Compile Include="Utility\LoggerTests.cs" />
6768
</ItemGroup>
6869
<ItemGroup>
6970
<None Include="App.config" />

0 commit comments

Comments
 (0)