Skip to content

Commit 394b274

Browse files
committed
Add the ILogger interface and FileLogger implementation
This change introduces the ILogger interface to abstract writing log messages to an arbitrary sink so that this interface can be passed to other components for logging behavior. It also adds the FileLogger implementation which is a refactored LogWriter implementation.
1 parent 15d6314 commit 394b274

File tree

7 files changed

+276
-174
lines changed

7 files changed

+276
-174
lines changed

src/PowerShellEditorServices.Host/EditorServicesHost.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public class EditorServicesHost
3434
{
3535
#region Private Fields
3636

37+
private ILogger logger;
3738
private bool enableConsoleRepl;
3839
private HostDetails hostDetails;
3940
private ProfilePaths profilePaths;
@@ -114,7 +115,8 @@ public EditorServicesHost(
114115
/// <param name="logLevel">The minimum level of log messages to be written.</param>
115116
public void StartLogging(string logFilePath, LogLevel logLevel)
116117
{
117-
Logger.Initialize(logFilePath, logLevel);
118+
this.logger = new FileLogger(logFilePath, logLevel);
119+
Logger.Initialize(this.logger);
118120

119121
#if CoreCLR
120122
FileVersionInfo fileVersionInfo =
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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+
using System.Threading;
11+
12+
namespace Microsoft.PowerShell.EditorServices.Utility
13+
{
14+
/// <summary>
15+
/// Provides an implementation of ILogger for writing messages to
16+
/// a log file on disk.
17+
/// </summary>
18+
public class FileLogger : ILogger, IDisposable
19+
{
20+
private TextWriter textWriter;
21+
private LogLevel minimumLogLevel = LogLevel.Verbose;
22+
23+
/// <summary>
24+
/// Creates an ILogger implementation that writes to the specified file.
25+
/// </summary>
26+
/// <param name="logFilePath">
27+
/// Specifies the path at which log messages will be written.
28+
/// </param>
29+
/// <param name="minimumLogLevel">
30+
/// Specifies the minimum log message level to write to the log file.
31+
/// </param>
32+
public FileLogger(string logFilePath, LogLevel minimumLogLevel)
33+
{
34+
this.minimumLogLevel = minimumLogLevel;
35+
36+
// Ensure that we have a usable log file path
37+
if (!Path.IsPathRooted(logFilePath))
38+
{
39+
logFilePath =
40+
Path.Combine(
41+
#if CoreCLR
42+
AppContext.BaseDirectory,
43+
#else
44+
AppDomain.CurrentDomain.BaseDirectory,
45+
#endif
46+
logFilePath);
47+
}
48+
49+
if (!this.TryOpenLogFile(logFilePath))
50+
{
51+
// If the log file couldn't be opened at this location,
52+
// try opening it in a more reliable path
53+
this.TryOpenLogFile(
54+
Path.Combine(
55+
#if CoreCLR
56+
Environment.GetEnvironmentVariable("TEMP"),
57+
#else
58+
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
59+
#endif
60+
Path.GetFileName(logFilePath)));
61+
}
62+
}
63+
64+
/// <summary>
65+
/// Writes a message to the log file.
66+
/// </summary>
67+
/// <param name="logLevel">The level at which the message will be written.</param>
68+
/// <param name="logMessage">The message text to be written.</param>
69+
/// <param name="callerName">The name of the calling method.</param>
70+
/// <param name="callerSourceFile">The source file path where the calling method exists.</param>
71+
/// <param name="callerLineNumber">The line number of the calling method.</param>
72+
public void Write(
73+
LogLevel logLevel,
74+
string logMessage,
75+
string callerName = null,
76+
string callerSourceFile = null,
77+
int callerLineNumber = 0)
78+
{
79+
if (this.textWriter != null &&
80+
logLevel >= this.minimumLogLevel)
81+
{
82+
// Print the timestamp and log level
83+
this.textWriter.WriteLine(
84+
"{0} [{1}] - Method \"{2}\" at line {3} of {4}\r\n",
85+
DateTime.Now,
86+
logLevel.ToString().ToUpper(),
87+
callerName,
88+
callerLineNumber,
89+
callerSourceFile);
90+
91+
// Print out indented message lines
92+
foreach (var messageLine in logMessage.Split('\n'))
93+
{
94+
this.textWriter.WriteLine(" " + messageLine.TrimEnd());
95+
}
96+
97+
// Finish with a newline and flush the writer
98+
this.textWriter.WriteLine();
99+
this.textWriter.Flush();
100+
}
101+
}
102+
103+
/// <summary>
104+
/// Writes an error message and exception to the log file.
105+
/// </summary>
106+
/// <param name="errorMessage">The error message text to be written.</param>
107+
/// <param name="errorException">The exception to be written..</param>
108+
/// <param name="callerName">The name of the calling method.</param>
109+
/// <param name="callerSourceFile">The source file path where the calling method exists.</param>
110+
/// <param name="callerLineNumber">The line number of the calling method.</param>
111+
public void WriteException(
112+
string errorMessage,
113+
Exception errorException,
114+
[CallerMemberName] string callerName = null,
115+
[CallerFilePath] string callerSourceFile = null,
116+
[CallerLineNumber] int callerLineNumber = 0)
117+
{
118+
this.Write(
119+
LogLevel.Error,
120+
$"{errorMessage}\r\n\r\n{errorException.ToString()}",
121+
callerName,
122+
callerSourceFile,
123+
callerLineNumber);
124+
}
125+
126+
/// <summary>
127+
/// Flushes any remaining log write and closes the log file.
128+
/// </summary>
129+
public void Dispose()
130+
{
131+
if (this.textWriter != null)
132+
{
133+
this.textWriter.Flush();
134+
this.textWriter.Dispose();
135+
this.textWriter = null;
136+
}
137+
}
138+
139+
private bool TryOpenLogFile(string logFilePath)
140+
{
141+
try
142+
{
143+
// Make sure the log directory exists
144+
Directory.CreateDirectory(
145+
Path.GetDirectoryName(
146+
logFilePath));
147+
148+
// Open the log file for writing with UTF8 encoding
149+
this.textWriter =
150+
new StreamWriter(
151+
new FileStream(
152+
logFilePath,
153+
FileMode.Create),
154+
Encoding.UTF8);
155+
156+
return true;
157+
}
158+
catch (Exception e)
159+
{
160+
if (e is UnauthorizedAccessException ||
161+
e is IOException)
162+
{
163+
// This exception is thrown when we can't open the file
164+
// at the path in logFilePath. Return false to indicate
165+
// that the log file couldn't be created.
166+
return false;
167+
}
168+
169+
// Unexpected exception, rethrow it
170+
throw;
171+
}
172+
}
173+
}
174+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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+
using System.Threading;
11+
12+
namespace Microsoft.PowerShell.EditorServices.Utility
13+
{
14+
/// <summary>
15+
/// Defines the level indicators for log messages.
16+
/// </summary>
17+
public enum LogLevel
18+
{
19+
/// <summary>
20+
/// Indicates a verbose log message.
21+
/// </summary>
22+
Verbose,
23+
24+
/// <summary>
25+
/// Indicates a normal, non-verbose log message.
26+
/// </summary>
27+
Normal,
28+
29+
/// <summary>
30+
/// Indicates a warning message.
31+
/// </summary>
32+
Warning,
33+
34+
/// <summary>
35+
/// Indicates an error message.
36+
/// </summary>
37+
Error
38+
}
39+
40+
/// <summary>
41+
/// Defines an interface for writing messages to a logging implementation.
42+
/// </summary>
43+
public interface ILogger : IDisposable
44+
{
45+
/// <summary>
46+
/// Writes a message to the log file.
47+
/// </summary>
48+
/// <param name="logLevel">The level at which the message will be written.</param>
49+
/// <param name="logMessage">The message text to be written.</param>
50+
/// <param name="callerName">The name of the calling method.</param>
51+
/// <param name="callerSourceFile">The source file path where the calling method exists.</param>
52+
/// <param name="callerLineNumber">The line number of the calling method.</param>
53+
void Write(
54+
LogLevel logLevel,
55+
string logMessage,
56+
[CallerMemberName] string callerName = null,
57+
[CallerFilePath] string callerSourceFile = null,
58+
[CallerLineNumber] int callerLineNumber = 0);
59+
60+
/// <summary>
61+
/// Writes an error message and exception to the log file.
62+
/// </summary>
63+
/// <param name="errorMessage">The error message text to be written.</param>
64+
/// <param name="errorException">The exception to be written..</param>
65+
/// <param name="callerName">The name of the calling method.</param>
66+
/// <param name="callerSourceFile">The source file path where the calling method exists.</param>
67+
/// <param name="callerLineNumber">The line number of the calling method.</param>
68+
void WriteException(
69+
string errorMessage,
70+
Exception errorException,
71+
[CallerMemberName] string callerName = null,
72+
[CallerFilePath] string callerSourceFile = null,
73+
[CallerLineNumber] int callerLineNumber = 0);
74+
}
75+
}

0 commit comments

Comments
 (0)