Support stderr in console logger (#913)

This commit is contained in:
Pavel Krymets 2018-11-05 11:33:41 -08:00 коммит произвёл GitHub
Родитель 64724ea6e4
Коммит b454c5a67e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 126 добавлений и 13 удалений

Просмотреть файл

@ -8,7 +8,8 @@
"Console":
{
"IncludeScopes": "true",
"TimestampFormat": "[HH:mm:ss] "
"TimestampFormat": "[HH:mm:ss] ",
"LogToStandardErrorThreshold": "Warning"
}
}
}

Просмотреть файл

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Extensions.Logging.Abstractions.Internal;
@ -51,15 +52,18 @@ namespace Microsoft.Extensions.Logging.Console
Name = name;
Filter = filter ?? ((category, logLevel) => true);
ScopeProvider = scopeProvider;
LogToStandardErrorThreshold = LogLevel.None;
_queueProcessor = loggerProcessor;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Console = new WindowsLogConsole();
ErrorConsole = new WindowsLogConsole(stdErr: true);
}
else
{
Console = new AnsiLogConsole(new AnsiSystemConsole());
ErrorConsole = new AnsiLogConsole(new AnsiSystemConsole(stdErr: true));
}
}
@ -77,6 +81,20 @@ namespace Microsoft.Extensions.Logging.Console
}
}
internal IConsole ErrorConsole
{
get { return _queueProcessor.ErrorConsole; }
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_queueProcessor.ErrorConsole = value;
}
}
public Func<string, LogLevel, bool> Filter
{
get { return _filter; }
@ -100,6 +118,8 @@ namespace Microsoft.Extensions.Logging.Console
public bool DisableColors { get; set; }
internal LogLevel LogToStandardErrorThreshold { get; set; }
internal string TimestampFormat { get; set; }
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
@ -180,7 +200,8 @@ namespace Microsoft.Extensions.Logging.Console
MessageColor = DefaultConsoleColor,
LevelString = hasLevel ? logLevelString : null,
LevelBackground = hasLevel ? logLevelColors.Background : null,
LevelForeground = hasLevel ? logLevelColors.Foreground : null
LevelForeground = hasLevel ? logLevelColors.Foreground : null,
LogAsError = logLevel >= LogToStandardErrorThreshold
});
logBuilder.Clear();
@ -289,14 +310,23 @@ namespace Microsoft.Extensions.Logging.Console
private class AnsiSystemConsole : IAnsiSystemConsole
{
private readonly TextWriter _textWriter;
/// <inheritdoc />
public AnsiSystemConsole(bool stdErr = false)
{
_textWriter = stdErr? System.Console.Error : System.Console.Out;
}
public void Write(string message)
{
System.Console.Write(message);
_textWriter.Write(message);
}
public void WriteLine(string message)
{
System.Console.WriteLine(message);
_textWriter.WriteLine(message);
}
}
}

Просмотреть файл

@ -8,6 +8,11 @@ namespace Microsoft.Extensions.Logging.Console
public bool IncludeScopes { get; set; }
public bool DisableColors { get; set; }
/// <summary>
/// Gets or sets value indicating the minimum level of messaged that would get written to <c>Console.Error</c>.
/// </summary>
public LogLevel LogToStandardErrorThreshold { get; set; } = LogLevel.None;
/// <summary>
/// Gets or sets format string used to format timestamp in logging messages. Defaults to <c>null</c>
/// </summary>

Просмотреть файл

@ -25,6 +25,7 @@ namespace Microsoft.Extensions.Logging.Console
private bool _disableColors;
private IExternalScopeProvider _scopeProvider;
private string _timestampFormat;
private LogLevel _logToStandardErrorThreshold;
public ConsoleLoggerProvider(Func<string, LogLevel, bool> filter, bool includeScopes)
: this(filter, includeScopes, false)
@ -56,12 +57,14 @@ namespace Microsoft.Extensions.Logging.Console
_includeScopes = options.IncludeScopes;
_disableColors = options.DisableColors;
_timestampFormat = options.TimestampFormat;
_logToStandardErrorThreshold = options.LogToStandardErrorThreshold;
var scopeProvider = GetScopeProvider();
foreach (var logger in _loggers.Values)
{
logger.ScopeProvider = scopeProvider;
logger.DisableColors = options.DisableColors;
logger.TimestampFormat = options.TimestampFormat;
logger.LogToStandardErrorThreshold = options.LogToStandardErrorThreshold;
}
}
@ -124,7 +127,8 @@ namespace Microsoft.Extensions.Logging.Console
return new ConsoleLogger(name, GetFilter(name, _settings), includeScopes? _scopeProvider: null, _messageQueue)
{
DisableColors = disableColors,
TimestampFormat = _timestampFormat
TimestampFormat = _timestampFormat,
LogToStandardErrorThreshold = _logToStandardErrorThreshold
};
}

Просмотреть файл

@ -15,6 +15,7 @@ namespace Microsoft.Extensions.Logging.Console.Internal
private readonly Thread _outputThread;
public IConsole Console;
public IConsole ErrorConsole;
public ConsoleLoggerProcessor()
{
@ -46,18 +47,20 @@ namespace Microsoft.Extensions.Logging.Console.Internal
// for testing
internal virtual void WriteMessage(LogMessageEntry message)
{
var console = message.LogAsError ? ErrorConsole : Console;
if (message.TimeStamp != null)
{
Console.Write(message.TimeStamp, message.MessageColor, message.MessageColor);
console.Write(message.TimeStamp, message.MessageColor, message.MessageColor);
}
if (message.LevelString != null)
{
Console.Write(message.LevelString, message.LevelBackground, message.LevelForeground);
console.Write(message.LevelString, message.LevelBackground, message.LevelForeground);
}
Console.Write(message.Message, message.MessageColor, message.MessageColor);
Console.Flush();
console.Write(message.Message, message.MessageColor, message.MessageColor);
console.Flush();
}
private void ProcessLogQueue()

Просмотреть файл

@ -13,5 +13,6 @@ namespace Microsoft.Extensions.Logging.Console.Internal
public ConsoleColor? LevelForeground;
public ConsoleColor? MessageColor;
public string Message;
public bool LogAsError;
}
}

