Skip to content
This repository was archived by the owner on Dec 13, 2018. It is now read-only.

Commit b454c5a

Browse files
authored
Support stderr in console logger (#913)
1 parent 64724ea commit b454c5a

File tree

8 files changed

+126
-13
lines changed

8 files changed

+126
-13
lines changed

samples/SampleApp/logging.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"Console":
99
{
1010
"IncludeScopes": "true",
11-
"TimestampFormat": "[HH:mm:ss] "
11+
"TimestampFormat": "[HH:mm:ss] ",
12+
"LogToStandardErrorThreshold": "Warning"
1213
}
1314
}
1415
}

src/Microsoft.Extensions.Logging.Console/ConsoleLogger.cs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.IO;
56
using System.Runtime.InteropServices;
67
using System.Text;
78
using Microsoft.Extensions.Logging.Abstractions.Internal;
@@ -51,15 +52,18 @@ internal ConsoleLogger(string name, Func<string, LogLevel, bool> filter, IExtern
5152
Name = name;
5253
Filter = filter ?? ((category, logLevel) => true);
5354
ScopeProvider = scopeProvider;
55+
LogToStandardErrorThreshold = LogLevel.None;
5456
_queueProcessor = loggerProcessor;
5557

5658
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
5759
{
5860
Console = new WindowsLogConsole();
61+
ErrorConsole = new WindowsLogConsole(stdErr: true);
5962
}
6063
else
6164
{
6265
Console = new AnsiLogConsole(new AnsiSystemConsole());
66+
ErrorConsole = new AnsiLogConsole(new AnsiSystemConsole(stdErr: true));
6367
}
6468
}
6569

@@ -77,6 +81,20 @@ public IConsole Console
7781
}
7882
}
7983

84+
internal IConsole ErrorConsole
85+
{
86+
get { return _queueProcessor.ErrorConsole; }
87+
set
88+
{
89+
if (value == null)
90+
{
91+
throw new ArgumentNullException(nameof(value));
92+
}
93+
94+
_queueProcessor.ErrorConsole = value;
95+
}
96+
}
97+
8098
public Func<string, LogLevel, bool> Filter
8199
{
82100
get { return _filter; }
@@ -100,6 +118,8 @@ public Func<string, LogLevel, bool> Filter
100118

101119
public bool DisableColors { get; set; }
102120

121+
internal LogLevel LogToStandardErrorThreshold { get; set; }
122+
103123
internal string TimestampFormat { get; set; }
104124

105125
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
@@ -180,7 +200,8 @@ public virtual void WriteMessage(LogLevel logLevel, string logName, int eventId,
180200
MessageColor = DefaultConsoleColor,
181201
LevelString = hasLevel ? logLevelString : null,
182202
LevelBackground = hasLevel ? logLevelColors.Background : null,
183-
LevelForeground = hasLevel ? logLevelColors.Foreground : null
203+
LevelForeground = hasLevel ? logLevelColors.Foreground : null,
204+
LogAsError = logLevel >= LogToStandardErrorThreshold
184205
});
185206

186207
logBuilder.Clear();
@@ -289,14 +310,23 @@ public ConsoleColors(ConsoleColor? foreground, ConsoleColor? background)
289310

290311
private class AnsiSystemConsole : IAnsiSystemConsole
291312
{
313+
314+
private readonly TextWriter _textWriter;
315+
316+
/// <inheritdoc />
317+
public AnsiSystemConsole(bool stdErr = false)
318+
{
319+
_textWriter = stdErr? System.Console.Error : System.Console.Out;
320+
}
321+
292322
public void Write(string message)
293323
{
294-
System.Console.Write(message);
324+
_textWriter.Write(message);
295325
}
296326

297327
public void WriteLine(string message)
298328
{
299-
System.Console.WriteLine(message);
329+
_textWriter.WriteLine(message);
300330
}
301331
}
302332
}

src/Microsoft.Extensions.Logging.Console/ConsoleLoggerOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ public class ConsoleLoggerOptions
88
public bool IncludeScopes { get; set; }
99
public bool DisableColors { get; set; }
1010

11+
/// <summary>
12+
/// Gets or sets value indicating the minimum level of messaged that would get written to <c>Console.Error</c>.
13+
/// </summary>
14+
public LogLevel LogToStandardErrorThreshold { get; set; } = LogLevel.None;
15+
1116
/// <summary>
1217
/// Gets or sets format string used to format timestamp in logging messages. Defaults to <c>null</c>
1318
/// </summary>

src/Microsoft.Extensions.Logging.Console/ConsoleLoggerProvider.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class ConsoleLoggerProvider : ILoggerProvider, ISupportExternalScope
2525
private bool _disableColors;
2626
private IExternalScopeProvider _scopeProvider;
2727
private string _timestampFormat;
28+
private LogLevel _logToStandardErrorThreshold;
2829

