Skip to content

Commit 26077c0

Browse files
committed
Merge pull request #19 from PowerShell/daviwil/add-logging
Add basic diagnostic logging support
2 parents 200aaf6 + b230746 commit 26077c0

File tree

7 files changed

+348
-1
lines changed

7 files changed

+348
-1
lines changed

src/PowerShellEditorServices.Host/Program.cs

+20
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

+7
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

+16-1
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

+1
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>
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

+1
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)