Просмотреть файл

@ -2,11 +2,20 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
namespace Microsoft.Extensions.Logging.Console.Internal
{
public class WindowsLogConsole : IConsole
{
private readonly TextWriter _textWriter;
/// <inheritdoc />
public WindowsLogConsole(bool stdErr = false)
{
_textWriter = stdErr? System.Console.Error : System.Console.Out;
}
private bool SetColor(ConsoleColor? background, ConsoleColor? foreground)
{
if (background.HasValue)
@ -30,7 +39,7 @@ namespace Microsoft.Extensions.Logging.Console.Internal
public void Write(string message, ConsoleColor? background, ConsoleColor? foreground)
{
var colorChanged = SetColor(background, foreground);
System.Console.Out.Write(message);
_textWriter.Write(message);
if (colorChanged)
{
ResetColor();
@ -40,7 +49,7 @@ namespace Microsoft.Extensions.Logging.Console.Internal
public void WriteLine(string message, ConsoleColor? background, ConsoleColor? foreground)
{
var colorChanged = SetColor(background, foreground);
System.Console.Out.WriteLine(message);
_textWriter.WriteLine(message);
if (colorChanged)
{
ResetColor();

Просмотреть файл

@ -27,15 +27,18 @@ namespace Microsoft.Extensions.Logging.Test
private const string _state = "This is a test, and {curly braces} are just fine!";
private Func<object, Exception, string> _defaultFormatter = (state, exception) => state.ToString();
private static (ConsoleLogger Logger, ConsoleSink Sink) SetUp(Func<string, LogLevel, bool> filter, bool includeScopes = false, bool disableColors = false)
private static (ConsoleLogger Logger, ConsoleSink Sink, ConsoleSink ErrorSink) SetUp(Func<string, LogLevel, bool> filter, bool includeScopes = false, bool disableColors = false)
{
// Arrange
var sink = new ConsoleSink();
var errorSink = new ConsoleSink();
var console = new TestConsole(sink);
var errorConsole = new TestConsole(errorSink);
var logger = new ConsoleLogger(_loggerName, filter, includeScopes ? new LoggerExternalScopeProvider() : null, new TestLoggerProcessor());
logger.Console = console;
logger.ErrorConsole = errorConsole;
logger.DisableColors = disableColors;
return (logger, sink);
return (logger, sink, errorSink);
}
public ConsoleLoggerTest()
@ -845,6 +848,32 @@ namespace Microsoft.Extensions.Logging.Test
Assert.True(logger.DisableColors);
}
[Fact]
public void ConsoleLoggerLogsToError_WhenOverErrorLevel()
{
// Arrange
var (logger, sink, errorSink) = SetUp(null);
logger.LogToStandardErrorThreshold = LogLevel.Warning;
// Act
logger.LogInformation("Info");
logger.LogWarning("Warn");
// Assert
Assert.Equal(2, sink.Writes.Count);
Assert.Equal(
"info: test[0]" + Environment.NewLine +
" Info" + Environment.NewLine,
GetMessage(sink.Writes));
Assert.Equal(2, errorSink.Writes.Count);
Assert.Equal(
"warn: test[0]" + Environment.NewLine +
" Warn" + Environment.NewLine,
GetMessage(errorSink.Writes));
}
[Theory]
[MemberData(nameof(LevelsWithPrefixes))]
public void WriteCore_NullMessageWithException(LogLevel level, string prefix)
@ -1049,6 +1078,37 @@ namespace Microsoft.Extensions.Logging.Test
Assert.Null(logger.ScopeProvider);
}
[Fact]
public void ConsoleLoggerOptions_LogAsErrorLevel_IsReadFromLoggingConfiguration()
{
var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] { new KeyValuePair<string, string>("Console:LogToStandardErrorThreshold", "Warning") }).Build();
var loggerProvider = new ServiceCollection()
.AddLogging(builder => builder
.AddConfiguration(configuration)
.AddConsole())
.BuildServiceProvider()
.GetRequiredService<ILoggerProvider>();
var consoleLoggerProvider = Assert.IsType<ConsoleLoggerProvider>(loggerProvider);
var logger = (ConsoleLogger)consoleLoggerProvider.CreateLogger("Category");
Assert.Equal(LogLevel.Warning, logger.LogToStandardErrorThreshold);
}
[Fact]
public void ConsoleLoggerOptions_LogAsErrorLevel_IsAppliedToLoggers()
{
// Arrange
var monitor = new TestOptionsMonitor(new ConsoleLoggerOptions());
var loggerProvider = new ConsoleLoggerProvider(monitor);
var logger = (ConsoleLogger)loggerProvider.CreateLogger("Name");
// Act & Assert
Assert.Equal(LogLevel.None, logger.LogToStandardErrorThreshold);
monitor.Set(new ConsoleLoggerOptions() { LogToStandardErrorThreshold = LogLevel.Error});
Assert.Equal(LogLevel.Error, logger.LogToStandardErrorThreshold);
}
[Fact]
public void ConsoleLoggerOptions_IncludeScopes_IsReadFromLoggingConfiguration()
{