2930
public ConsoleLoggerProvider(Func<string, LogLevel, bool> filter, bool includeScopes)
3031
: this(filter, includeScopes, false)
@@ -56,12 +57,14 @@ private void ReloadLoggerOptions(ConsoleLoggerOptions options)
5657
_includeScopes = options.IncludeScopes;
5758
_disableColors = options.DisableColors;
5859
_timestampFormat = options.TimestampFormat;
60+
_logToStandardErrorThreshold = options.LogToStandardErrorThreshold;
5961
var scopeProvider = GetScopeProvider();
6062
foreach (var logger in _loggers.Values)
6163
{
6264
logger.ScopeProvider = scopeProvider;
6365
logger.DisableColors = options.DisableColors;
6466
logger.TimestampFormat = options.TimestampFormat;
67+
logger.LogToStandardErrorThreshold = options.LogToStandardErrorThreshold;
6568
}
6669
}
6770

@@ -124,7 +127,8 @@ private ConsoleLogger CreateLoggerImplementation(string name)
124127
return new ConsoleLogger(name, GetFilter(name, _settings), includeScopes? _scopeProvider: null, _messageQueue)
125128
{
126129
DisableColors = disableColors,
127-
TimestampFormat = _timestampFormat
130+
TimestampFormat = _timestampFormat,
131+
LogToStandardErrorThreshold = _logToStandardErrorThreshold
128132
};
129133
}
130134

src/Microsoft.Extensions.Logging.Console/Internal/ConsoleLoggerProcessor.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public class ConsoleLoggerProcessor : IDisposable
1515
private readonly Thread _outputThread;
1616

1717
public IConsole Console;
18+
public IConsole ErrorConsole;
1819

1920
public ConsoleLoggerProcessor()
2021
{
@@ -46,18 +47,20 @@ public virtual void EnqueueMessage(LogMessageEntry message)
4647
// for testing
4748
internal virtual void WriteMessage(LogMessageEntry message)
4849
{
50+
var console = message.LogAsError ? ErrorConsole : Console;
51+
4952
if (message.TimeStamp != null)
5053
{
51-
Console.Write(message.TimeStamp, message.MessageColor, message.MessageColor);
54+
console.Write(message.TimeStamp, message.MessageColor, message.MessageColor);
5255
}
5356

5457
if (message.LevelString != null)
5558
{
56-
Console.Write(message.LevelString, message.LevelBackground, message.LevelForeground);
59+
console.Write(message.LevelString, message.LevelBackground, message.LevelForeground);
5760
}
5861

59-
Console.Write(message.Message, message.MessageColor, message.MessageColor);
60-
Console.Flush();
62+
console.Write(message.Message, message.MessageColor, message.MessageColor);
63+
console.Flush();
6164
}
6265

6366
private void ProcessLogQueue()

src/Microsoft.Extensions.Logging.Console/Internal/LogMessageEntry.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ public struct LogMessageEntry
1313
public ConsoleColor? LevelForeground;
1414
public ConsoleColor? MessageColor;
1515
public string Message;
16+
public bool LogAsError;
1617
}
1718
}

src/Microsoft.Extensions.Logging.Console/Internal/WindowsLogConsole.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,20 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.IO;
56

67
namespace Microsoft.Extensions.Logging.Console.Internal
78
{
89
public class WindowsLogConsole : IConsole
910
{
11+
private readonly TextWriter _textWriter;
12+
13+
/// <inheritdoc />
14+
public WindowsLogConsole(bool stdErr = false)
15+
{
16+
_textWriter = stdErr? System.Console.Error : System.Console.Out;
17+
}
18+
1019
private bool SetColor(ConsoleColor? background, ConsoleColor? foreground)
1120
{
1221
if (background.HasValue)
@@ -30,7 +39,7 @@ private void ResetColor()
3039
public void Write(string message, ConsoleColor? background, ConsoleColor? foreground)
3140
{
3241
var colorChanged = SetColor(background, foreground);
33-
System.Console.Out.Write(message);
42+
_textWriter.Write(message);
3443
if (colorChanged)
3544
{
3645
ResetColor();
@@ -40,7 +49,7 @@ public void Write(string message, ConsoleColor? background, ConsoleColor? foregr
4049
public void WriteLine(string message, ConsoleColor? background, ConsoleColor? foreground)
4150
{
4251
var colorChanged = SetColor(background, foreground);
43-
System.Console.Out.WriteLine(message);
52+
_textWriter.WriteLine(message);
4453
if (colorChanged)
4554
{
4655
ResetColor();

test/Microsoft.Extensions.Logging.Test/ConsoleLoggerTest.cs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,18 @@ public class ConsoleLoggerTest
2727
private const string _state = "This is a test, and {curly braces} are just fine!";
2828
private Func<object, Exception, string> _defaultFormatter = (state, exception) => state.ToString();
2929

30-
private static (ConsoleLogger Logger, ConsoleSink Sink) SetUp(Func<string, LogLevel, bool> filter, bool includeScopes = false, bool disableColors = false)
30+
private static (ConsoleLogger Logger, ConsoleSink Sink, ConsoleSink ErrorSink) SetUp(Func<string, LogLevel, bool> filter, bool includeScopes = false, bool disableColors = false)
3131
{
3232
// Arrange
3333
var sink = new ConsoleSink();
34+
var errorSink = new ConsoleSink();
3435
var console = new TestConsole(sink);
36+
var errorConsole = new TestConsole(errorSink);
3537
var logger = new ConsoleLogger(_loggerName, filter, includeScopes ? new LoggerExternalScopeProvider() : null, new TestLoggerProcessor());
3638
logger.Console = console;
39+
logger.ErrorConsole = errorConsole;
3740
logger.DisableColors = disableColors;
38-
return (logger, sink);
41+
return (logger, sink, errorSink);
3942
}
4043

4144
public ConsoleLoggerTest()
@@ -845,6 +848,32 @@ public void ConsoleLogger_Settings_DisableColors()
845848
Assert.True(logger.DisableColors);
846849
}
847850

851+
[Fact]
852+
public void ConsoleLoggerLogsToError_WhenOverErrorLevel()
853+
{
854+
// Arrange
855+
var (logger, sink, errorSink) = SetUp(null);
856+
857+
logger.LogToStandardErrorThreshold = LogLevel.Warning;
858+
859+
// Act
860+
logger.LogInformation("Info");
861+
logger.LogWarning("Warn");
862+
863+
// Assert
864+
Assert.Equal(2, sink.Writes.Count);
865+
Assert.Equal(
866+
"info: test[0]" + Environment.NewLine +
867+
" Info" + Environment.NewLine,
868+
GetMessage(sink.Writes));
869+
870+
Assert.Equal(2, errorSink.Writes.Count);
871+
Assert.Equal(
872+
"warn: test[0]" + Environment.NewLine +
873+
" Warn" + Environment.NewLine,
874+
GetMessage(errorSink.Writes));
875+
}
876+
848877
[Theory]
849878
[MemberData(nameof(LevelsWithPrefixes))]
850879
public void WriteCore_NullMessageWithException(LogLevel level, string prefix)
@@ -1049,6 +1078,37 @@ public void ConsoleLoggerOptions_IncludeScopes_IsAppliedToLoggers()
10491078
Assert.Null(logger.ScopeProvider);
10501079
}
10511080

1081+
[Fact]
1082+
public void ConsoleLoggerOptions_LogAsErrorLevel_IsReadFromLoggingConfiguration()
1083+
{
1084+
var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] { new KeyValuePair<string, string>("Console:LogToStandardErrorThreshold", "Warning") }).Build();
1085+
1086+
var loggerProvider = new ServiceCollection()
1087+
.AddLogging(builder => builder
1088+
.AddConfiguration(configuration)
1089+
.AddConsole())
1090+
.BuildServiceProvider()
1091+
.GetRequiredService<ILoggerProvider>();
1092+
1093+
var consoleLoggerProvider = Assert.IsType<ConsoleLoggerProvider>(loggerProvider);
1094+
var logger = (ConsoleLogger)consoleLoggerProvider.CreateLogger("Category");
1095+
Assert.Equal(LogLevel.Warning, logger.LogToStandardErrorThreshold);
1096+
}
1097+
1098+
[Fact]
1099+
public void ConsoleLoggerOptions_LogAsErrorLevel_IsAppliedToLoggers()
1100+
{
1101+
// Arrange
1102+
var monitor = new TestOptionsMonitor(new ConsoleLoggerOptions());
1103+
var loggerProvider = new ConsoleLoggerProvider(monitor);
1104+
var logger = (ConsoleLogger)loggerProvider.CreateLogger("Name");
1105+
1106+
// Act & Assert
1107+
Assert.Equal(LogLevel.None, logger.LogToStandardErrorThreshold);
1108+
monitor.Set(new ConsoleLoggerOptions() { LogToStandardErrorThreshold = LogLevel.Error});
1109+
Assert.Equal(LogLevel.Error, logger.LogToStandardErrorThreshold);
1110+
}
1111+
10521112
[Fact]
10531113
public void ConsoleLoggerOptions_IncludeScopes_IsReadFromLoggingConfiguration()
10541114
{

0 commit comments

Comments
 (0)