internal static extern bool SetConsoleMode(IntPtr handle, int mode);
internal const int ENABLE_PROCESSED_INPUT = 0x0001;
+ internal const uint ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004;
+ internal const int STD_OUTPUT_HANDLE = -11;
}
}
using System.Buffers;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
Debug.Assert(_rentedBuffer.Length - _index >= sizeHint);
}
}
+
+ internal static partial class ThrowHelper
+ {
+ [DoesNotReturn]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowOutOfMemoryException_BufferMaximumSizeExceeded(uint capacity)
+ {
+ throw new OutOfMemoryException(SR.Format(SR.BufferMaximumSizeExceeded, capacity));
+ }
+ }
}
}
namespace Microsoft.Extensions.Logging.Abstractions
{
+ public readonly partial struct LogEntry<TState>
+ {
+ private readonly TState _State_k__BackingField;
+ private readonly object _dummy;
+ private readonly int _dummyPrimitive;
+ public LogEntry(Microsoft.Extensions.Logging.LogLevel logLevel, string category, Microsoft.Extensions.Logging.EventId eventId, TState state, System.Exception exception, System.Func<TState, System.Exception, string> formatter) { throw null; }
+ public string Category { get { throw null; } }
+ public Microsoft.Extensions.Logging.EventId EventId { get { throw null; } }
+ public System.Exception Exception { get { throw null; } }
+ public System.Func<TState, System.Exception, string> Formatter { get { throw null; } }
+ public Microsoft.Extensions.Logging.LogLevel LogLevel { get { throw null; } }
+ public TState State { get { throw null; } }
+ }
public partial class NullLogger : Microsoft.Extensions.Logging.ILogger
{
internal NullLogger() { }
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace Microsoft.Extensions.Logging.Abstractions
+{
+ /// <summary>
+ /// Holds the information for a single log entry.
+ /// </summary>
+ public readonly struct LogEntry<TState>
+ {
+ /// <summary>
+ /// Initializes an instance of the LogEntry struct.
+ /// </summary>
+ /// <param name="logLevel">The log level.</param>
+ /// <param name="category">The category name for the log.</param>
+ /// <param name="eventId">The log event Id.</param>
+ /// <param name="state">The state for which log is being written.</param>
+ /// <param name="exception">The log exception.</param>
+ /// <param name="formatter">The formatter.</param>
+ public LogEntry(LogLevel logLevel, string category, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
+ {
+ LogLevel = logLevel;
+ Category = category;
+ EventId = eventId;
+ State = state;
+ Exception = exception;
+ Formatter = formatter;
+ }
+
+ /// <summary>
+ /// Gets the LogLevel
+ /// </summary>
+ public LogLevel LogLevel { get; }
+
+ /// <summary>
+ /// Gets the log category
+ /// </summary>
+ public string Category { get; }
+
+ /// <summary>
+ /// Gets the log EventId
+ /// </summary>
+ public EventId EventId { get; }
+
+ /// <summary>
+ /// Gets the TState
+ /// </summary>
+ public TState State { get; }
+
+ /// <summary>
+ /// Gets the log exception
+ /// </summary>
+ public Exception Exception { get; }
+
+ /// <summary>
+ /// Gets the formatter
+ /// </summary>
+ public Func<TState, Exception, string> Formatter { get; }
+ }
+}
{
public static Microsoft.Extensions.Logging.ILoggingBuilder AddConsole(this Microsoft.Extensions.Logging.ILoggingBuilder builder) { throw null; }
public static Microsoft.Extensions.Logging.ILoggingBuilder AddConsole(this Microsoft.Extensions.Logging.ILoggingBuilder builder, System.Action<Microsoft.Extensions.Logging.Console.ConsoleLoggerOptions> configure) { throw null; }
+ public static Microsoft.Extensions.Logging.ILoggingBuilder AddConsoleFormatter<TFormatter, TOptions>(this Microsoft.Extensions.Logging.ILoggingBuilder builder) where TFormatter : Microsoft.Extensions.Logging.Console.ConsoleFormatter where TOptions : Microsoft.Extensions.Logging.Console.ConsoleFormatterOptions { throw null; }
+ public static Microsoft.Extensions.Logging.ILoggingBuilder AddConsoleFormatter<TFormatter, TOptions>(this Microsoft.Extensions.Logging.ILoggingBuilder builder, System.Action<TOptions> configure) where TFormatter : Microsoft.Extensions.Logging.Console.ConsoleFormatter where TOptions : Microsoft.Extensions.Logging.Console.ConsoleFormatterOptions { throw null; }
+ public static Microsoft.Extensions.Logging.ILoggingBuilder AddJsonConsole(this Microsoft.Extensions.Logging.ILoggingBuilder builder, System.Action<Microsoft.Extensions.Logging.Console.JsonConsoleFormatterOptions> configure) { throw null; }
+ public static Microsoft.Extensions.Logging.ILoggingBuilder AddSimpleConsole(this Microsoft.Extensions.Logging.ILoggingBuilder builder, System.Action<Microsoft.Extensions.Logging.Console.SimpleConsoleFormatterOptions> configure) { throw null; }
+ public static Microsoft.Extensions.Logging.ILoggingBuilder AddSystemdConsole(this Microsoft.Extensions.Logging.ILoggingBuilder builder, System.Action<Microsoft.Extensions.Logging.Console.ConsoleFormatterOptions> configure) { throw null; }
}
}
namespace Microsoft.Extensions.Logging.Console
{
+ public abstract partial class ConsoleFormatter
+ {
+ protected ConsoleFormatter(string name) { }
+ public string Name { get { throw null; } }
+ public abstract void Write<TState>(in Microsoft.Extensions.Logging.Abstractions.LogEntry<TState> logEntry, Microsoft.Extensions.Logging.IExternalScopeProvider scopeProvider, System.IO.TextWriter textWriter);
+ }
+ public static partial class ConsoleFormatterNames
+ {
+ public const string Json = "json";
+ public const string Simple = "simple";
+ public const string Systemd = "systemd";
+ }
+ public partial class ConsoleFormatterOptions
+ {
+ public ConsoleFormatterOptions() { }
+ public bool IncludeScopes { get { throw null; } set { } }
+ public string TimestampFormat { get { throw null; } set { } }
+ public bool UseUtcTimestamp { get { throw null; } set { } }
+ }
+ [System.ObsoleteAttribute("ConsoleLoggerFormat has been deprecated.", false)]
public enum ConsoleLoggerFormat
{
Default = 0,
public partial class ConsoleLoggerOptions
{
public ConsoleLoggerOptions() { }
+ [System.ObsoleteAttribute("ConsoleLoggerOptions.DisableColors has been deprecated. Please use SimpleConsoleFormatterOptions.DisableColors instead.", false)]
public bool DisableColors { get { throw null; } set { } }
+ [System.ObsoleteAttribute("ConsoleLoggerOptions.Format has been deprecated. Please use ConsoleLoggerOptions.FormatterName instead.", false)]
public Microsoft.Extensions.Logging.Console.ConsoleLoggerFormat Format { get { throw null; } set { } }
+ public string FormatterName { get { throw null; } set { } }
+ [System.ObsoleteAttribute("ConsoleLoggerOptions.IncludeScopes has been deprecated. Please use ConsoleFormatterOptions.IncludeScopes instead.", false)]
public bool IncludeScopes { get { throw null; } set { } }
public Microsoft.Extensions.Logging.LogLevel LogToStandardErrorThreshold { get { throw null; } set { } }
+ [System.ObsoleteAttribute("ConsoleLoggerOptions.TimestampFormat has been deprecated. Please use ConsoleFormatterOptions.TimestampFormat instead.", false)]
public string TimestampFormat { get { throw null; } set { } }
+ [System.ObsoleteAttribute("ConsoleLoggerOptions.UseUtcTimestamp has been deprecated. Please use ConsoleFormatterOptions.UseUtcTimestamp instead.", false)]
public bool UseUtcTimestamp { get { throw null; } set { } }
}
[Microsoft.Extensions.Logging.ProviderAliasAttribute("Console")]
public partial class ConsoleLoggerProvider : Microsoft.Extensions.Logging.ILoggerProvider, Microsoft.Extensions.Logging.ISupportExternalScope, System.IDisposable
{
public ConsoleLoggerProvider(Microsoft.Extensions.Options.IOptionsMonitor<Microsoft.Extensions.Logging.Console.ConsoleLoggerOptions> options) { }
+ public ConsoleLoggerProvider(Microsoft.Extensions.Options.IOptionsMonitor<Microsoft.Extensions.Logging.Console.ConsoleLoggerOptions> options, System.Collections.Generic.IEnumerable<Microsoft.Extensions.Logging.Console.ConsoleFormatter> formatters) { }
public Microsoft.Extensions.Logging.ILogger CreateLogger(string name) { throw null; }
public void Dispose() { }
public void SetScopeProvider(Microsoft.Extensions.Logging.IExternalScopeProvider scopeProvider) { }
}
+ public partial class JsonConsoleFormatterOptions : Microsoft.Extensions.Logging.Console.ConsoleFormatterOptions
+ {
+ public JsonConsoleFormatterOptions() { }
+ public System.Text.Json.JsonWriterOptions JsonWriterOptions { get { throw null; } set { } }
+ }
+ public partial class SimpleConsoleFormatterOptions : Microsoft.Extensions.Logging.Console.ConsoleFormatterOptions
+ {
+ public SimpleConsoleFormatterOptions() { }
+ public bool DisableColors { get { throw null; } set { } }
+ public bool SingleLine { get { throw null; } set { } }
+ }
}
<ItemGroup>
<Compile Include="Microsoft.Extensions.Logging.Console.cs" />
+ <ProjectReference Include="..\..\System.Text.Json\ref\System.Text.Json.csproj" />
<ProjectReference Include="..\..\Microsoft.Extensions.Logging.Abstractions\ref\Microsoft.Extensions.Logging.Abstractions.csproj" />
<ProjectReference Include="..\..\Microsoft.Extensions.Logging\ref\Microsoft.Extensions.Logging.csproj" />
<ProjectReference Include="..\..\Microsoft.Extensions.Options\ref\Microsoft.Extensions.Options.csproj" />
namespace Microsoft.Extensions.Logging.Console
{
/// <summary>
- /// For non-Windows platform consoles which understand the ANSI escape code sequences to represent color
+ /// For consoles which understand the ANSI escape code sequences to represent color
/// </summary>
internal class AnsiLogConsole : IConsole
{
- private readonly StringBuilder _outputBuilder;
private readonly IAnsiSystemConsole _systemConsole;
- public AnsiLogConsole(IAnsiSystemConsole systemConsole)
+ public AnsiLogConsole(bool stdErr = false)
{
- _outputBuilder = new StringBuilder();
- _systemConsole = systemConsole;
+ _systemConsole = new AnsiSystemConsole(stdErr);
}
- public void Write(string message, ConsoleColor? background, ConsoleColor? foreground)
+ public void Write(string message)
{
- // Order: backgroundcolor, foregroundcolor, Message, reset foregroundcolor, reset backgroundcolor
- if (background.HasValue)
- {
- _outputBuilder.Append(GetBackgroundColorEscapeCode(background.Value));
- }
-
- if (foreground.HasValue)
- {
- _outputBuilder.Append(GetForegroundColorEscapeCode(foreground.Value));
- }
-
- _outputBuilder.Append(message);
-
- if (foreground.HasValue)
- {
- _outputBuilder.Append("\x1B[39m\x1B[22m"); // reset to default foreground color
- }
-
- if (background.HasValue)
- {
- _outputBuilder.Append("\x1B[49m"); // reset to the background color
- }
- }
-
- public void WriteLine(string message, ConsoleColor? background, ConsoleColor? foreground)
- {
- Write(message, background, foreground);
- _outputBuilder.AppendLine();
- }
-
- public void Flush()
- {
- _systemConsole.Write(_outputBuilder.ToString());
- _outputBuilder.Clear();
- }
-
- private static string GetForegroundColorEscapeCode(ConsoleColor color)
- {
- switch (color)
- {
- case ConsoleColor.Black:
- return "\x1B[30m";
- case ConsoleColor.DarkRed:
- return "\x1B[31m";
- case ConsoleColor.DarkGreen:
- return "\x1B[32m";
- case ConsoleColor.DarkYellow:
- return "\x1B[33m";
- case ConsoleColor.DarkBlue:
- return "\x1B[34m";
- case ConsoleColor.DarkMagenta:
- return "\x1B[35m";
- case ConsoleColor.DarkCyan:
- return "\x1B[36m";
- case ConsoleColor.Gray:
- return "\x1B[37m";
- case ConsoleColor.Red:
- return "\x1B[1m\x1B[31m";
- case ConsoleColor.Green:
- return "\x1B[1m\x1B[32m";
- case ConsoleColor.Yellow:
- return "\x1B[1m\x1B[33m";
- case ConsoleColor.Blue:
- return "\x1B[1m\x1B[34m";
- case ConsoleColor.Magenta:
- return "\x1B[1m\x1B[35m";
- case ConsoleColor.Cyan:
- return "\x1B[1m\x1B[36m";
- case ConsoleColor.White:
- return "\x1B[1m\x1B[37m";
- default:
- return "\x1B[39m\x1B[22m"; // default foreground color
- }
- }
-
- private static string GetBackgroundColorEscapeCode(ConsoleColor color)
- {
- switch (color)
- {
- case ConsoleColor.Black:
- return "\x1B[40m";
- case ConsoleColor.Red:
- return "\x1B[41m";
- case ConsoleColor.Green:
- return "\x1B[42m";
- case ConsoleColor.Yellow:
- return "\x1B[43m";
- case ConsoleColor.Blue:
- return "\x1B[44m";
- case ConsoleColor.Magenta:
- return "\x1B[45m";
- case ConsoleColor.Cyan:
- return "\x1B[46m";
- case ConsoleColor.White:
- return "\x1B[47m";
- default:
- return "\x1B[49m"; // Use default background color
- }
+ _systemConsole.Write(message);
}
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+
+namespace Microsoft.Extensions.Logging.Console
+{
+ internal class AnsiParser
+ {
+ private readonly Action<string, int, int, ConsoleColor?, ConsoleColor?> _onParseWrite;
+ public AnsiParser(Action<string, int, int, ConsoleColor?, ConsoleColor?> onParseWrite)
+ {
+ if (onParseWrite == null)
+ {
+ throw new ArgumentNullException(nameof(onParseWrite));
+ }
+ _onParseWrite = onParseWrite;
+ }
+
+ /// <summary>
+ /// Parses a subset of display attributes
+ /// Set Display Attributes
+ /// Set Attribute Mode [{attr1};...;{attrn}m
+ /// Sets multiple display attribute settings. The following lists standard attributes that are getting parsed:
+ /// 1 Bright
+ /// Foreground Colours
+ /// 30 Black
+ /// 31 Red
+ /// 32 Green
+ /// 33 Yellow
+ /// 34 Blue
+ /// 35 Magenta
+ /// 36 Cyan
+ /// 37 White
+ /// Background Colours
+ /// 40 Black
+ /// 41 Red
+ /// 42 Green
+ /// 43 Yellow
+ /// 44 Blue
+ /// 45 Magenta
+ /// 46 Cyan
+ /// 47 White
+ /// </summary>
+ public void Parse(string message)
+ {
+ int startIndex = -1;
+ int length = 0;
+ ConsoleColor? foreground = null;
+ ConsoleColor? background = null;
+ var span = message.AsSpan();
+ const char EscapeChar = '\x1B';
+ ConsoleColor? color = null;
+ bool isBright = false;
+ for (int i = 0; i < span.Length; i++)
+ {
+ if (span[i] == EscapeChar && span.Length >= i + 4 && span[i + 1] == '[')
+ {
+ if (span[i + 3] == 'm')
+ {
+ #if (NETSTANDARD2_0 || NETFRAMEWORK)
+ if (ushort.TryParse(span.Slice(i + 2, length: 1).ToString(), out ushort escapeCode))
+ #else
+ if (ushort.TryParse(span.Slice(i + 2, length: 1), out ushort escapeCode))
+ #endif
+ {
+ if (startIndex != -1)
+ {
+ _onParseWrite(message, startIndex, length, background, foreground);
+ startIndex = -1;
+ length = 0;
+ }
+ if (escapeCode == 1)
+ isBright = true;
+ i += 3;
+ continue;
+ }
+ }
+ else if (span.Length >= i + 5 && span[i + 4] == 'm')
+ {
+ #if (NETSTANDARD2_0 || NETFRAMEWORK)
+ if (ushort.TryParse(span.Slice(i + 2, length: 2).ToString(), out ushort escapeCode))
+ #else
+ if (ushort.TryParse(span.Slice(i + 2, length: 2), out ushort escapeCode))
+ #endif
+ {
+ if (startIndex != -1)
+ {
+ _onParseWrite(message, startIndex, length, background, foreground);
+ startIndex = -1;
+ length = 0;
+ }
+ if (TryGetForegroundColor(escapeCode, isBright, out color))
+ {
+ foreground = color;
+ isBright = false;
+ }
+ else if (TryGetBackgroundColor(escapeCode, out color))
+ {
+ background = color;
+ }
+ i += 4;
+ continue;
+ }
+ }
+ }
+ if (startIndex == -1)
+ {
+ startIndex = i;
+ }
+ int nextEscapeIndex = -1;
+ if (i < message.Length - 1)
+ {
+ nextEscapeIndex = message.IndexOf(EscapeChar, i + 1);
+ }
+ if (nextEscapeIndex < 0)
+ {
+ length = message.Length - startIndex;
+ break;
+ }
+ length = nextEscapeIndex - startIndex;
+ i = nextEscapeIndex - 1;
+ }
+ if (startIndex != -1)
+ {
+ _onParseWrite(message, startIndex, length, background, foreground);
+ }
+ }
+
+ internal const string DefaultForegroundColor = "\x1B[39m\x1B[22m"; // reset to default foreground color
+ internal const string DefaultBackgroundColor = "\x1B[49m"; // reset to the background color
+
+ internal static string GetForegroundColorEscapeCode(ConsoleColor color)
+ {
+ return color switch
+ {
+ ConsoleColor.Black => "\x1B[30m",
+ ConsoleColor.DarkRed => "\x1B[31m",
+ ConsoleColor.DarkGreen => "\x1B[32m",
+ ConsoleColor.DarkYellow => "\x1B[33m",
+ ConsoleColor.DarkBlue => "\x1B[34m",
+ ConsoleColor.DarkMagenta => "\x1B[35m",
+ ConsoleColor.DarkCyan => "\x1B[36m",
+ ConsoleColor.Gray => "\x1B[37m",
+ ConsoleColor.Red => "\x1B[1m\x1B[31m",
+ ConsoleColor.Green => "\x1B[1m\x1B[32m",
+ ConsoleColor.Yellow => "\x1B[1m\x1B[33m",
+ ConsoleColor.Blue => "\x1B[1m\x1B[34m",
+ ConsoleColor.Magenta => "\x1B[1m\x1B[35m",
+ ConsoleColor.Cyan => "\x1B[1m\x1B[36m",
+ ConsoleColor.White => "\x1B[1m\x1B[37m",
+ _ => DefaultForegroundColor // default foreground color
+ };
+ }
+
+ internal static string GetBackgroundColorEscapeCode(ConsoleColor color)
+ {
+ return color switch
+ {
+ ConsoleColor.Black => "\x1B[40m",
+ ConsoleColor.DarkRed => "\x1B[41m",
+ ConsoleColor.DarkGreen => "\x1B[42m",
+ ConsoleColor.DarkYellow => "\x1B[43m",
+ ConsoleColor.DarkBlue => "\x1B[44m",
+ ConsoleColor.DarkMagenta => "\x1B[45m",
+ ConsoleColor.DarkCyan => "\x1B[46m",
+ ConsoleColor.Gray => "\x1B[47m",
+ _ => DefaultBackgroundColor // Use default background color
+ };
+ }
+
+ private static bool TryGetForegroundColor(int number, bool isBright, out ConsoleColor? color)
+ {
+ color = number switch
+ {
+ 30 => ConsoleColor.Black,
+ 31 => isBright ? ConsoleColor.Red: ConsoleColor.DarkRed,
+ 32 => isBright ? ConsoleColor.Green: ConsoleColor.DarkGreen,
+ 33 => isBright ? ConsoleColor.Yellow: ConsoleColor.DarkYellow,
+ 34 => isBright ? ConsoleColor.Blue: ConsoleColor.DarkBlue,
+ 35 => isBright ? ConsoleColor.Magenta: ConsoleColor.DarkMagenta,
+ 36 => isBright ? ConsoleColor.Cyan: ConsoleColor.DarkCyan,
+ 37 => isBright ? ConsoleColor.White: ConsoleColor.Gray,
+ _ => null
+ };
+ return color != null || number == 39;
+ }
+
+ private static bool TryGetBackgroundColor(int number, out ConsoleColor? color)
+ {
+ color = number switch
+ {
+ 40 => ConsoleColor.Black,
+ 41 => ConsoleColor.DarkRed,
+ 42 => ConsoleColor.DarkGreen,
+ 43 => ConsoleColor.DarkYellow,
+ 44 => ConsoleColor.DarkBlue,
+ 45 => ConsoleColor.DarkMagenta,
+ 46 => ConsoleColor.DarkCyan,
+ 47 => ConsoleColor.Gray,
+ _ => null
+ };
+ return color != null || number == 49;
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+
+namespace Microsoft.Extensions.Logging.Console
+{
+ internal class AnsiParsingLogConsole : IConsole
+ {
+ private readonly TextWriter _textWriter;
+ private readonly AnsiParser _parser;
+
+ public AnsiParsingLogConsole(bool stdErr = false)
+ {
+ _textWriter = stdErr ? System.Console.Error : System.Console.Out;
+ _parser = new AnsiParser(WriteToConsole);
+ }
+
+ public void Write(string message)
+ {
+ _parser.Parse(message);
+ }
+
+ private bool SetColor(ConsoleColor? background, ConsoleColor? foreground)
+ {
+ var backgroundChanged = SetBackgroundColor(background);
+ return SetForegroundColor(foreground) || backgroundChanged;
+ }
+
+ private bool SetBackgroundColor(ConsoleColor? background)
+ {
+ if (background.HasValue)
+ {
+ System.Console.BackgroundColor = background.Value;
+ return true;
+ }
+ return false;
+ }
+
+ private bool SetForegroundColor(ConsoleColor? foreground)
+ {
+ if (foreground.HasValue)
+ {
+ System.Console.ForegroundColor = foreground.Value;
+ return true;
+ }
+ return false;
+ }
+
+ private void ResetColor()
+ {
+ System.Console.ResetColor();
+ }
+
+ private void WriteToConsole(string message, int startIndex, int length, ConsoleColor? background, ConsoleColor? foreground)
+ {
+ ReadOnlySpan<char> span = message.AsSpan().Slice(startIndex, length);
+ var colorChanged = SetColor(background, foreground);
+#if (NETSTANDARD2_0 || NETFRAMEWORK)
+ _textWriter.Write(span.ToString());
+#else
+ _textWriter.Write(span);
+#endif
+ if (colorChanged)
+ {
+ ResetColor();
+ }
+ }
+ }
+}
{
private readonly TextWriter _textWriter;
- public AnsiSystemConsole(bool stdErr = false)
+ public AnsiSystemConsole(bool stdErr)
{
_textWriter = stdErr ? System.Console.Error : System.Console.Out;
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Text;
+using Microsoft.Extensions.Logging.Abstractions;
+
+namespace Microsoft.Extensions.Logging.Console
+{
+ /// <summary>
+ /// Allows custom log messages formatting
+ /// </summary>
+ public abstract class ConsoleFormatter
+ {
+ protected ConsoleFormatter(string name)
+ {
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ }
+
+ /// <summary>
+ /// Gets the name associated with the console log formatter.
+ /// </summary>
+ public string Name { get; }
+
+ /// <summary>
+ /// Writes the log message to the specified TextWriter.
+ /// </summary>
+ /// <remarks>
+ /// if the formatter wants to write colors to the console, it can do so by embedding ANSI color codes into the string
+ /// </remarks>
+ /// <param name="logEntry">The log entry.</param>
+ /// <param name="scopeProvider">The provider of scope data.</param>
+ /// <param name="textWriter">The string writer embedding ansi code for colors.</param>
+ /// <typeparam name="TState">The type of the object to be written.</typeparam>
+ public abstract void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider scopeProvider, TextWriter textWriter);
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.Extensions.Logging.Console
+{
+ public static class ConsoleFormatterNames
+ {
+ public const string Simple = "simple";
+ public const string Json = "json";
+ public const string Systemd = "systemd";
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Text;
+
+namespace Microsoft.Extensions.Logging.Console
+{
+ /// <summary>
+ /// Options for the built-in console log formatter.
+ /// </summary>
+ public class ConsoleFormatterOptions
+ {
+ public ConsoleFormatterOptions() { }
+
+ /// <summary>
+ /// Includes scopes when <see langword="true" />.
+ /// </summary>
+ public bool IncludeScopes { get; set; }
+
+ /// <summary>
+ /// Gets or sets format string used to format timestamp in logging messages. Defaults to <c>null</c>.
+ /// </summary>
+ public string TimestampFormat { get; set; }
+
+ /// <summary>
+ /// Gets or sets indication whether or not UTC timezone should be used to for timestamps in logging messages. Defaults to <c>false</c>.
+ /// </summary>
+ public bool UseUtcTimestamp { get; set; }
+ }
+}
using System;
using System.Diagnostics;
+using System.IO;
using System.Text;
+using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.Extensions.Logging.Console
{
internal class ConsoleLogger : ILogger
{
- private const string LoglevelPadding = ": ";
- private static readonly string _messagePadding = new string(' ', GetLogLevelString(LogLevel.Information).Length + LoglevelPadding.Length);
- private static readonly string _newLineWithMessagePadding = Environment.NewLine + _messagePadding;
-
private readonly string _name;
private readonly ConsoleLoggerProcessor _queueProcessor;
- [ThreadStatic]
- private static StringBuilder _logBuilder;
-
internal ConsoleLogger(string name, ConsoleLoggerProcessor loggerProcessor)
{
if (name == null)
_queueProcessor = loggerProcessor;
}
+ internal ConsoleFormatter Formatter { get; set; }
internal IExternalScopeProvider ScopeProvider { get; set; }
internal ConsoleLoggerOptions Options { get; set; }
+ [ThreadStatic]
+ private static StringWriter t_stringWriter;
+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
-
if (formatter == null)
{
throw new ArgumentNullException(nameof(formatter));
}
+ t_stringWriter ??= new StringWriter();
+ LogEntry<TState> logEntry = new LogEntry<TState>(logLevel, _name, eventId, state, exception, formatter);
+ Formatter.Write(in logEntry, ScopeProvider, t_stringWriter);
- string message = formatter(state, exception);
-
- if (!string.IsNullOrEmpty(message) || exception != null)
- {
- WriteMessage(logLevel, _name, eventId.Id, message, exception);
- }
- }
-
- public virtual void WriteMessage(LogLevel logLevel, string logName, int eventId, string message, Exception exception)
- {
- ConsoleLoggerFormat format = Options.Format;
- Debug.Assert(format >= ConsoleLoggerFormat.Default && format <= ConsoleLoggerFormat.Systemd);
-
- StringBuilder logBuilder = _logBuilder;
- _logBuilder = null;
-
- if (logBuilder == null)
- {
- logBuilder = new StringBuilder();
- }
-
- LogMessageEntry entry;
- if (format == ConsoleLoggerFormat.Default)
- {
- entry = CreateDefaultLogMessage(logBuilder, logLevel, logName, eventId, message, exception);
- }
- else if (format == ConsoleLoggerFormat.Systemd)
- {
- entry = CreateSystemdLogMessage(logBuilder, logLevel, logName, eventId, message, exception);
- }
- else
- {
- entry = default;
- }
- _queueProcessor.EnqueueMessage(entry);
-
- logBuilder.Clear();
- if (logBuilder.Capacity > 1024)
- {
- logBuilder.Capacity = 1024;
- }
- _logBuilder = logBuilder;
- }
-
- private LogMessageEntry CreateDefaultLogMessage(StringBuilder logBuilder, LogLevel logLevel, string logName, int eventId, string message, Exception exception)
- {
- // Example:
- // info: ConsoleApp.Program[10]
- // Request received
-
- ConsoleColors logLevelColors = GetLogLevelConsoleColors(logLevel);
- string logLevelString = GetLogLevelString(logLevel);
- // category and event id
- logBuilder.Append(LoglevelPadding);
- logBuilder.Append(logName);
- logBuilder.Append('[');
- logBuilder.Append(eventId);
- logBuilder.AppendLine("]");
-
- // scope information
- GetScopeInformation(logBuilder, multiLine: true);
-
- if (!string.IsNullOrEmpty(message))
- {
- // message
- logBuilder.Append(_messagePadding);
-
- int len = logBuilder.Length;
- logBuilder.AppendLine(message);
- logBuilder.Replace(Environment.NewLine, _newLineWithMessagePadding, len, message.Length);
- }
-
- // Example:
- // System.InvalidOperationException
- // at Namespace.Class.Function() in File:line X
- if (exception != null)
- {
- // exception message
- logBuilder.AppendLine(exception.ToString());
- }
-
- string timestamp = null;
- string timestampFormat = Options.TimestampFormat;
- if (timestampFormat != null)
- {
- DateTime dateTime = GetCurrentDateTime();
- timestamp = dateTime.ToString(timestampFormat);
- }
-
- return new LogMessageEntry(
- message: logBuilder.ToString(),
- timeStamp: timestamp,
- levelString: logLevelString,
- levelBackground: logLevelColors.Background,
- levelForeground: logLevelColors.Foreground,
- messageColor: null,
- logAsError: logLevel >= Options.LogToStandardErrorThreshold
- );
- }
-
- private LogMessageEntry CreateSystemdLogMessage(StringBuilder logBuilder, LogLevel logLevel, string logName, int eventId, string message, Exception exception)
- {
- // systemd reads messages from standard out line-by-line in a '<pri>message' format.
- // newline characters are treated as message delimiters, so we must replace them.
- // Messages longer than the journal LineMax setting (default: 48KB) are cropped.
- // Example:
- // <6>ConsoleApp.Program[10] Request received
-
- // loglevel
- string logLevelString = GetSyslogSeverityString(logLevel);
- logBuilder.Append(logLevelString);
-
- // timestamp
- string timestampFormat = Options.TimestampFormat;
- if (timestampFormat != null)
- {
- DateTime dateTime = GetCurrentDateTime();
- logBuilder.Append(dateTime.ToString(timestampFormat));
- }
-
- // category and event id
- logBuilder.Append(logName);
- logBuilder.Append('[');
- logBuilder.Append(eventId);
- logBuilder.Append(']');
-
- // scope information
- GetScopeInformation(logBuilder, multiLine: false);
-
- // message
- if (!string.IsNullOrEmpty(message))
+ var sb = t_stringWriter.GetStringBuilder();
+ if (sb.Length == 0)
{
- logBuilder.Append(' ');
- // message
- AppendAndReplaceNewLine(logBuilder, message);
+ return;
}
-
- // exception
- // System.InvalidOperationException at Namespace.Class.Function() in File:line X
- if (exception != null)
+ string computedAnsiString = sb.ToString();
+ sb.Clear();
+ if (sb.Capacity > 1024)
{
- logBuilder.Append(' ');
- AppendAndReplaceNewLine(logBuilder, exception.ToString());
+ sb.Capacity = 1024;
}
-
- // newline delimiter
- logBuilder.Append(Environment.NewLine);
-
- return new LogMessageEntry(
- message: logBuilder.ToString(),
- logAsError: logLevel >= Options.LogToStandardErrorThreshold
- );
-
- static void AppendAndReplaceNewLine(StringBuilder sb, string message)
- {
- int len = sb.Length;
- sb.Append(message);
- sb.Replace(Environment.NewLine, " ", len, message.Length);
- }
- }
-
- private DateTime GetCurrentDateTime()
- {
- return Options.UseUtcTimestamp ? DateTime.UtcNow : DateTime.Now;
+ _queueProcessor.EnqueueMessage(new LogMessageEntry(computedAnsiString, logAsError: logLevel >= Options.LogToStandardErrorThreshold));
}
public bool IsEnabled(LogLevel logLevel)
}
public IDisposable BeginScope<TState>(TState state) => ScopeProvider?.Push(state) ?? NullScope.Instance;
-
- private static string GetLogLevelString(LogLevel logLevel)
- {
- switch (logLevel)
- {
- case LogLevel.Trace:
- return "trce";
- case LogLevel.Debug:
- return "dbug";
- case LogLevel.Information:
- return "info";
- case LogLevel.Warning:
- return "warn";
- case LogLevel.Error:
- return "fail";
- case LogLevel.Critical:
- return "crit";
- default:
- throw new ArgumentOutOfRangeException(nameof(logLevel));
- }
- }
-
- private static string GetSyslogSeverityString(LogLevel logLevel)
- {
- // 'Syslog Message Severities' from https://tools.ietf.org/html/rfc5424.
- switch (logLevel)
- {
- case LogLevel.Trace:
- case LogLevel.Debug:
- return "<7>"; // debug-level messages
- case LogLevel.Information:
- return "<6>"; // informational messages
- case LogLevel.Warning:
- return "<4>"; // warning conditions
- case LogLevel.Error:
- return "<3>"; // error conditions
- case LogLevel.Critical:
- return "<2>"; // critical conditions
- default:
- throw new ArgumentOutOfRangeException(nameof(logLevel));
- }
- }
-
- private ConsoleColors GetLogLevelConsoleColors(LogLevel logLevel)
- {
- if (!Options.DisableColors)
- {
- // We must explicitly set the background color if we are setting the foreground color,
- // since just setting one can look bad on the users console.
- switch (logLevel)
- {
- case LogLevel.Critical:
- return new ConsoleColors(ConsoleColor.White, ConsoleColor.Red);
- case LogLevel.Error:
- return new ConsoleColors(ConsoleColor.Black, ConsoleColor.Red);
- case LogLevel.Warning:
- return new ConsoleColors(ConsoleColor.Yellow, ConsoleColor.Black);
- case LogLevel.Information:
- return new ConsoleColors(ConsoleColor.DarkGreen, ConsoleColor.Black);
- case LogLevel.Debug:
- return new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black);
- case LogLevel.Trace:
- return new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black);
- }
- }
-
- return new ConsoleColors(null, null);
- }
-
- private void GetScopeInformation(StringBuilder stringBuilder, bool multiLine)
- {
- IExternalScopeProvider scopeProvider = ScopeProvider;
- if (Options.IncludeScopes && scopeProvider != null)
- {
- int initialLength = stringBuilder.Length;
-
- scopeProvider.ForEachScope((scope, state) =>
- {
- (StringBuilder builder, int paddAt) = state;
- bool padd = paddAt == builder.Length;
- if (padd)
- {
- builder.Append(_messagePadding);
- builder.Append("=> ");
- }
- else
- {
- builder.Append(" => ");
- }
- builder.Append(scope);
- }, (stringBuilder, multiLine ? initialLength : -1));
-
- if (stringBuilder.Length > initialLength && multiLine)
- {
- stringBuilder.AppendLine();
- }
- }
- }
-
- private readonly struct ConsoleColors
- {
- public ConsoleColors(ConsoleColor? foreground, ConsoleColor? background)
- {
- Foreground = foreground;
- Background = background;
- }
-
- public ConsoleColor? Foreground { get; }
-
- public ConsoleColor? Background { get; }
- }
}
}
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Configuration;
using Microsoft.Extensions.Logging.Console;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.Logging
{
{
builder.AddConfiguration();
+ builder.AddConsoleFormatter<JsonConsoleFormatter, JsonConsoleFormatterOptions>();
+ builder.AddConsoleFormatter<SystemdConsoleFormatter, ConsoleFormatterOptions>();
+ builder.AddConsoleFormatter<SimpleConsoleFormatter, SimpleConsoleFormatterOptions>();
+
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, ConsoleLoggerProvider>());
LoggerProviderOptions.RegisterProviderOptions<ConsoleLoggerOptions, ConsoleLoggerProvider>(builder.Services);
+
return builder;
}
return builder;
}
+
+ /// <summary>
+ /// Add and configure a console log formatter named 'json' to the factory.
+ /// </summary>
+ /// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
+ /// <param name="configure">A delegate to configure the <see cref="ConsoleLogger"/> options for the built-in default log formatter.</param>
+ public static ILoggingBuilder AddSimpleConsole(this ILoggingBuilder builder, Action<SimpleConsoleFormatterOptions> configure)
+ {
+ return builder.AddConsoleWithFormatter<SimpleConsoleFormatterOptions>(ConsoleFormatterNames.Simple, configure);
+ }
+
+ /// <summary>
+ /// Add and configure a console log formatter named 'json' to the factory.
+ /// </summary>
+ /// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
+ /// <param name="configure">A delegate to configure the <see cref="ConsoleLogger"/> options for the built-in json log formatter.</param>
+ public static ILoggingBuilder AddJsonConsole(this ILoggingBuilder builder, Action<JsonConsoleFormatterOptions> configure)
+ {
+ return builder.AddConsoleWithFormatter<JsonConsoleFormatterOptions>(ConsoleFormatterNames.Json, configure);
+ }
+
+ /// <summary>
+ /// Add and configure a console log formatter named 'systemd' to the factory.
+ /// </summary>
+ /// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
+ /// <param name="configure">A delegate to configure the <see cref="ConsoleLogger"/> options for the built-in systemd log formatter.</param>
+ public static ILoggingBuilder AddSystemdConsole(this ILoggingBuilder builder, Action<ConsoleFormatterOptions> configure)
+ {
+ return builder.AddConsoleWithFormatter<ConsoleFormatterOptions>(ConsoleFormatterNames.Systemd, configure);
+ }
+
+ internal static ILoggingBuilder AddConsoleWithFormatter<TOptions>(this ILoggingBuilder builder, string name, Action<TOptions> configure)
+ where TOptions : ConsoleFormatterOptions
+ {
+ if (configure == null)
+ {
+ throw new ArgumentNullException(nameof(configure));
+ }
+ builder.AddConsole((ConsoleLoggerOptions options) => options.FormatterName = name);
+ builder.Services.Configure(configure);
+
+ return builder;
+ }
+
+ /// <summary>
+ /// Adds a custom console logger formatter 'TFormatter' to be configured with options 'TOptions'.
+ /// </summary>
+ /// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
+ public static ILoggingBuilder AddConsoleFormatter<TFormatter, TOptions>(this ILoggingBuilder builder)
+ where TOptions : ConsoleFormatterOptions
+ where TFormatter : ConsoleFormatter
+ {
+ builder.AddConfiguration();
+
+ builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ConsoleFormatter, TFormatter>());
+ builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<TOptions>, ConsoleLoggerFormatterConfigureOptions<TFormatter, TOptions>>());
+ builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptionsChangeTokenSource<TOptions>, ConsoleLoggerFormatterOptionsChangeTokenSource<TFormatter, TOptions>>());
+
+ return builder;
+ }
+
+ /// <summary>
+ /// Adds a custom console logger formatter 'TFormatter' to be configured with options 'TOptions'.
+ /// </summary>
+ /// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
+ /// <param name="configure">A delegate to configure options 'TOptions' for custom formatter 'TFormatter'.</param>
+ public static ILoggingBuilder AddConsoleFormatter<TFormatter, TOptions>(this ILoggingBuilder builder, Action<TOptions> configure)
+ where TOptions : ConsoleFormatterOptions
+ where TFormatter : ConsoleFormatter
+ {
+ if (configure == null)
+ {
+ throw new ArgumentNullException(nameof(configure));
+ }
+
+ builder.AddConsoleFormatter<TFormatter, TOptions>();
+ builder.Services.Configure(configure);
+ return builder;
+ }
+ }
+
+ internal class ConsoleLoggerFormatterConfigureOptions<TFormatter, TOptions> : ConfigureFromConfigurationOptions<TOptions>
+ where TOptions : ConsoleFormatterOptions
+ where TFormatter : ConsoleFormatter
+ {
+ public ConsoleLoggerFormatterConfigureOptions(ILoggerProviderConfiguration<ConsoleLoggerProvider> providerConfiguration) :
+ base(providerConfiguration.Configuration.GetSection("FormatterOptions"))
+ {
+ }
+ }
+
+ internal class ConsoleLoggerFormatterOptionsChangeTokenSource<TFormatter, TOptions> : ConfigurationChangeTokenSource<TOptions>
+ where TOptions : ConsoleFormatterOptions
+ where TFormatter : ConsoleFormatter
+ {
+ public ConsoleLoggerFormatterOptionsChangeTokenSource(ILoggerProviderConfiguration<ConsoleLoggerProvider> providerConfiguration)
+ : base(providerConfiguration.Configuration.GetSection("FormatterOptions"))
+ {
+ }
}
}
/// <summary>
/// Format of <see cref="ConsoleLogger" /> messages.
/// </summary>
+ [System.ObsoleteAttribute("ConsoleLoggerFormat has been deprecated.", false)]
public enum ConsoleLoggerFormat
{
/// <summary>
/// </summary>
public class ConsoleLoggerOptions
{
- private ConsoleLoggerFormat _format = ConsoleLoggerFormat.Default;
-
- /// <summary>
- /// Includes scopes when <see langword="true" />.
- /// </summary>
- public bool IncludeScopes { get; set; }
-
/// <summary>
/// Disables colors when <see langword="true" />.
/// </summary>
+ [System.ObsoleteAttribute("ConsoleLoggerOptions.DisableColors has been deprecated. Please use SimpleConsoleFormatterOptions.DisableColors instead.", false)]
public bool DisableColors { get; set; }
+#pragma warning disable CS0618
+ private ConsoleLoggerFormat _format = ConsoleLoggerFormat.Default;
/// <summary>
/// Gets or sets log message format. Defaults to <see cref="ConsoleLoggerFormat.Default" />.
/// </summary>
+ [System.ObsoleteAttribute("ConsoleLoggerOptions.Format has been deprecated. Please use ConsoleLoggerOptions.FormatterName instead.", false)]
public ConsoleLoggerFormat Format
{
get => _format;
}
_format = value;
}
+#pragma warning restore CS0618
}
/// <summary>
+ /// Name of the log message formatter to use. Defaults to "simple" />.
+ /// </summary>
+ public string FormatterName { get; set; }
+
+ /// <summary>
+ /// Includes scopes when <see langword="true" />.
+ /// </summary>
+ [System.ObsoleteAttribute("ConsoleLoggerOptions.IncludeScopes has been deprecated. Please use ConsoleFormatterOptions.IncludeScopes instead.", false)]
+ public bool IncludeScopes { get; set; }
+
+ /// <summary>
/// Gets or sets value indicating the minimum level of messages 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>
+ [System.ObsoleteAttribute("ConsoleLoggerOptions.TimestampFormat has been deprecated. Please use ConsoleFormatterOptions.TimestampFormat instead.", false)]
public string TimestampFormat { get; set; }
/// <summary>
/// Gets or sets indication whether or not UTC timezone should be used to for timestamps in logging messages. Defaults to <c>false</c>.
/// </summary>
+ [System.ObsoleteAttribute("ConsoleLoggerOptions.UseUtcTimestamp has been deprecated. Please use ConsoleFormatterOptions.UseUtcTimestamp instead.", false)]
public bool UseUtcTimestamp { get; set; }
}
-}
+}
\ No newline at end of file
}
// for testing
- internal virtual void WriteMessage(LogMessageEntry message)
+ internal virtual void WriteMessage(LogMessageEntry entry)
{
- IConsole console = message.LogAsError ? ErrorConsole : Console;
-
- if (message.TimeStamp != null)
- {
- console.Write(message.TimeStamp, message.MessageColor, message.MessageColor);
- }
-
- if (message.LevelString != null)
- {
- console.Write(message.LevelString, message.LevelBackground, message.LevelForeground);
- }
-
- console.Write(message.Message, message.MessageColor, message.MessageColor);
- console.Flush();
+ IConsole console = entry.LogAsError ? ErrorConsole : Console;
+ console.Write(entry.Message);
}
private void ProcessLogQueue()
using System;
using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
using System.Runtime.InteropServices;
+using Microsoft.Extensions.Logging.Configuration;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.Logging.Console
{
private readonly IOptionsMonitor<ConsoleLoggerOptions> _options;
private readonly ConcurrentDictionary<string, ConsoleLogger> _loggers;
+ private ConcurrentDictionary<string, ConsoleFormatter> _formatters;
private readonly ConsoleLoggerProcessor _messageQueue;
private IDisposable _optionsReloadToken;
/// </summary>
/// <param name="options">The options to create <see cref="ConsoleLogger"/> instances with.</param>
public ConsoleLoggerProvider(IOptionsMonitor<ConsoleLoggerOptions> options)
+ : this(options, Enumerable.Empty<ConsoleFormatter>()) { }
+
+ /// <summary>
+ /// Creates an instance of <see cref="ConsoleLoggerProvider"/>.
+ /// </summary>
+ /// <param name="options">The options to create <see cref="ConsoleLogger"/> instances with.</param>
+ /// <param name="formatters">Log formatters added for <see cref="ConsoleLogger"/> insteaces.</param>
+ public ConsoleLoggerProvider(IOptionsMonitor<ConsoleLoggerOptions> options, IEnumerable<ConsoleFormatter> formatters)
{
_options = options;
_loggers = new ConcurrentDictionary<string, ConsoleLogger>();
+ SetFormatters(formatters);
ReloadLoggerOptions(options.CurrentValue);
_optionsReloadToken = _options.OnChange(ReloadLoggerOptions);
_messageQueue = new ConsoleLoggerProcessor();
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ if (DoesConsoleSupportAnsi())
+ {
+ _messageQueue.Console = new AnsiLogConsole();
+ _messageQueue.ErrorConsole = new AnsiLogConsole(stdErr: true);
+ }
+ else
+ {
+ _messageQueue.Console = new AnsiParsingLogConsole();
+ _messageQueue.ErrorConsole = new AnsiParsingLogConsole(stdErr: true);
+ }
+ }
+
+ private static bool DoesConsoleSupportAnsi()
+ {
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return true;
+ }
+ // for Windows, check the console mode
+ var stdOutHandle = Interop.Kernel32.GetStdHandle(Interop.Kernel32.STD_OUTPUT_HANDLE);
+ if (!Interop.Kernel32.GetConsoleMode(stdOutHandle, out int consoleMode))
+ {
+ return false;
+ }
+
+ return (consoleMode & Interop.Kernel32.ENABLE_VIRTUAL_TERMINAL_PROCESSING) == Interop.Kernel32.ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+ }
+
+ private void SetFormatters(IEnumerable<ConsoleFormatter> formatters = null)
+ {
+ _formatters = new ConcurrentDictionary<string, ConsoleFormatter>(StringComparer.OrdinalIgnoreCase);
+ if (formatters == null || !formatters.Any())
{
- _messageQueue.Console = new WindowsLogConsole();
- _messageQueue.ErrorConsole = new WindowsLogConsole(stdErr: true);
+ var defaultMonitor = new FormatterOptionsMonitor<SimpleConsoleFormatterOptions>(new SimpleConsoleFormatterOptions());
+ var systemdMonitor = new FormatterOptionsMonitor<ConsoleFormatterOptions>(new ConsoleFormatterOptions());
+ var jsonMonitor = new FormatterOptionsMonitor<JsonConsoleFormatterOptions>(new JsonConsoleFormatterOptions());
+ _formatters.GetOrAdd(ConsoleFormatterNames.Simple, formatterName => new SimpleConsoleFormatter(defaultMonitor));
+ _formatters.GetOrAdd(ConsoleFormatterNames.Systemd, formatterName => new SystemdConsoleFormatter(systemdMonitor));
+ _formatters.GetOrAdd(ConsoleFormatterNames.Json, formatterName => new JsonConsoleFormatter(jsonMonitor));
}
else
{
- _messageQueue.Console = new AnsiLogConsole(new AnsiSystemConsole());
- _messageQueue.ErrorConsole = new AnsiLogConsole(new AnsiSystemConsole(stdErr: true));
+ foreach (ConsoleFormatter formatter in formatters)
+ {
+ _formatters.GetOrAdd(formatter.Name, formatterName => formatter);
+ }
}
}
+ // warning: ReloadLoggerOptions can be called before the ctor completed,... before registering all of the state used in this method need to be initialized
private void ReloadLoggerOptions(ConsoleLoggerOptions options)
{
- foreach (System.Collections.Generic.KeyValuePair<string, ConsoleLogger> logger in _loggers)
+ if (options.FormatterName == null || !_formatters.TryGetValue(options.FormatterName, out ConsoleFormatter logFormatter))
+ {
+#pragma warning disable CS0618
+ logFormatter = options.Format switch
+ {
+ ConsoleLoggerFormat.Systemd => _formatters[ConsoleFormatterNames.Systemd],
+ _ => _formatters[ConsoleFormatterNames.Simple],
+ };
+ if (options.FormatterName == null)
+ {
+ UpdateFormatterOptions(logFormatter, options);
+ }
+#pragma warning restore CS0618
+ }
+
+ foreach (KeyValuePair<string, ConsoleLogger> logger in _loggers)
{
logger.Value.Options = options;
+ logger.Value.Formatter = logFormatter;
}
}
/// <inheritdoc />
public ILogger CreateLogger(string name)
{
+ if (_options.CurrentValue.FormatterName == null || !_formatters.TryGetValue(_options.CurrentValue.FormatterName, out ConsoleFormatter logFormatter))
+ {
+#pragma warning disable CS0618
+ logFormatter = _options.CurrentValue.Format switch
+ {
+ ConsoleLoggerFormat.Systemd => _formatters[ConsoleFormatterNames.Systemd],
+ _ => _formatters[ConsoleFormatterNames.Simple],
+ };
+ if (_options.CurrentValue.FormatterName == null)
+ {
+ UpdateFormatterOptions(logFormatter, _options.CurrentValue);
+ }
+#pragma warning disable CS0618
+ }
+
return _loggers.GetOrAdd(name, loggerName => new ConsoleLogger(name, _messageQueue)
{
Options = _options.CurrentValue,
- ScopeProvider = _scopeProvider
+ ScopeProvider = _scopeProvider,
+ Formatter = logFormatter,
});
}
+#pragma warning disable CS0618
+ private void UpdateFormatterOptions(ConsoleFormatter formatter, ConsoleLoggerOptions deprecatedFromOptions)
+ {
+ // kept for deprecated apis:
+ if (formatter is SimpleConsoleFormatter defaultFormatter)
+ {
+ defaultFormatter.FormatterOptions = new SimpleConsoleFormatterOptions()
+ {
+ DisableColors = deprecatedFromOptions.DisableColors,
+ IncludeScopes = deprecatedFromOptions.IncludeScopes,
+ TimestampFormat = deprecatedFromOptions.TimestampFormat,
+ UseUtcTimestamp = deprecatedFromOptions.UseUtcTimestamp,
+ };
+ }
+ else
+ if (formatter is SystemdConsoleFormatter systemdFormatter)
+ {
+ systemdFormatter.FormatterOptions = new ConsoleFormatterOptions()
+ {
+ IncludeScopes = deprecatedFromOptions.IncludeScopes,
+ TimestampFormat = deprecatedFromOptions.TimestampFormat,
+ UseUtcTimestamp = deprecatedFromOptions.UseUtcTimestamp,
+ };
+ }
+ }
+#pragma warning restore CS0618
/// <inheritdoc />
public void Dispose()
{
logger.Value.ScopeProvider = _scopeProvider;
}
-
}
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Text;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.Logging.Console
+{
+ internal class FormatterOptionsMonitor<TOptions> : IOptionsMonitor<TOptions> where TOptions : ConsoleFormatterOptions
+ {
+ private TOptions _options;
+ public FormatterOptionsMonitor(TOptions options)
+ {
+ _options = options;
+ }
+
+ public TOptions Get(string name) => _options;
+
+ public IDisposable OnChange(Action<TOptions, string> listener)
+ {
+ return null;
+ }
+
+ public TOptions CurrentValue => _options;
+ }
+}
\ No newline at end of file
{
internal interface IConsole
{
- void Write(string message, ConsoleColor? background, ConsoleColor? foreground);
- void WriteLine(string message, ConsoleColor? background, ConsoleColor? foreground);
- void Flush();
+ void Write(string message);
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Buffers;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.Logging.Console
+{
+ internal class JsonConsoleFormatter : ConsoleFormatter, IDisposable
+ {
+ private IDisposable _optionsReloadToken;
+
+ public JsonConsoleFormatter(IOptionsMonitor<JsonConsoleFormatterOptions> options)
+ : base (ConsoleFormatterNames.Json)
+ {
+ ReloadLoggerOptions(options.CurrentValue);
+ _optionsReloadToken = options.OnChange(ReloadLoggerOptions);
+ }
+
+ public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider scopeProvider, TextWriter textWriter)
+ {
+ string message = logEntry.Formatter(logEntry.State, logEntry.Exception);
+ if (logEntry.Exception == null && message == null)
+ {
+ return;
+ }
+ LogLevel logLevel = logEntry.LogLevel;
+ string category = logEntry.Category;
+ int eventId = logEntry.EventId.Id;
+ Exception exception = logEntry.Exception;
+ const int DefaultBufferSize = 1024;
+ using (var output = new PooledByteBufferWriter(DefaultBufferSize))
+ {
+ using (var writer = new Utf8JsonWriter(output, FormatterOptions.JsonWriterOptions))
+ {
+ writer.WriteStartObject();
+ string timestamp = null;
+ var timestampFormat = FormatterOptions.TimestampFormat;
+ if (timestampFormat != null)
+ {
+ var dateTime = FormatterOptions.UseUtcTimestamp ? DateTime.UtcNow : DateTime.Now;
+ timestamp = dateTime.ToString(timestampFormat);
+ }
+ writer.WriteString("Timestamp", timestamp);
+ writer.WriteNumber(nameof(logEntry.EventId), eventId);
+ writer.WriteString(nameof(logEntry.LogLevel), GetLogLevelString(logLevel));
+ writer.WriteString(nameof(logEntry.Category), category);
+ writer.WriteString("Message", message);
+
+ if (exception != null)
+ {
+ writer.WriteStartObject(nameof(Exception));
+ writer.WriteString(nameof(exception.Message), exception.Message.ToString());
+ writer.WriteString("Type", exception.GetType().ToString());
+ writer.WriteStartArray(nameof(exception.StackTrace));
+ string stackTrace = exception?.StackTrace;
+ if (stackTrace != null)
+ {
+#if (NETSTANDARD2_0 || NETFRAMEWORK)
+ foreach (var stackTraceLines in stackTrace?.Split(new string[] { Environment.NewLine }, StringSplitOptions.None))
+#else
+ foreach (var stackTraceLines in stackTrace?.Split(Environment.NewLine))
+#endif
+ {
+ writer.WriteStringValue(stackTraceLines);
+ }
+ }
+ writer.WriteEndArray();
+ writer.WriteNumber(nameof(exception.HResult), exception.HResult);
+ writer.WriteEndObject();
+ }
+
+ if (logEntry.State is IReadOnlyCollection<KeyValuePair<string, object>> stateDictionary)
+ {
+ foreach (KeyValuePair<string, object> item in stateDictionary)
+ {
+ writer.WriteString(item.Key, Convert.ToString(item.Value, CultureInfo.InvariantCulture));
+ }
+ }
+ WriteScopeInformation(writer, scopeProvider);
+ writer.WriteEndObject();
+ writer.Flush();
+ }
+#if (NETSTANDARD2_0 || NETFRAMEWORK)
+ textWriter.Write(Encoding.UTF8.GetString(output.WrittenMemory.Span.ToArray()));
+#else
+ textWriter.Write(Encoding.UTF8.GetString(output.WrittenMemory.Span));
+#endif
+ }
+ textWriter.Write(Environment.NewLine);
+ }
+
+ private static string GetLogLevelString(LogLevel logLevel)
+ {
+ return logLevel switch
+ {
+ LogLevel.Trace => "Trace",
+ LogLevel.Debug => "Debug",
+ LogLevel.Information => "Information",
+ LogLevel.Warning => "Warning",
+ LogLevel.Error => "Error",
+ LogLevel.Critical => "Critical",
+ _ => throw new ArgumentOutOfRangeException(nameof(logLevel))
+ };
+ }
+
+ private void WriteScopeInformation(Utf8JsonWriter writer, IExternalScopeProvider scopeProvider)
+ {
+ if (FormatterOptions.IncludeScopes && scopeProvider != null)
+ {
+ int numScopes = 0;
+ writer.WriteStartObject("Scopes");
+ scopeProvider.ForEachScope((scope, state) =>
+ {
+ if (scope is IReadOnlyCollection<KeyValuePair<string, object>> scopeDictionary)
+ {
+ foreach (KeyValuePair<string, object> item in scopeDictionary)
+ {
+ state.WriteString(item.Key, Convert.ToString(item.Value, CultureInfo.InvariantCulture));
+ }
+ }
+ else
+ {
+ state.WriteString(numScopes++.ToString(), scope.ToString());
+ }
+ }, writer);
+ writer.WriteEndObject();
+ }
+ }
+
+ internal JsonConsoleFormatterOptions FormatterOptions { get; set; }
+
+ private void ReloadLoggerOptions(JsonConsoleFormatterOptions options)
+ {
+ FormatterOptions = options;
+ }
+
+ public void Dispose()
+ {
+ _optionsReloadToken?.Dispose();
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Text;
+using System.Text.Json;
+
+namespace Microsoft.Extensions.Logging.Console
+{
+ /// <summary>
+ /// Options for the built-in json console log formatter.
+ /// </summary>
+ public class JsonConsoleFormatterOptions : ConsoleFormatterOptions
+ {
+ public JsonConsoleFormatterOptions() { }
+
+ /// <summary>
+ /// Gets or sets JsonWriterOptions.
+ /// </summary>
+ public JsonWriterOptions JsonWriterOptions { get; set; }
+ }
+}
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Collections.Concurrent;
namespace Microsoft.Extensions.Logging.Console
{
internal readonly struct LogMessageEntry
{
- public LogMessageEntry(string message, string timeStamp = null, string levelString = null, ConsoleColor? levelBackground = null, ConsoleColor? levelForeground = null, ConsoleColor? messageColor = null, bool logAsError = false)
+ public LogMessageEntry(string message, bool logAsError = false)
{
- TimeStamp = timeStamp;
- LevelString = levelString;
- LevelBackground = levelBackground;
- LevelForeground = levelForeground;
- MessageColor = messageColor;
Message = message;
LogAsError = logAsError;
}
- public readonly string TimeStamp;
- public readonly string LevelString;
- public readonly ConsoleColor? LevelBackground;
- public readonly ConsoleColor? LevelForeground;
- public readonly ConsoleColor? MessageColor;
public readonly string Message;
public readonly bool LogAsError;
}
<TargetFrameworks>$(NetCoreAppCurrent);netstandard2.0;net461;$(NetFrameworkCurrent)</TargetFrameworks>
<ExcludeCurrentFullFrameworkFromPackage>true</ExcludeCurrentFullFrameworkFromPackage>
<EnableDefaultItems>true</EnableDefaultItems>
+ <Nullable>annotations</Nullable>
</PropertyGroup>
<PropertyGroup>
<!-- Ensure Assemblies are first resolved via targeting pack when targeting net461 -->
Link="Common\src\Extensions\Logging\NullExternalScopeProvider.cs" />
<Compile Include="$(CommonPath)Extensions\Logging\NullScope.cs"
Link="Common\src\Extensions\Logging\NullScope.cs" />
+ <Compile Include="$(CommonPath)System\Text\Json\PooledByteBufferWriter.cs"
+ Link="Common\System\Text\Json\PooledByteBufferWriter.cs" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs"
+ Link="Common\Interop\Windows\Interop.Libraries.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetConsoleMode.cs"
+ Link="Common\Interop\Windows\Interop.GetConsoleMode.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetStdHandle.cs"
+ Link="Common\Interop\Windows\Interop.GetStdHandle.cs" />
+ </ItemGroup>
+
+ <ItemGroup Condition="'$(TargetFramework)' != '$(NetCoreAppCurrent)'">
+ <Compile Include="$(CommonPath)System\Runtime\InteropServices\SuppressGCTransitionAttribute.internal.cs"
+ Link="Common\System\Runtime\InteropServices\SuppressGCTransitionAttribute.internal.cs" />
+ </ItemGroup>
+
+ <ItemGroup Condition="'$(TargetFramework)' != '$(NetCoreAppCurrent)'">
+ <Reference Include="System.Buffers" />
+ <Reference Include="Microsoft.Bcl.AsyncInterfaces" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="System.Collections" />
+ <Reference Include="System.Linq" />
+ <Reference Include="System.Memory" />
+ <Reference Include="System.Text.Json" />
+ <Reference Include="System.Runtime.InteropServices" />
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<Reference Include="Microsoft.Extensions.Logging" />
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
<Reference Include="Microsoft.Extensions.Logging.Configuration" />
<Reference Include="Microsoft.Extensions.Options" />
+ <Reference Include="Microsoft.Extensions.Options.ConfigurationExtensions" />
+ <Reference Include="Microsoft.Extensions.Configuration.Abstractions" />
+ <Reference Include="Microsoft.Extensions.Configuration" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.StartsWith('net4'))">
<Reference Include="mscorlib" />
<Reference Include="System" />
+ <Reference Include="System.Core" />
<Reference Include="System.ValueTuple" />
<Reference Include="System.Runtime" />
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.3.0" Condition="'$(TargetFramework)' == 'net461'" />
using System.Runtime.CompilerServices;
-[assembly: InternalsVisibleTo("Microsoft.Extensions.Logging.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+[assembly: InternalsVisibleTo("Microsoft.Extensions.Logging.Console.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="BufferMaximumSizeExceeded" xml:space="preserve">
+ <value>Cannot allocate a buffer of size {0}.</value>
+ </data>
+</root>
\ No newline at end of file
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.Logging.Console
+{
+ internal class SimpleConsoleFormatter : ConsoleFormatter, IDisposable
+ {
+ private const string LoglevelPadding = ": ";
+ private static readonly string _messagePadding = new string(' ', GetLogLevelString(LogLevel.Information).Length + LoglevelPadding.Length);
+ private static readonly string _newLineWithMessagePadding = Environment.NewLine + _messagePadding;
+ private IDisposable _optionsReloadToken;
+
+ public SimpleConsoleFormatter(IOptionsMonitor<SimpleConsoleFormatterOptions> options)
+ : base (ConsoleFormatterNames.Simple)
+ {
+ ReloadLoggerOptions(options.CurrentValue);
+ _optionsReloadToken = options.OnChange(ReloadLoggerOptions);
+ }
+
+ private void ReloadLoggerOptions(SimpleConsoleFormatterOptions options)
+ {
+ FormatterOptions = options;
+ }
+
+ public void Dispose()
+ {
+ _optionsReloadToken?.Dispose();
+ }
+
+ internal SimpleConsoleFormatterOptions FormatterOptions { get; set; }
+
+ public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider scopeProvider, TextWriter textWriter)
+ {
+ string message = logEntry.Formatter(logEntry.State, logEntry.Exception);
+ if (logEntry.Exception == null && message == null)
+ {
+ return;
+ }
+ LogLevel logLevel = logEntry.LogLevel;
+ ConsoleColors logLevelColors = GetLogLevelConsoleColors(logLevel);
+ string logLevelString = GetLogLevelString(logLevel);
+
+ string timestamp = null;
+ string timestampFormat = FormatterOptions.TimestampFormat;
+ if (timestampFormat != null)
+ {
+ DateTime dateTime = GetCurrentDateTime();
+ timestamp = dateTime.ToString(timestampFormat);
+ }
+ if (timestamp != null)
+ {
+ textWriter.Write(timestamp);
+ }
+ if (logLevelString != null)
+ {
+ textWriter.WriteColoredMessage(logLevelString, logLevelColors.Background, logLevelColors.Foreground);
+ }
+ CreateDefaultLogMessage(textWriter, logEntry, message, scopeProvider);
+ }
+
+ private void CreateDefaultLogMessage<TState>(TextWriter textWriter, in LogEntry<TState> logEntry, string message, IExternalScopeProvider scopeProvider)
+ {
+ bool singleLine = FormatterOptions.SingleLine;
+ int eventId = logEntry.EventId.Id;
+ Exception exception = logEntry.Exception;
+
+ // Example:
+ // info: ConsoleApp.Program[10]
+ // Request received
+
+ // category and event id
+ textWriter.Write(LoglevelPadding + logEntry.Category + '[' + eventId + "]");
+ if (!singleLine)
+ {
+ textWriter.Write(Environment.NewLine);
+ }
+
+ // scope information
+ WriteScopeInformation(textWriter, scopeProvider, singleLine);
+ if (singleLine)
+ {
+ textWriter.Write(' ');
+ }
+ WriteMessage(textWriter, message, singleLine);
+
+ // Example:
+ // System.InvalidOperationException
+ // at Namespace.Class.Function() in File:line X
+ if (exception != null)
+ {
+ // exception message
+ WriteMessage(textWriter, exception.ToString(), singleLine);
+ }
+ if (singleLine)
+ {
+ textWriter.Write(Environment.NewLine);
+ }
+ }
+
+ private void WriteMessage(TextWriter textWriter, string message, bool singleLine)
+ {
+ if (!string.IsNullOrEmpty(message))
+ {
+ if (singleLine)
+ {
+ WriteReplacing(textWriter, Environment.NewLine, " ", message);
+ }
+ else
+ {
+ textWriter.Write(_messagePadding);
+ WriteReplacing(textWriter, Environment.NewLine, _newLineWithMessagePadding, message);
+ textWriter.Write(Environment.NewLine);
+ }
+ }
+
+ static void WriteReplacing(TextWriter writer, string oldValue, string newValue, string message)
+ {
+ string newMessage = message.Replace(oldValue, newValue);
+ writer.Write(newMessage);
+ }
+ }
+
+ private DateTime GetCurrentDateTime()
+ {
+ return FormatterOptions.UseUtcTimestamp ? DateTime.UtcNow : DateTime.Now;
+ }
+
+ private static string GetLogLevelString(LogLevel logLevel)
+ {
+ return logLevel switch
+ {
+ LogLevel.Trace => "trce",
+ LogLevel.Debug => "dbug",
+ LogLevel.Information => "info",
+ LogLevel.Warning => "warn",
+ LogLevel.Error => "fail",
+ LogLevel.Critical => "crit",
+ _ => throw new ArgumentOutOfRangeException(nameof(logLevel))
+ };
+ }
+
+ private ConsoleColors GetLogLevelConsoleColors(LogLevel logLevel)
+ {
+ if (FormatterOptions.DisableColors)
+ {
+ return new ConsoleColors(null, null);
+ }
+ // We must explicitly set the background color if we are setting the foreground color,
+ // since just setting one can look bad on the users console.
+ return logLevel switch
+ {
+ LogLevel.Trace => new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black),
+ LogLevel.Debug => new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black),
+ LogLevel.Information => new ConsoleColors(ConsoleColor.DarkGreen, ConsoleColor.Black),
+ LogLevel.Warning => new ConsoleColors(ConsoleColor.Yellow, ConsoleColor.Black),
+ LogLevel.Error => new ConsoleColors(ConsoleColor.Black, ConsoleColor.DarkRed),
+ LogLevel.Critical => new ConsoleColors(ConsoleColor.White, ConsoleColor.DarkRed),
+ _ => new ConsoleColors(null, null)
+ };
+ }
+
+ private void WriteScopeInformation(TextWriter textWriter, IExternalScopeProvider scopeProvider, bool singleLine)
+ {
+ if (FormatterOptions.IncludeScopes && scopeProvider != null)
+ {
+ bool paddingNeeded = !singleLine;
+ scopeProvider.ForEachScope((scope, state) =>
+ {
+ if (paddingNeeded)
+ {
+ paddingNeeded = false;
+ state.Write(_messagePadding + "=> ");
+ }
+ else
+ {
+ state.Write(" => ");
+ }
+ state.Write(scope);
+ }, textWriter);
+
+ if (!paddingNeeded && !singleLine)
+ {
+ textWriter.Write(Environment.NewLine);
+ }
+ }
+ }
+
+ private readonly struct ConsoleColors
+ {
+ public ConsoleColors(ConsoleColor? foreground, ConsoleColor? background)
+ {
+ Foreground = foreground;
+ Background = background;
+ }
+
+ public ConsoleColor? Foreground { get; }
+
+ public ConsoleColor? Background { get; }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.Extensions.Logging.Console
+{
+ /// <summary>
+ /// Options for the built-in default console log formatter.
+ /// </summary>
+ public class SimpleConsoleFormatterOptions : ConsoleFormatterOptions
+ {
+ public SimpleConsoleFormatterOptions() { }
+
+ /// <summary>
+ /// Disables colors when <see langword="true" />.
+ /// </summary>
+ public bool DisableColors { get; set; }
+
+ /// <summary>
+ /// When <see langword="false" />, the entire message gets logged in a single line.
+ /// </summary>
+ public bool SingleLine { get; set; }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Text;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.Extensions.Logging.Console
+{
+ internal class SystemdConsoleFormatter : ConsoleFormatter, IDisposable
+ {
+ private IDisposable _optionsReloadToken;
+
+ public SystemdConsoleFormatter(IOptionsMonitor<ConsoleFormatterOptions> options)
+ : base(ConsoleFormatterNames.Systemd)
+ {
+ ReloadLoggerOptions(options.CurrentValue);
+ _optionsReloadToken = options.OnChange(ReloadLoggerOptions);
+ }
+
+ private void ReloadLoggerOptions(ConsoleFormatterOptions options)
+ {
+ FormatterOptions = options;
+ }
+
+ public void Dispose()
+ {
+ _optionsReloadToken?.Dispose();
+ }
+
+ internal ConsoleFormatterOptions FormatterOptions { get; set; }
+
+ public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider scopeProvider, TextWriter textWriter)
+ {
+ string message = logEntry.Formatter(logEntry.State, logEntry.Exception);
+ if (logEntry.Exception == null && message == null)
+ {
+ return;
+ }
+ LogLevel logLevel = logEntry.LogLevel;
+ string category = logEntry.Category;
+ int eventId = logEntry.EventId.Id;
+ Exception exception = logEntry.Exception;
+ // systemd reads messages from standard out line-by-line in a '<pri>message' format.
+ // newline characters are treated as message delimiters, so we must replace them.
+ // Messages longer than the journal LineMax setting (default: 48KB) are cropped.
+ // Example:
+ // <6>ConsoleApp.Program[10] Request received
+
+ // loglevel
+ string logLevelString = GetSyslogSeverityString(logLevel);
+ textWriter.Write(logLevelString);
+
+ // timestamp
+ string timestampFormat = FormatterOptions.TimestampFormat;
+ if (timestampFormat != null)
+ {
+ DateTime dateTime = GetCurrentDateTime();
+ textWriter.Write(dateTime.ToString(timestampFormat));
+ }
+
+ // category and event id
+ textWriter.Write(category);
+ textWriter.Write('[');
+ textWriter.Write(eventId);
+ textWriter.Write(']');
+
+ // scope information
+ WriteScopeInformation(textWriter, scopeProvider);
+
+ // message
+ if (!string.IsNullOrEmpty(message))
+ {
+ textWriter.Write(' ');
+ // message
+ WriteReplacingNewLine(textWriter, message);
+ }
+
+ // exception
+ // System.InvalidOperationException at Namespace.Class.Function() in File:line X
+ if (exception != null)
+ {
+ textWriter.Write(' ');
+ WriteReplacingNewLine(textWriter, exception.ToString());
+ }
+
+ // newline delimiter
+ textWriter.Write(Environment.NewLine);
+
+ static void WriteReplacingNewLine(TextWriter writer, string message)
+ {
+ string newMessage = message.Replace(Environment.NewLine, " ");
+ writer.Write(newMessage);
+ }
+ }
+
+ private DateTime GetCurrentDateTime()
+ {
+ return FormatterOptions.UseUtcTimestamp ? DateTime.UtcNow : DateTime.Now;
+ }
+
+ private static string GetSyslogSeverityString(LogLevel logLevel)
+ {
+ // 'Syslog Message Severities' from https://tools.ietf.org/html/rfc5424.
+ return logLevel switch
+ {
+ LogLevel.Trace => "<7>",
+ LogLevel.Debug => "<7>", // debug-level messages
+ LogLevel.Information => "<6>", // informational messages
+ LogLevel.Warning => "<4>", // warning conditions
+ LogLevel.Error => "<3>", // error conditions
+ LogLevel.Critical => "<2>", // critical conditions
+ _ => throw new ArgumentOutOfRangeException(nameof(logLevel))
+ };
+ }
+
+ private void WriteScopeInformation(TextWriter textWriter, IExternalScopeProvider scopeProvider)
+ {
+ if (FormatterOptions.IncludeScopes && scopeProvider != null)
+ {
+ scopeProvider.ForEachScope((scope, state) =>
+ {
+ state.Write(" => ");
+ state.Write(scope);
+ }, textWriter);
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using System.Text;
+using System.Collections.Concurrent;
+
+namespace Microsoft.Extensions.Logging.Console
+{
+ internal static class TextWriterExtensions
+ {
+ public static void WriteColoredMessage(this TextWriter textWriter, string message, ConsoleColor? background, ConsoleColor? foreground)
+ {
+ // Order: backgroundcolor, foregroundcolor, Message, reset foregroundcolor, reset backgroundcolor
+ if (background.HasValue)
+ {
+ textWriter.Write(AnsiParser.GetBackgroundColorEscapeCode(background.Value));
+ }
+ if (foreground.HasValue)
+ {
+ textWriter.Write(AnsiParser.GetForegroundColorEscapeCode(foreground.Value));
+ }
+ textWriter.Write(message);
+ if (foreground.HasValue)
+ {
+ textWriter.Write(AnsiParser.DefaultForegroundColor); // reset to default foreground color
+ }
+ if (background.HasValue)
+ {
+ textWriter.Write(AnsiParser.DefaultBackgroundColor); // reset to the background color
+ }
+ }
+ }
+}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using System.IO;
-
-namespace Microsoft.Extensions.Logging.Console
-{
- internal class WindowsLogConsole : IConsole
- {
- private readonly TextWriter _textWriter;
-
- public WindowsLogConsole(bool stdErr = false)
- {
- _textWriter = stdErr ? System.Console.Error : System.Console.Out;
- }
-
- private bool SetColor(ConsoleColor? background, ConsoleColor? foreground)
- {
- if (background.HasValue)
- {
- System.Console.BackgroundColor = background.Value;
- }
-
- if (foreground.HasValue)
- {
- System.Console.ForegroundColor = foreground.Value;
- }
-
- return background.HasValue || foreground.HasValue;
- }
-
- private void ResetColor()
- {
- System.Console.ResetColor();
- }
-
- public void Write(string message, ConsoleColor? background, ConsoleColor? foreground)
- {
- bool colorChanged = SetColor(background, foreground);
- _textWriter.Write(message);
- if (colorChanged)
- {
- ResetColor();
- }
- }
-
- public void WriteLine(string message, ConsoleColor? background, ConsoleColor? foreground)
- {
- bool colorChanged = SetColor(background, foreground);
- _textWriter.WriteLine(message);
- if (colorChanged)
- {
- ResetColor();
- }
- }
-
- public void Flush()
- {
- // No action required as for every write, data is sent directly to the console
- // output stream
- }
- }
-}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using Microsoft.Extensions.Logging.Test.Console;
+using Microsoft.Extensions.Logging.Console;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.Console.Test
+{
+ public class AnsiParserTests
+ {
+ private const char EscapeChar = '\x1B';
+
+ [Theory]
+ [InlineData(1, "No Color", "No Color")]
+ [InlineData(2, "\x1B[41mColored\x1B[49mNo Color", "No Color")]
+ [InlineData(2, "\x1B[41m\x1B[1m\x1B[31mmColored\x1B[39m\x1B[49mNo Color", "No Color")]
+ public void Parse_CheckTimesWrittenToConsole(int numSegments, string message, string lastSegment)
+ {
+ // Arrange
+ var segments = new List<ConsoleContext>();
+ Action<string, int, int, ConsoleColor?, ConsoleColor?> onParseWrite = (message, startIndex, length, bg, fg) => {
+ segments.Add(new ConsoleContext() {
+ BackgroundColor = bg,
+ ForegroundColor = fg,
+ Message = message.AsSpan().Slice(startIndex, length).ToString()
+ });
+ };
+ var parser = new AnsiParser(onParseWrite);
+
+ // Act
+ parser.Parse(message);
+
+ // Assert
+ Assert.Equal(numSegments, segments.Count);
+ Assert.Equal(lastSegment, segments.Last().Message);
+ }
+
+ [Theory]
+ [MemberData(nameof(Colors))]
+ public void Parse_SetBackgroundForegroundAndMessageThenReset_Success(ConsoleColor background, ConsoleColor foreground)
+ {
+ // Arrange
+ var message = AnsiParser.GetBackgroundColorEscapeCode(background)
+ + AnsiParser.GetForegroundColorEscapeCode(foreground)
+ + "Request received"
+ + AnsiParser.DefaultForegroundColor //resets foreground color
+ + AnsiParser.DefaultBackgroundColor; //resets background color
+ var segments = new List<ConsoleContext>();
+ Action<string, int, int, ConsoleColor?, ConsoleColor?> onParseWrite = (message, startIndex, length, bg, fg) => {
+ segments.Add(new ConsoleContext() {
+ BackgroundColor = bg,
+ ForegroundColor = fg,
+ Message = message.AsSpan().Slice(startIndex, length).ToString()
+ });
+ };
+ var parser = new AnsiParser(onParseWrite);
+
+ // Act
+ parser.Parse(message);
+
+ // Assert
+ Assert.Equal(1, segments.Count);
+ Assert.Equal("Request received", segments[0].Message);
+ VerifyForeground(foreground, segments[0]);
+ VerifyBackground(background, segments[0]);
+ }
+
+ [Fact]
+ public void Parse_MessageWithMultipleColors_ParsedIntoMultipleSegments()
+ {
+ // Arrange
+ var message = AnsiParser.GetBackgroundColorEscapeCode(ConsoleColor.DarkRed)
+ + AnsiParser.GetForegroundColorEscapeCode(ConsoleColor.Gray)
+ + "Message1"
+ + AnsiParser.DefaultForegroundColor
+ + AnsiParser.DefaultBackgroundColor
+ + "NoColor"
+ + AnsiParser.GetBackgroundColorEscapeCode(ConsoleColor.DarkGreen)
+ + AnsiParser.GetForegroundColorEscapeCode(ConsoleColor.Cyan)
+ + "Message2"
+ + AnsiParser.GetBackgroundColorEscapeCode(ConsoleColor.DarkBlue)
+ + AnsiParser.GetForegroundColorEscapeCode(ConsoleColor.Yellow)
+ + "Message3"
+ + AnsiParser.DefaultForegroundColor
+ + AnsiParser.DefaultBackgroundColor;
+ var segments = new List<ConsoleContext>();
+ Action<string, int, int, ConsoleColor?, ConsoleColor?> onParseWrite = (message, startIndex, length, bg, fg) => {
+ segments.Add(new ConsoleContext() {
+ BackgroundColor = bg,
+ ForegroundColor = fg,
+ Message = message.AsSpan().Slice(startIndex, length).ToString()
+ });
+ };
+ var parser = new AnsiParser(onParseWrite);
+
+ // Act
+ parser.Parse(message);
+
+ // Assert
+ Assert.Equal(4, segments.Count);
+ Assert.Equal("NoColor", segments[1].Message);
+ Assert.Null(segments[1].ForegroundColor);
+ Assert.Null(segments[1].BackgroundColor);
+
+ Assert.Equal("Message1", segments[0].Message);
+ Assert.Equal("Message2", segments[2].Message);
+ Assert.Equal("Message3", segments[3].Message);
+ VerifyBackground(ConsoleColor.DarkRed, segments[0]);
+ VerifyBackground(ConsoleColor.DarkGreen, segments[2]);
+ VerifyBackground(ConsoleColor.DarkBlue, segments[3]);
+ VerifyForeground(ConsoleColor.Gray, segments[0]);
+ VerifyForeground(ConsoleColor.Cyan, segments[2]);
+ VerifyForeground(ConsoleColor.Yellow, segments[3]);
+ }
+
+ [Fact]
+ public void Parse_RepeatedColorChange_PicksLastSet()
+ {
+ // Arrange
+ var message = AnsiParser.GetBackgroundColorEscapeCode(ConsoleColor.DarkRed)
+ + AnsiParser.GetBackgroundColorEscapeCode(ConsoleColor.DarkGreen)
+ + AnsiParser.GetBackgroundColorEscapeCode(ConsoleColor.DarkBlue)
+ + AnsiParser.GetForegroundColorEscapeCode(ConsoleColor.Gray)
+ + AnsiParser.GetForegroundColorEscapeCode(ConsoleColor.Cyan)
+ + AnsiParser.GetForegroundColorEscapeCode(ConsoleColor.Yellow)
+ + "Request received"
+ + AnsiParser.DefaultForegroundColor //resets foreground color
+ + AnsiParser.DefaultBackgroundColor; //resets background color
+ var segments = new List<ConsoleContext>();
+ Action<string, int, int, ConsoleColor?, ConsoleColor?> onParseWrite = (message, startIndex, length, bg, fg) => {
+ segments.Add(new ConsoleContext() {
+ BackgroundColor = bg,
+ ForegroundColor = fg,
+ Message = message.AsSpan().Slice(startIndex, length).ToString()
+ });
+ };
+ var parser = new AnsiParser(onParseWrite);
+
+ // Act
+ parser.Parse(message);
+
+ // Assert
+ Assert.Equal(1, segments.Count);
+ Assert.Equal("Request received", segments[0].Message);
+ VerifyBackground(ConsoleColor.DarkBlue, segments[0]);
+ VerifyForeground(ConsoleColor.Yellow, segments[0]);
+ }
+
+ [Theory]
+ // supported
+ [InlineData("\x1B[77mInfo", "Info")]
+ [InlineData("\x1B[77m\x1B[1m\x1B[2m\x1B[0mInfo\x1B[1m", "Info")]
+ [InlineData("\x1B[7mInfo", "Info")]
+ [InlineData("\x1B[40m\x1B[1m\x1B[33mwarn\x1B[39m\x1B[22m\x1B[49m:", "warn", ":")]
+ // unsupported: skips
+ [InlineData("Info\x1B[77m:", "Info", ":")]
+ [InlineData("Info\x1B[7m:", "Info", ":")]
+ // treats as content
+ [InlineData("\x1B", "\x1B")]
+ [InlineData("\x1B ", "\x1B ")]
+ [InlineData("\x1Bm", "\x1Bm")]
+ [InlineData("\x1B m", "\x1B m")]
+ [InlineData("\x1Bxym", "\x1Bxym")]
+ [InlineData("\x1B[", "\x1B[")]
+ [InlineData("\x1B[m", "\x1B[m")]
+ [InlineData("\x1B[ ", "\x1B[ ")]
+ [InlineData("\x1B[ m", "\x1B[ m")]
+ [InlineData("\x1B[xym", "\x1B[xym")]
+ [InlineData("\x1B[7777m", "\x1B[7777m")]
+ [InlineData("\x1B\x1B\x1B", "\x1B\x1B\x1B")]
+ [InlineData("Message\x1B\x1B\x1B", "Message\x1B\x1B\x1B")]
+ [InlineData("\x1B\x1BMessage\x1B", "\x1B\x1BMessage\x1B")]
+ [InlineData("\x1B\x1B\x1BMessage", "\x1B\x1B\x1BMessage")]
+ [InlineData("Message\x1B ", "Message\x1B ")]
+ [InlineData("\x1BmMessage", "\x1BmMessage")]
+ [InlineData("\x1B[77m\x1B m\x1B[40m", "\x1B m")]
+ [InlineData("\x1B mMessage\x1Bxym", "\x1B mMessage\x1Bxym")]
+ public void Parse_ValidSupportedOrUnsupportedCodesInMessage_MessageParsedSuccessfully(string messageWithUnsupportedCode, params string[] output)
+ {
+ // Arrange
+ var message = messageWithUnsupportedCode;
+ var segments = new List<ConsoleContext>();
+ Action<string, int, int, ConsoleColor?, ConsoleColor?> onParseWrite = (message, startIndex, length, bg, fg) => {
+ segments.Add(new ConsoleContext() {
+ BackgroundColor = bg,
+ ForegroundColor = fg,
+ Message = message.AsSpan().Slice(startIndex, length).ToString()
+ });
+ };
+ var parser = new AnsiParser(onParseWrite);
+
+ // Act
+ parser.Parse(messageWithUnsupportedCode);
+
+ // Assert
+ Assert.Equal(output.Length, segments.Count);
+ for (int i = 0; i < output.Length; i++)
+ Assert.Equal(output[i], segments[i].Message);
+ }
+
+ [Fact]
+ public void NullDelegate_Throws()
+ {
+ Assert.Throws<ArgumentNullException>(() => new AnsiParser(null));
+ }
+
+ public static TheoryData<ConsoleColor, ConsoleColor> Colors
+ {
+ get
+ {
+ var data = new TheoryData<ConsoleColor, ConsoleColor>();
+ foreach (ConsoleColor background in Enum.GetValues(typeof(ConsoleColor)))
+ {
+ foreach (ConsoleColor foreground in Enum.GetValues(typeof(ConsoleColor)))
+ {
+ data.Add(background, foreground);
+ }
+ }
+ return data;
+ }
+ }
+
+ private void VerifyBackground(ConsoleColor background, ConsoleContext segment)
+ {
+ if (IsBackgroundColorNotSupported(background))
+ {
+ Assert.Null(segment.BackgroundColor);
+ }
+ else
+ {
+ Assert.Equal(background, segment.BackgroundColor);
+ }
+ }
+
+ private void VerifyForeground(ConsoleColor foreground, ConsoleContext segment)
+ {
+ if (IsForegroundColorNotSupported(foreground))
+ {
+ Assert.Null(segment.ForegroundColor);
+ }
+ else
+ {
+ Assert.Equal(foreground, segment.ForegroundColor);
+ }
+ }
+
+ private class TestAnsiSystemConsole : IAnsiSystemConsole
+ {
+ public string Message { get; private set; }
+
+ public void Write(string message)
+ {
+ Message = message;
+ }
+ }
+
+ private static bool IsBackgroundColorNotSupported(ConsoleColor color)
+ {
+ return AnsiParser.GetBackgroundColorEscapeCode(color).Equals(
+ AnsiParser.DefaultBackgroundColor, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private static bool IsForegroundColorNotSupported(ConsoleColor color)
+ {
+ return AnsiParser.GetForegroundColorEscapeCode(color).Equals(
+ AnsiParser.DefaultForegroundColor, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+}
\ No newline at end of file
public static readonly ConsoleColor? DefaultForegroundColor;
private ConsoleSink _sink;
+ private AnsiParser _parser;
public TestConsole(ConsoleSink sink)
{
_sink = sink;
+ _parser = new AnsiParser(OnParseWrite);
BackgroundColor = DefaultBackgroundColor;
ForegroundColor = DefaultForegroundColor;
}
public ConsoleColor? ForegroundColor { get; private set; }
- public void Write(string message, ConsoleColor? background, ConsoleColor? foreground)
+ public void Write(string message)
+ {
+ _parser.Parse(message);
+ }
+
+ public void OnParseWrite(string message, int startIndex, int length, ConsoleColor? background, ConsoleColor? foreground)
{
var consoleContext = new ConsoleContext();
- consoleContext.Message = message;
+ consoleContext.Message = message.AsSpan().Slice(startIndex, length).ToString();
if (background.HasValue)
{
ResetColor();
}
- public void WriteLine(string message, ConsoleColor? background, ConsoleColor? foreground)
- {
- Write(message + Environment.NewLine, background, foreground);
- }
-
- public void Flush()
- {
- }
-
private void ResetColor()
{
BackgroundColor = DefaultBackgroundColor;
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using System.Text.RegularExpressions;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Logging.Console;
+using Microsoft.Extensions.Logging.Test.Console;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.Console.Test
+{
+ public class ConsoleFormatterTests
+ {
+ protected const string _loggerName = "test";
+ protected const string _state = "This is a test, and {curly braces} are just fine!";
+ protected readonly Func<object, Exception, string> _defaultFormatter = (state, exception) => state.ToString();
+
+ protected string GetMessage(List<ConsoleContext> contexts)
+ {
+ return string.Join("", contexts.Select(c => c.Message));
+ }
+
+ internal static (ConsoleLogger Logger, ConsoleSink Sink, ConsoleSink ErrorSink, Func<LogLevel, string> GetLevelPrefix, int WritesPerMsg) SetUp(
+ ConsoleLoggerOptions options = null,
+ SimpleConsoleFormatterOptions simpleOptions = null,
+ ConsoleFormatterOptions systemdOptions = null,
+ JsonConsoleFormatterOptions jsonOptions = null)
+ {
+ // Arrange
+ var sink = new ConsoleSink();
+ var errorSink = new ConsoleSink();
+ var console = new TestConsole(sink);
+ var errorConsole = new TestConsole(errorSink);
+ var consoleLoggerProcessor = new TestLoggerProcessor();
+ consoleLoggerProcessor.Console = console;
+ consoleLoggerProcessor.ErrorConsole = errorConsole;
+
+ var logger = new ConsoleLogger(_loggerName, consoleLoggerProcessor);
+ logger.ScopeProvider = new LoggerExternalScopeProvider();
+ logger.Options = options ?? new ConsoleLoggerOptions();
+ var formatters = new ConcurrentDictionary<string, ConsoleFormatter>(ConsoleLoggerTest.GetFormatters(simpleOptions, systemdOptions, jsonOptions).ToDictionary(f => f.Name));
+
+ Func<LogLevel, string> levelAsString;
+ int writesPerMsg;
+ switch (logger.Options.FormatterName)
+ {
+ case ConsoleFormatterNames.Simple:
+ levelAsString = ConsoleLoggerTest.LogLevelAsStringDefault;
+ writesPerMsg = 2;
+ logger.Formatter = formatters[ConsoleFormatterNames.Simple];
+ break;
+ case ConsoleFormatterNames.Systemd:
+ levelAsString = ConsoleLoggerTest.GetSyslogSeverityString;
+ writesPerMsg = 1;
+ logger.Formatter = formatters[ConsoleFormatterNames.Systemd];
+ break;
+ case ConsoleFormatterNames.Json:
+ levelAsString = ConsoleLoggerTest.GetJsonLogLevelString;
+ writesPerMsg = 1;
+ logger.Formatter = formatters[ConsoleFormatterNames.Json];
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(logger.Options.FormatterName));
+ }
+
+ return (logger, sink, errorSink, levelAsString, writesPerMsg);
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ public void ConsoleLoggerOptions_TimeStampFormat_IsReloaded()
+ {
+ // Arrange
+ var monitor = new TestOptionsMonitor(new ConsoleLoggerOptions() { FormatterName = "NonExistentFormatter" });
+ var loggerProvider = new ConsoleLoggerProvider(monitor, ConsoleLoggerTest.GetFormatters());
+ var logger = (ConsoleLogger)loggerProvider.CreateLogger("Name");
+
+ // Act & Assert
+ Assert.Equal("NonExistentFormatter", logger.Options.FormatterName);
+ Assert.Equal(ConsoleFormatterNames.Simple, logger.Formatter.Name);
+ }
+
+ [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [MemberData(nameof(FormatterNames))]
+ public void InvalidLogLevel_Throws(string formatterName)
+ {
+ // Arrange
+ var t = SetUp(
+ new ConsoleLoggerOptions { FormatterName = formatterName }
+ );
+ var logger = (ILogger)t.Logger;
+
+ // Act/Assert
+ Assert.Throws<ArgumentOutOfRangeException>(() => logger.Log((LogLevel)8, 0, _state, null, _defaultFormatter));
+ }
+
+ [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [MemberData(nameof(FormatterNamesAndLevels))]
+ public void NoMessageOrException_Noop(string formatterName, LogLevel level)
+ {
+ // Arrange
+ var t = SetUp(new ConsoleLoggerOptions { FormatterName = formatterName });
+ var levelPrefix = t.GetLevelPrefix(level);
+ var logger = t.Logger;
+ var sink = t.Sink;
+ var ex = new Exception("Exception message" + Environment.NewLine + "with a second line");
+
+ // Act
+ Func<object, Exception, string> formatter = (state, exception) => null;
+ logger.Log(level, 0, _state, null, formatter);
+
+ // Assert
+ Assert.Equal(0, sink.Writes.Count);
+ }
+
+ [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [MemberData(nameof(FormatterNamesAndLevels))]
+ public void Log_LogsCorrectTimestamp(string formatterName, LogLevel level)
+ {
+ // Arrange
+ var t = SetUp(
+ new ConsoleLoggerOptions { FormatterName = formatterName },
+ new SimpleConsoleFormatterOptions { TimestampFormat = "yyyy-MM-ddTHH:mm:sszz ", UseUtcTimestamp = false },
+ new ConsoleFormatterOptions { TimestampFormat = "yyyy-MM-ddTHH:mm:sszz ", UseUtcTimestamp = false },
+ new JsonConsoleFormatterOptions {
+ TimestampFormat = "yyyy-MM-ddTHH:mm:sszz ",
+ UseUtcTimestamp = false,
+ JsonWriterOptions = new JsonWriterOptions()
+ {
+ // otherwise escapes for timezone formatting from + to \u002b
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
+ Indented = true
+ }
+ });
+ var levelPrefix = t.GetLevelPrefix(level);
+ var logger = t.Logger;
+ var sink = t.Sink;
+ var ex = new Exception("Exception message" + Environment.NewLine + "with a second line");
+
+ // Act
+ logger.Log(level, 0, _state, ex, _defaultFormatter);
+
+ // Assert
+ switch (formatterName)
+ {
+ case ConsoleFormatterNames.Simple:
+ {
+ Assert.Equal(3, sink.Writes.Count);
+ Assert.StartsWith(levelPrefix, sink.Writes[1].Message);
+ Assert.Matches(@"^\d{4}\D\d{2}\D\d{2}\D\d{2}\D\d{2}\D\d{2}\D\d{2}\s$", sink.Writes[0].Message);
+ var parsedDateTime = DateTimeOffset.Parse(sink.Writes[0].Message.Trim());
+ Assert.Equal(DateTimeOffset.Now.Offset, parsedDateTime.Offset);
+ }
+ break;
+ case ConsoleFormatterNames.Systemd:
+ {
+ Assert.Single(sink.Writes);
+ Assert.StartsWith(levelPrefix, sink.Writes[0].Message);
+ var regexMatch = Regex.Match(sink.Writes[0].Message, @"^<\d>(\d{4}\D\d{2}\D\d{2}\D\d{2}\D\d{2}\D\d{2}\D\d{2})\s[^\s]");
+ Assert.True(regexMatch.Success);
+ var parsedDateTime = DateTimeOffset.Parse(regexMatch.Groups[1].Value);
+ Assert.Equal(DateTimeOffset.Now.Offset, parsedDateTime.Offset);
+ }
+ break;
+ case ConsoleFormatterNames.Json:
+ {
+ Assert.Single(sink.Writes);
+ var regexMatch = Regex.Match(sink.Writes[0].Message, @"(\d{4}\D\d{2}\D\d{2}\D\d{2}\D\d{2}\D\d{2}\D\d{2})");
+ Assert.True(regexMatch.Success, sink.Writes[0].Message);
+ var parsedDateTime = DateTimeOffset.Parse(regexMatch.Groups[1].Value);
+ Assert.Equal(DateTimeOffset.Now.Offset, parsedDateTime.Offset);
+ }
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(formatterName));
+ }
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ public void NullFormatterName_Throws()
+ {
+ // Arrange
+ Assert.Throws<ArgumentNullException>(() => new NullNameConsoleFormatter());
+ }
+
+ private class NullNameConsoleFormatter : ConsoleFormatter
+ {
+ public NullNameConsoleFormatter() : base(null) { }
+ public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider scopeProvider, TextWriter textWriter) { }
+ }
+
+ public static TheoryData<string, LogLevel> FormatterNamesAndLevels
+ {
+ get
+ {
+ var data = new TheoryData<string, LogLevel>();
+ foreach (LogLevel level in Enum.GetValues(typeof(LogLevel)))
+ {
+ if (level == LogLevel.None)
+ {
+ continue;
+ }
+ data.Add(ConsoleFormatterNames.Simple, level);
+ data.Add(ConsoleFormatterNames.Systemd, level);
+ data.Add(ConsoleFormatterNames.Json, level);
+ }
+ return data;
+ }
+ }
+
+ public static TheoryData<string> FormatterNames
+ {
+ get
+ {
+ var data = new TheoryData<string>();
+ data.Add(ConsoleFormatterNames.Simple);
+ data.Add(ConsoleFormatterNames.Systemd);
+ data.Add(ConsoleFormatterNames.Json);
+ return data;
+ }
+ }
+
+ public static TheoryData<LogLevel> Levels
+ {
+ get
+ {
+ var data = new TheoryData<LogLevel>();
+ foreach (LogLevel value in Enum.GetValues(typeof(LogLevel)))
+ {
+ data.Add(value);
+ }
+ return data;
+ }
+ }
+
+ }
+
+ public class TestFormatter : ConsoleFormatter, IDisposable
+ {
+ private IDisposable _optionsReloadToken;
+
+ public TestFormatter(IOptionsMonitor<SimpleConsoleFormatterOptions> options)
+ : base ("TestFormatter")
+ {
+ FormatterOptions = options.CurrentValue;
+ ReloadLoggerOptions(options.CurrentValue);
+ _optionsReloadToken = options.OnChange(ReloadLoggerOptions);
+ }
+
+ private void ReloadLoggerOptions(SimpleConsoleFormatterOptions options)
+ {
+ FormatterOptions = options;
+ }
+
+ public void Dispose()
+ {
+ _optionsReloadToken?.Dispose();
+ }
+
+ internal SimpleConsoleFormatterOptions FormatterOptions { get; set; }
+ public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider scopeProvider, TextWriter textWriter)
+ {
+ ;
+ }
+ }
+}
--- /dev/null
+
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Text.Encodings.Web;
+using System.IO;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Console;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.Test
+{
+ public class ConsoleLoggerExtensionsTests
+ {
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ public void AddConsole_NullConfigure_Throws()
+ {
+ Assert.Throws<ArgumentNullException>(() =>
+ new ServiceCollection()
+ .AddLogging(builder =>
+ {
+ builder.AddConsole(null);
+ }));
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ public void AddSimpleConsole_NullConfigure_Throws()
+ {
+ Assert.Throws<ArgumentNullException>(() =>
+ new ServiceCollection()
+ .AddLogging(builder =>
+ {
+ builder.AddSimpleConsole(null);
+ }));
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ public void AddSystemdConsole_NullConfigure_Throws()
+ {
+ Assert.Throws<ArgumentNullException>(() =>
+ new ServiceCollection()
+ .AddLogging(builder =>
+ {
+ builder.AddSystemdConsole(null);
+ }));
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ public void AddJsonConsole_NullConfigure_Throws()
+ {
+ Assert.Throws<ArgumentNullException>(() =>
+ new ServiceCollection()
+ .AddLogging(builder =>
+ {
+ builder.AddJsonConsole(null);
+ }));
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ public void AddConsoleFormatter_NullConfigure_Throws()
+ {
+ Assert.Throws<ArgumentNullException>(() =>
+ new ServiceCollection()
+ .AddLogging(builder =>
+ {
+ builder.AddConsoleFormatter<CustomFormatter, CustomOptions>(null);
+ }));
+ }
+
+ [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [MemberData(nameof(FormatterNames))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/38337", TestPlatforms.Browser)]
+ public void AddConsole_ConsoleLoggerOptionsFromConfigFile_IsReadFromLoggingConfiguration(string formatterName)
+ {
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] {
+ new KeyValuePair<string, string>("Console:FormatterName", formatterName)
+ }).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(formatterName, logger.Options.FormatterName);
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/38337", TestPlatforms.Browser)]
+ public void AddConsoleFormatter_CustomFormatter_IsReadFromLoggingConfiguration()
+ {
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] {
+ new KeyValuePair<string, string>("Console:FormatterName", "custom"),
+ new KeyValuePair<string, string>("Console:FormatterOptions:CustomLabel", "random"),
+ }).Build();
+
+ var loggerProvider = new ServiceCollection()
+ .AddLogging(builder => builder
+ .AddConfiguration(configuration)
+ .AddConsoleFormatter<CustomFormatter, CustomOptions>(fOptions => { fOptions.CustomLabel = "random"; })
+ .AddConsole(o => { o.FormatterName = "custom"; })
+ )
+ .BuildServiceProvider()
+ .GetRequiredService<ILoggerProvider>();
+
+ var consoleLoggerProvider = Assert.IsType<ConsoleLoggerProvider>(loggerProvider);
+ var logger = (ConsoleLogger)consoleLoggerProvider.CreateLogger("Category");
+ Assert.Equal("custom", logger.Options.FormatterName);
+ var formatter = Assert.IsType<CustomFormatter>(logger.Formatter);
+ Assert.Equal("random", formatter.FormatterOptions.CustomLabel);
+ }
+
+ private class CustomFormatter : ConsoleFormatter, IDisposable
+ {
+ private IDisposable _optionsReloadToken;
+
+ public CustomFormatter(IOptionsMonitor<CustomOptions> options)
+ : base("custom")
+ {
+ ReloadLoggerOptions(options.CurrentValue);
+ _optionsReloadToken = options.OnChange(ReloadLoggerOptions);
+ }
+
+ private void ReloadLoggerOptions(CustomOptions options)
+ {
+ FormatterOptions = options;
+ }
+
+ public CustomOptions FormatterOptions { get; set; }
+ public string CustomLog { get; set; }
+
+ public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider scopeProvider, TextWriter textWriter)
+ {
+ CustomLog = logEntry.Formatter(logEntry.State, logEntry.Exception);
+ }
+
+ public void Dispose()
+ {
+ _optionsReloadToken?.Dispose();
+ }
+ }
+
+ private class CustomOptions : ConsoleFormatterOptions
+ {
+ public string CustomLabel { get; set; }
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/38337", TestPlatforms.Browser)]
+ public void AddSimpleConsole_ChangeProperties_IsReadFromLoggingConfiguration()
+ {
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] {
+ new KeyValuePair<string, string>("Console:FormatterOptions:DisableColors", "true"),
+ new KeyValuePair<string, string>("Console:FormatterOptions:SingleLine", "true"),
+ new KeyValuePair<string, string>("Console:FormatterOptions:TimestampFormat", "HH:mm "),
+ new KeyValuePair<string, string>("Console:FormatterOptions:UseUtcTimestamp", "true"),
+ new KeyValuePair<string, string>("Console:FormatterOptions:IncludeScopes", "true"),
+ }).Build();
+
+ var loggerProvider = new ServiceCollection()
+ .AddLogging(builder => builder
+ .AddConfiguration(configuration)
+ .AddSimpleConsole(o => {})
+ )
+ .BuildServiceProvider()
+ .GetRequiredService<ILoggerProvider>();
+
+ var consoleLoggerProvider = Assert.IsType<ConsoleLoggerProvider>(loggerProvider);
+ var logger = (ConsoleLogger)consoleLoggerProvider.CreateLogger("Category");
+ Assert.Equal(ConsoleFormatterNames.Simple, logger.Options.FormatterName);
+ var formatter = Assert.IsType<SimpleConsoleFormatter>(logger.Formatter);
+ Assert.True(formatter.FormatterOptions.DisableColors);
+ Assert.True(formatter.FormatterOptions.SingleLine);
+ Assert.Equal("HH:mm ", formatter.FormatterOptions.TimestampFormat);
+ Assert.True(formatter.FormatterOptions.UseUtcTimestamp);
+ Assert.True(formatter.FormatterOptions.IncludeScopes);
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/38337", TestPlatforms.Browser)]
+ public void AddSystemdConsole_ChangeProperties_IsReadFromLoggingConfiguration()
+ {
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] {
+ new KeyValuePair<string, string>("Console:FormatterOptions:TimestampFormat", "HH:mm "),
+ new KeyValuePair<string, string>("Console:FormatterOptions:UseUtcTimestamp", "true"),
+ new KeyValuePair<string, string>("Console:FormatterOptions:IncludeScopes", "true"),
+ }).Build();
+
+ var loggerProvider = new ServiceCollection()
+ .AddLogging(builder => builder
+ .AddConfiguration(configuration)
+ .AddSystemdConsole(o => {})
+ )
+ .BuildServiceProvider()
+ .GetRequiredService<ILoggerProvider>();
+
+ var consoleLoggerProvider = Assert.IsType<ConsoleLoggerProvider>(loggerProvider);
+ var logger = (ConsoleLogger)consoleLoggerProvider.CreateLogger("Category");
+ Assert.Equal(ConsoleFormatterNames.Systemd, logger.Options.FormatterName);
+ var formatter = Assert.IsType<SystemdConsoleFormatter>(logger.Formatter);
+ Assert.Equal("HH:mm ", formatter.FormatterOptions.TimestampFormat);
+ Assert.True(formatter.FormatterOptions.UseUtcTimestamp);
+ Assert.True(formatter.FormatterOptions.IncludeScopes);
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/38337", TestPlatforms.Browser)]
+ public void AddJsonConsole_ChangeProperties_IsReadFromLoggingConfiguration()
+ {
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] {
+ new KeyValuePair<string, string>("Console:FormatterOptions:TimestampFormat", "HH:mm "),
+ new KeyValuePair<string, string>("Console:FormatterOptions:UseUtcTimestamp", "true"),
+ new KeyValuePair<string, string>("Console:FormatterOptions:IncludeScopes", "true"),
+ new KeyValuePair<string, string>("Console:FormatterOptions:JsonWriterOptions:Indented", "true"),
+ }).Build();
+
+ var loggerProvider = new ServiceCollection()
+ .AddLogging(builder => builder
+ .AddConfiguration(configuration)
+ .AddJsonConsole(o => {})
+ )
+ .BuildServiceProvider()
+ .GetRequiredService<ILoggerProvider>();
+
+ var consoleLoggerProvider = Assert.IsType<ConsoleLoggerProvider>(loggerProvider);
+ var logger = (ConsoleLogger)consoleLoggerProvider.CreateLogger("Category");
+ Assert.Equal(ConsoleFormatterNames.Json, logger.Options.FormatterName);
+ var formatter = Assert.IsType<JsonConsoleFormatter>(logger.Formatter);
+ Assert.Equal("HH:mm ", formatter.FormatterOptions.TimestampFormat);
+ Assert.True(formatter.FormatterOptions.UseUtcTimestamp);
+ Assert.True(formatter.FormatterOptions.IncludeScopes);
+ Assert.True(formatter.FormatterOptions.JsonWriterOptions.Indented);
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/38337", TestPlatforms.Browser)]
+ public void AddJsonConsole_OutsideConfig_TakesProperty()
+ {
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] {
+ new KeyValuePair<string, string>("Console:FormatterOptions:TimestampFormat", "HH:mm "),
+ new KeyValuePair<string, string>("Console:FormatterOptions:UseUtcTimestamp", "true"),
+ new KeyValuePair<string, string>("Console:FormatterOptions:IncludeScopes", "true"),
+ }).Build();
+
+ var loggerProvider = new ServiceCollection()
+ .AddLogging(builder => builder
+ .AddConfiguration(configuration)
+ .AddJsonConsole(o => {
+ o.JsonWriterOptions = new JsonWriterOptions()
+ {
+ Indented = false,
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+ };
+ })
+ )
+ .BuildServiceProvider()
+ .GetRequiredService<ILoggerProvider>();
+
+ var consoleLoggerProvider = Assert.IsType<ConsoleLoggerProvider>(loggerProvider);
+ var logger = (ConsoleLogger)consoleLoggerProvider.CreateLogger("Category");
+ Assert.Equal(ConsoleFormatterNames.Json, logger.Options.FormatterName);
+ var formatter = Assert.IsType<JsonConsoleFormatter>(logger.Formatter);
+ Assert.Equal("HH:mm ", formatter.FormatterOptions.TimestampFormat);
+ Assert.True(formatter.FormatterOptions.UseUtcTimestamp);
+ Assert.True(formatter.FormatterOptions.IncludeScopes);
+ Assert.False(formatter.FormatterOptions.JsonWriterOptions.Indented);
+ Assert.Equal(JavaScriptEncoder.UnsafeRelaxedJsonEscaping, formatter.FormatterOptions.JsonWriterOptions.Encoder);
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/38337", TestPlatforms.Browser)]
+ public void AddConsole_NullFormatterNameUsingSystemdFormat_AnyDeprecatedPropertiesOverwriteFormatterOptions()
+ {
+ var configs = new[] {
+ new KeyValuePair<string, string>("Console:Format", "Systemd"),
+ new KeyValuePair<string, string>("Console:TimestampFormat", "HH:mm:ss "),
+ new KeyValuePair<string, string>("Console:FormatterOptions:TimestampFormat", "HH:mm "),
+ };
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(configs).Build();
+
+ var loggerProvider = new ServiceCollection()
+ .AddLogging(builder => builder
+ .AddConfiguration(configuration)
+ .AddConsole(o => { })
+ )
+ .BuildServiceProvider()
+ .GetRequiredService<ILoggerProvider>();
+
+ var consoleLoggerProvider = Assert.IsType<ConsoleLoggerProvider>(loggerProvider);
+ var logger = (ConsoleLogger)consoleLoggerProvider.CreateLogger("Category");
+ Assert.Null(logger.Options.FormatterName);
+ var formatter = Assert.IsType<SystemdConsoleFormatter>(logger.Formatter);
+ Assert.Equal("HH:mm:ss ", formatter.FormatterOptions.TimestampFormat);
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/38337", TestPlatforms.Browser)]
+ public void AddConsole_NullFormatterName_UsingSystemdFormat_IgnoreFormatterOptionsAndUseDeprecatedInstead()
+ {
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] {
+ new KeyValuePair<string, string>("Console:Format", "Systemd"),
+ new KeyValuePair<string, string>("Console:IncludeScopes", "true"),
+ new KeyValuePair<string, string>("Console:TimestampFormat", "HH:mm:ss "),
+ new KeyValuePair<string, string>("Console:FormatterOptions:TimestampFormat", "HH:mm "),
+ }).Build();
+
+ var loggerProvider = new ServiceCollection()
+ .AddLogging(builder => builder
+ .AddConfiguration(configuration)
+ .AddConsole(o => {
+#pragma warning disable CS0618
+ o.IncludeScopes = false;
+#pragma warning restore CS0618
+ })
+ )
+ .BuildServiceProvider()
+ .GetRequiredService<ILoggerProvider>();
+
+ var consoleLoggerProvider = Assert.IsType<ConsoleLoggerProvider>(loggerProvider);
+ var logger = (ConsoleLogger)consoleLoggerProvider.CreateLogger("Category");
+ Assert.Null(logger.Options.FormatterName);
+ var formatter = Assert.IsType<SystemdConsoleFormatter>(logger.Formatter);
+
+ Assert.Equal("HH:mm:ss ", formatter.FormatterOptions.TimestampFormat); // ignore FormatterOptions, using deprecated one
+ Assert.False(formatter.FormatterOptions.UseUtcTimestamp); // not set anywhere, defaulted to false
+ Assert.False(formatter.FormatterOptions.IncludeScopes); // setup using lambda wins over config
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/38337", TestPlatforms.Browser)]
+ public void AddConsole_NullFormatterName_UsingDefaultFormat_IgnoreFormatterOptionsAndUseDeprecatedInstead()
+ {
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] {
+ new KeyValuePair<string, string>("Console:Format", "Default"),
+ new KeyValuePair<string, string>("Console:IncludeScopes", "true"),
+ new KeyValuePair<string, string>("Console:TimestampFormat", "HH:mm:ss "),
+ new KeyValuePair<string, string>("Console:FormatterOptions:SingleLine", "true"),
+ new KeyValuePair<string, string>("Console:FormatterOptions:TimestampFormat", "HH:mm "),
+ }).Build();
+
+ var loggerProvider = new ServiceCollection()
+ .AddLogging(builder => builder
+ .AddConfiguration(configuration)
+ .AddConsole(o => {
+#pragma warning disable CS0618
+ o.DisableColors = true;
+ o.IncludeScopes = false;
+#pragma warning restore CS0618
+ })
+ )
+ .BuildServiceProvider()
+ .GetRequiredService<ILoggerProvider>();
+
+ var consoleLoggerProvider = Assert.IsType<ConsoleLoggerProvider>(loggerProvider);
+ var logger = (ConsoleLogger)consoleLoggerProvider.CreateLogger("Category");
+ Assert.Null(logger.Options.FormatterName);
+#pragma warning disable CS0618
+ Assert.True(logger.Options.DisableColors);
+#pragma warning restore CS0618
+ var formatter = Assert.IsType<SimpleConsoleFormatter>(logger.Formatter);
+
+ Assert.False(formatter.FormatterOptions.SingleLine); // ignored
+ Assert.Equal("HH:mm:ss ", formatter.FormatterOptions.TimestampFormat); // ignore FormatterOptions, using deprecated one
+ Assert.False(formatter.FormatterOptions.UseUtcTimestamp); // not set anywhere, defaulted to false
+ Assert.False(formatter.FormatterOptions.IncludeScopes); // setup using lambda wins over config
+ Assert.True(formatter.FormatterOptions.DisableColors); // setup using lambda
+ }
+
+ [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [InlineData("missingFormatter")]
+ [InlineData("simple")]
+ [InlineData("Simple")]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/38337", TestPlatforms.Browser)]
+ public void AddConsole_FormatterNameIsSet_UsingDefaultFormat_IgnoreDeprecatedAndUseFormatterOptionsInstead(string formatterName)
+ {
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] {
+ new KeyValuePair<string, string>("Console:Format", "Default"),
+ new KeyValuePair<string, string>("Console:FormatterName", formatterName),
+ new KeyValuePair<string, string>("Console:TimestampFormat", "HH:mm:ss "),
+ new KeyValuePair<string, string>("Console:FormatterOptions:IncludeScopes", "true"),
+ new KeyValuePair<string, string>("Console:FormatterOptions:SingleLine", "true"),
+ new KeyValuePair<string, string>("Console:FormatterOptions:TimestampFormat", "HH:mm "),
+ }).Build();
+
+ var loggerProvider = new ServiceCollection()
+ .AddLogging(builder => builder
+ .AddConfiguration(configuration)
+ .AddConsole(o => {
+#pragma warning disable CS0618
+ o.DisableColors = true;
+ o.IncludeScopes = false;
+#pragma warning restore CS0618
+ })
+ )
+ .BuildServiceProvider()
+ .GetRequiredService<ILoggerProvider>();
+
+ var consoleLoggerProvider = Assert.IsType<ConsoleLoggerProvider>(loggerProvider);
+ var logger = (ConsoleLogger)consoleLoggerProvider.CreateLogger("Category");
+ Assert.Equal(formatterName, logger.Options.FormatterName);
+#pragma warning disable CS0618
+ Assert.True(logger.Options.DisableColors);
+#pragma warning restore CS0618
+ var formatter = Assert.IsType<SimpleConsoleFormatter>(logger.Formatter);
+
+ Assert.True(formatter.FormatterOptions.SingleLine); // picked from FormatterOptions
+ Assert.Equal("HH:mm ", formatter.FormatterOptions.TimestampFormat); // ignore deprecated, using FormatterOptions instead
+ Assert.False(formatter.FormatterOptions.UseUtcTimestamp); // not set anywhere, defaulted to false
+ Assert.True(formatter.FormatterOptions.IncludeScopes); // ignore deprecated set in lambda use FormatterOptions instead
+ Assert.False(formatter.FormatterOptions.DisableColors); // ignore deprecated set in lambda, defaulted to false
+ }
+
+ [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [InlineData("missingFormatter")]
+ [InlineData("systemd")]
+ [InlineData("Systemd")]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/38337", TestPlatforms.Browser)]
+ public void AddConsole_FormatterNameIsSet_UsingSystemdFormat_IgnoreDeprecatedAndUseFormatterOptionsInstead(string formatterName)
+ {
+ var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] {
+ new KeyValuePair<string, string>("Console:Format", "Systemd"),
+ new KeyValuePair<string, string>("Console:FormatterName", formatterName),
+ new KeyValuePair<string, string>("Console:TimestampFormat", "HH:mm:ss "),
+ new KeyValuePair<string, string>("Console:FormatterOptions:IncludeScopes", "true"),
+ new KeyValuePair<string, string>("Console:FormatterOptions:TimestampFormat", "HH:mm "),
+ }).Build();
+
+ var loggerProvider = new ServiceCollection()
+ .AddLogging(builder => builder
+ .AddConfiguration(configuration)
+ .AddConsole(o => {
+#pragma warning disable CS0618
+ o.UseUtcTimestamp = true;
+ o.IncludeScopes = false;
+#pragma warning restore CS0618
+ })
+ )
+ .BuildServiceProvider()
+ .GetRequiredService<ILoggerProvider>();
+
+ var consoleLoggerProvider = Assert.IsType<ConsoleLoggerProvider>(loggerProvider);
+ var logger = (ConsoleLogger)consoleLoggerProvider.CreateLogger("Category");
+ Assert.Equal(formatterName, logger.Options.FormatterName);
+#pragma warning disable CS0618
+ Assert.True(logger.Options.UseUtcTimestamp);
+#pragma warning restore CS0618
+ var formatter = Assert.IsType<SystemdConsoleFormatter>(logger.Formatter);
+
+ Assert.Equal("HH:mm ", formatter.FormatterOptions.TimestampFormat); // ignore deprecated, using FormatterOptions instead
+ Assert.True(formatter.FormatterOptions.IncludeScopes); // ignore deprecated set in lambda use FormatterOptions instead
+ Assert.False(formatter.FormatterOptions.UseUtcTimestamp); // ignore deprecated set in lambda, defaulted to false
+ }
+
+ public static TheoryData<string> FormatterNames
+ {
+ get
+ {
+ var data = new TheoryData<string>();
+ data.Add(ConsoleFormatterNames.Simple);
+ data.Add(ConsoleFormatterNames.Systemd);
+ data.Add(ConsoleFormatterNames.Json);
+ return data;
+ }
+ }
+ }
+}
\ No newline at end of file
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging.Configuration;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Logging.Test.Console;
using Microsoft.Extensions.Options;
using Xunit;
+#pragma warning disable CS0618
-namespace Microsoft.Extensions.Logging.Test
+namespace Microsoft.Extensions.Logging.Console.Test
{
public class ConsoleLoggerTest
{
private const string _state = "This is a test, and {curly braces} are just fine!";
private readonly Func<object, Exception, string> _defaultFormatter = (state, exception) => state.ToString();
+ internal static IEnumerable<ConsoleFormatter> GetFormatters(
+ SimpleConsoleFormatterOptions simpleOptions = null,
+ ConsoleFormatterOptions systemdOptions = null,
+ JsonConsoleFormatterOptions jsonOptions = null)
+ {
+ var defaultMonitor = new TestFormatterOptionsMonitor<SimpleConsoleFormatterOptions>(simpleOptions ?? new SimpleConsoleFormatterOptions());
+ var systemdMonitor = new TestFormatterOptionsMonitor<ConsoleFormatterOptions>(systemdOptions ?? new ConsoleFormatterOptions());
+ var jsonMonitor = new TestFormatterOptionsMonitor<JsonConsoleFormatterOptions>(jsonOptions ?? new JsonConsoleFormatterOptions());
+ var formatters = new List<ConsoleFormatter>() {
+ new SimpleConsoleFormatter(defaultMonitor),
+ new SystemdConsoleFormatter(systemdMonitor),
+ new JsonConsoleFormatter(jsonMonitor)
+ };
+ return formatters;
+ }
+
private static (ConsoleLogger Logger, ConsoleSink Sink, ConsoleSink ErrorSink, Func<LogLevel, string> GetLevelPrefix, int WritesPerMsg) SetUp(ConsoleLoggerOptions options = null)
{
// Arrange
var logger = new ConsoleLogger(_loggerName, consoleLoggerProcessor);
logger.ScopeProvider = new LoggerExternalScopeProvider();
logger.Options = options ?? new ConsoleLoggerOptions();
+ var formatters = new ConcurrentDictionary<string, ConsoleFormatter>(GetFormatters().ToDictionary(f => f.Name));
+
Func<LogLevel, string> levelAsString;
int writesPerMsg;
switch (logger.Options.Format)
case ConsoleLoggerFormat.Default:
levelAsString = LogLevelAsStringDefault;
writesPerMsg = 2;
+ logger.Formatter = formatters[ConsoleFormatterNames.Simple];
break;
case ConsoleLoggerFormat.Systemd:
levelAsString = GetSyslogSeverityString;
writesPerMsg = 1;
+ logger.Formatter = formatters[ConsoleFormatterNames.Systemd];
break;
default:
throw new ArgumentOutOfRangeException(nameof(logger.Options.Format));
}
+
+ UpdateFormatterOptions(logger.Formatter, logger.Options);
+ VerifyDeprecatedPropertiesUsedOnNullFormatterName(logger);
return (logger, sink, errorSink, levelAsString, writesPerMsg);
}
- private static string LogLevelAsStringDefault(LogLevel level)
+ private static void VerifyDeprecatedPropertiesUsedOnNullFormatterName(ConsoleLogger logger)
+ {
+ Assert.Null(logger.Options.FormatterName);
+ if (ConsoleLoggerFormat.Default == logger.Options.Format)
+ {
+ var formatter = Assert.IsType<SimpleConsoleFormatter>(logger.Formatter);
+ Assert.Equal(formatter.FormatterOptions.IncludeScopes, logger.Options.IncludeScopes);
+ Assert.Equal(formatter.FormatterOptions.UseUtcTimestamp, logger.Options.UseUtcTimestamp);
+ Assert.Equal(formatter.FormatterOptions.TimestampFormat, logger.Options.TimestampFormat);
+ Assert.Equal(formatter.FormatterOptions.DisableColors, logger.Options.DisableColors);
+ }
+ else
+ {
+ var formatter = Assert.IsType<SystemdConsoleFormatter>(logger.Formatter);
+ Assert.Equal(formatter.FormatterOptions.IncludeScopes, logger.Options.IncludeScopes);
+ Assert.Equal(formatter.FormatterOptions.UseUtcTimestamp, logger.Options.UseUtcTimestamp);
+ Assert.Equal(formatter.FormatterOptions.TimestampFormat, logger.Options.TimestampFormat);
+ }
+ }
+
+ private static void UpdateFormatterOptions(ConsoleFormatter formatter, ConsoleLoggerOptions deprecatedFromOptions)
+ {
+ // kept for deprecated apis:
+ if (formatter is SimpleConsoleFormatter defaultFormatter)
+ {
+ defaultFormatter.FormatterOptions.DisableColors = deprecatedFromOptions.DisableColors;
+ defaultFormatter.FormatterOptions.IncludeScopes = deprecatedFromOptions.IncludeScopes;
+ defaultFormatter.FormatterOptions.TimestampFormat = deprecatedFromOptions.TimestampFormat;
+ defaultFormatter.FormatterOptions.UseUtcTimestamp = deprecatedFromOptions.UseUtcTimestamp;
+ }
+ else
+ if (formatter is SystemdConsoleFormatter systemdFormatter)
+ {
+ systemdFormatter.FormatterOptions.IncludeScopes = deprecatedFromOptions.IncludeScopes;
+ systemdFormatter.FormatterOptions.TimestampFormat = deprecatedFromOptions.TimestampFormat;
+ systemdFormatter.FormatterOptions.UseUtcTimestamp = deprecatedFromOptions.UseUtcTimestamp;
+ }
+ }
+
+ internal static string LogLevelAsStringDefault(LogLevel level)
{
switch (level)
{
}
}
- private static string GetSyslogSeverityString(LogLevel level)
+ internal static string GetSyslogSeverityString(LogLevel level)
{
switch (level)
{
}
}
+ internal static string GetJsonLogLevelString(LogLevel logLevel)
+ {
+ return logLevel switch
+ {
+ LogLevel.Trace => "Trace",
+ LogLevel.Debug => "Debug",
+ LogLevel.Information => "Information",
+ LogLevel.Warning => "Warning",
+ LogLevel.Error => "Error",
+ LogLevel.Critical => "Critical",
+ _ => throw new ArgumentOutOfRangeException(nameof(logLevel))
+ };
+ }
+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
public void LogsWhenMessageIsNotProvided()
{
Assert.Equal(6, sink.Writes.Count);
Assert.Equal(
"crit: test[0]" + Environment.NewLine +
- " [null]" + Environment.NewLine,
+ _paddingString + "[null]" + Environment.NewLine,
GetMessage(sink.Writes.GetRange(0 * t.WritesPerMsg, t.WritesPerMsg)));
Assert.Equal(
"crit: test[0]" + Environment.NewLine +
- " [null]" + Environment.NewLine,
+ _paddingString + "[null]" + Environment.NewLine,
GetMessage(sink.Writes.GetRange(1 * t.WritesPerMsg, t.WritesPerMsg)));
Assert.Equal(
"crit: test[0]" + Environment.NewLine +
- " [null]" + Environment.NewLine +
- "System.InvalidOperationException: Invalid value" + Environment.NewLine,
+ _paddingString + "[null]" + Environment.NewLine +
+ _paddingString + "System.InvalidOperationException: Invalid value" + Environment.NewLine,
GetMessage(sink.Writes.GetRange(2 * t.WritesPerMsg, t.WritesPerMsg)));
}
var t = SetUp();
var logger = (ILogger)t.Logger;
var sink = t.Sink;
- var logMessage = "Route with name 'Default' was not found.";
+ var logMessage = "Route with name 'Simple' was not found.";
var expected1 = @"crit: test[0]" + Environment.NewLine +
- " Route with name 'Default' was not found." + Environment.NewLine;
+ _paddingString + "Route with name 'Simple' was not found." + Environment.NewLine;
var expected2 = @"crit: test[10]" + Environment.NewLine +
- " Route with name 'Default' was not found." + Environment.NewLine;
+ _paddingString + "Route with name 'Simple' was not found." + Environment.NewLine;
// Act
logger.LogCritical(logMessage);
}
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
- [InlineData("Route with name 'Default' was not found.")]
+ [InlineData(null, 0)]
+ [InlineData(null, 1)]
+ [InlineData("missingFormatter", 0)]
+ [InlineData("missingFormatter", 1)]
+ public void Options_FormatterNameNull_UsesDeprecatedProperties(string formatterName, int formatNumber)
+ {
+ // Arrange
+ ConsoleLoggerFormat format = (ConsoleLoggerFormat)formatNumber;
+ var options = new ConsoleLoggerOptions() { FormatterName = formatterName };
+ var monitor = new TestOptionsMonitor(options);
+ var loggerProvider = new ConsoleLoggerProvider(monitor, GetFormatters());
+ var logger = (ConsoleLogger)loggerProvider.CreateLogger("Name");
+
+ // Act
+ monitor.Set(new ConsoleLoggerOptions() { FormatterName = null, Format = format, TimestampFormat = "HH:mm:ss " });
+
+ // Assert
+ switch (format)
+ {
+ case ConsoleLoggerFormat.Default:
+ {
+ Assert.Null(logger.Options.FormatterName);
+ Assert.Equal(ConsoleFormatterNames.Simple, logger.Formatter.Name);
+ var formatter = Assert.IsType<SimpleConsoleFormatter>(logger.Formatter);
+ Assert.Equal("HH:mm:ss ", formatter.FormatterOptions.TimestampFormat);
+ }
+ break;
+ case ConsoleLoggerFormat.Systemd:
+ {
+ Assert.Null(logger.Options.FormatterName);
+ Assert.Equal(ConsoleFormatterNames.Systemd, logger.Formatter.Name);
+ var formatter = Assert.IsType<SystemdConsoleFormatter>(logger.Formatter);
+ Assert.Equal("HH:mm:ss ", formatter.FormatterOptions.TimestampFormat);
+ }
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(format));
+ }
+ loggerProvider?.Dispose();
+ }
+
+ [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ [InlineData("Route with name 'Simple' was not found.")]
public void Writes_NewLine_WhenExceptionIsProvided(string message)
{
// Arrange
var expectedMessage =
_paddingString + message + Environment.NewLine;
var expectedExceptionMessage =
- exception.ToString() + Environment.NewLine;
+ _paddingString + exception.ToString() + Environment.NewLine;
// Act
logger.LogCritical(eventId, exception, message);
// Assert
Assert.Equal(2, sink.Writes.Count);
var write = sink.Writes[0];
- Assert.Equal(ConsoleColor.Red, write.BackgroundColor);
+ Assert.Equal(ConsoleColor.DarkRed, write.BackgroundColor);
Assert.Equal(ConsoleColor.White, write.ForegroundColor);
write = sink.Writes[1];
Assert.Equal(TestConsole.DefaultBackgroundColor, write.BackgroundColor);
// Assert
Assert.Equal(2, sink.Writes.Count);
var write = sink.Writes[0];
- Assert.Equal(ConsoleColor.Red, write.BackgroundColor);
+ Assert.Equal(ConsoleColor.DarkRed, write.BackgroundColor);
Assert.Equal(ConsoleColor.Black, write.ForegroundColor);
write = sink.Writes[1];
Assert.Equal(TestConsole.DefaultBackgroundColor, write.BackgroundColor);
}
// Assert
- Assert.Equal(2 * levelSequence, sink.Writes.Count);
+ Assert.Equal(levelSequence, sink.Writes.Count);
foreach (ConsoleContext write in sink.Writes)
{
Assert.Null(write.ForegroundColor);
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
[MemberData(nameof(FormatsAndLevels))]
- public void WriteCore_LogsCorrectTimestamp(ConsoleLoggerFormat format, LogLevel level)
+ public void Log_LogsCorrectTimestamp(ConsoleLoggerFormat format, LogLevel level)
{
// Arrange
var t = SetUp(new ConsoleLoggerOptions { TimestampFormat = "yyyy-MM-ddTHH:mm:sszz ", Format = format, UseUtcTimestamp = false });
Assert.Equal(2, sink.Writes.Count);
Assert.Equal(
levelPrefix + ": test[0]" + Environment.NewLine +
- " This is a test, and {curly braces} are just fine!" + Environment.NewLine +
- "System.Exception: Exception message" + Environment.NewLine +
- "with a second line" + Environment.NewLine,
+ _paddingString + "This is a test, and {curly braces} are just fine!" + Environment.NewLine +
+ _paddingString + "System.Exception: Exception message" + Environment.NewLine +
+ _paddingString + "with a second line" + Environment.NewLine,
GetMessage(sink.Writes));
}
break;
{
// Assert
Assert.Equal(2, sink.Writes.Count);
+ var formatter = Assert.IsType<SimpleConsoleFormatter>(logger.Formatter);
+ Assert.True(formatter.FormatterOptions.IncludeScopes);
// scope
var write = sink.Writes[1];
Assert.Equal(header + Environment.NewLine
{
// Assert
Assert.Single(sink.Writes);
+ var formatter = Assert.IsType<SystemdConsoleFormatter>(logger.Formatter);
+ Assert.True(formatter.FormatterOptions.IncludeScopes);
// scope
var write = sink.Writes[0];
Assert.Equal(levelPrefix + header + " " + scope + " " + message + Environment.NewLine, write.Message);
Assert.Equal(2, sink.Writes.Count);
Assert.Equal(
"info: test[0]" + Environment.NewLine +
- " Info" + Environment.NewLine,
+ _paddingString + "Info" + Environment.NewLine,
GetMessage(sink.Writes));
Assert.Equal(2, errorSink.Writes.Count);
Assert.Equal(
"warn: test[0]" + Environment.NewLine +
- " Warn" + Environment.NewLine,
+ _paddingString + "Warn" + Environment.NewLine,
GetMessage(errorSink.Writes));
}
break;
Assert.Equal(2, sink.Writes.Count);
Assert.Equal(
levelPrefix + ": test[0]" + Environment.NewLine +
- "System.Exception: Exception message" + Environment.NewLine +
- "with a second line" + Environment.NewLine,
+ _paddingString + "System.Exception: Exception message" + Environment.NewLine +
+ _paddingString + "with a second line" + Environment.NewLine,
GetMessage(sink.Writes));
}
break;
Assert.Equal(2, sink.Writes.Count);
Assert.Equal(
levelPrefix + ": test[0]" + Environment.NewLine +
- "System.Exception: Exception message" + Environment.NewLine +
- "with a second line" + Environment.NewLine,
+ _paddingString + "System.Exception: Exception message" + Environment.NewLine +
+ _paddingString + "with a second line" + Environment.NewLine,
GetMessage(sink.Writes));
}
break;
Assert.Equal(2, sink.Writes.Count);
Assert.Equal(
levelPrefix + ": test[0]" + Environment.NewLine +
- " This is a test, and {curly braces} are just fine!" + Environment.NewLine,
+ _paddingString + "This is a test, and {curly braces} are just fine!" + Environment.NewLine,
GetMessage(sink.Writes));
}
break;
var console = new TestConsole(sink);
var processor = new ConsoleLoggerProcessor();
processor.Console = console;
+ var formatters = new ConcurrentDictionary<string, ConsoleFormatter>(GetFormatters().ToDictionary(f => f.Name));
var logger = new ConsoleLogger(_loggerName, loggerProcessor: processor);
logger.Options = new ConsoleLoggerOptions();
+ Assert.Null(logger.Options.FormatterName);
+ logger.Formatter = formatters[ConsoleFormatterNames.Simple];
+ UpdateFormatterOptions(logger.Formatter, logger.Options);
// Act
processor.Dispose();
logger.LogInformation("Logging after dispose");
}
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ public void ConsoleLoggerOptions_InvalidFormat_Throws()
+ {
+ Assert.Throws<ArgumentOutOfRangeException>(() => new ConsoleLoggerOptions() { Format = (ConsoleLoggerFormat)10 });
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
public void ConsoleLoggerOptions_DisableColors_IsReadFromLoggingConfiguration()
{
var configuration = new ConfigurationBuilder().AddInMemoryCollection(new[] { new KeyValuePair<string, string>("Console:DisableColors", "true") }).Build();
var consoleLoggerProvider = Assert.IsType<ConsoleLoggerProvider>(loggerProvider);
var logger = (ConsoleLogger)consoleLoggerProvider.CreateLogger("Category");
Assert.NotNull(logger.ScopeProvider);
+ var formatter = Assert.IsType<SimpleConsoleFormatter>(logger.Formatter);
+ Assert.True(formatter.FormatterOptions.IncludeScopes);
}
public static TheoryData<ConsoleLoggerFormat, LogLevel> FormatsAndLevels
throw new ArgumentOutOfRangeException(nameof(format));
}
}
+ }
-
- private class TestLoggerProcessor : ConsoleLoggerProcessor
+ internal class TestLoggerProcessor : ConsoleLoggerProcessor
+ {
+ public TestLoggerProcessor()
{
- public TestLoggerProcessor()
- {
- }
+ }
- public override void EnqueueMessage(LogMessageEntry message)
- {
- WriteMessage(message);
- }
+ public override void EnqueueMessage(LogMessageEntry message)
+ {
+ WriteMessage(message);
}
}
}
}
}
+#pragma warning restore CS0618
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.Json;
+using System.Text.RegularExpressions;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Logging.Console;
+using Microsoft.Extensions.Logging.Test.Console;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.Console.Test
+{
+ public class JsonConsoleFormatterTests : ConsoleFormatterTests
+ {
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ public void NoLogScope_DoesNotWriteAnyScopeContentToOutput_Json()
+ {
+ // Arrange
+ var t = ConsoleFormatterTests.SetUp(
+ new ConsoleLoggerOptions { FormatterName = ConsoleFormatterNames.Json },
+ new SimpleConsoleFormatterOptions { IncludeScopes = true },
+ new ConsoleFormatterOptions { IncludeScopes = true },
+ new JsonConsoleFormatterOptions {
+ IncludeScopes = true,
+ JsonWriterOptions = new JsonWriterOptions() { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }
+ });
+ var logger = t.Logger;
+ var sink = t.Sink;
+
+ // Act
+ using (logger.BeginScope("Scope with named parameter {namedParameter}", 123))
+ using (logger.BeginScope("SimpleScope"))
+ logger.Log(LogLevel.Warning, 0, "Message with {args}", 73, _defaultFormatter);
+
+ // Assert
+ Assert.Equal(1, sink.Writes.Count);
+ var write = sink.Writes[0];
+ Assert.Equal(TestConsole.DefaultBackgroundColor, write.BackgroundColor);
+ Assert.Equal(TestConsole.DefaultForegroundColor, write.ForegroundColor);
+ Assert.Contains("Message with {args}", write.Message);
+ Assert.Contains("73", write.Message);
+ Assert.Contains("{OriginalFormat}", write.Message);
+ Assert.Contains("namedParameter", write.Message);
+ Assert.Contains("123", write.Message);
+ Assert.Contains("SimpleScope", write.Message);
+ }
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFrameworks>$(NetCoreAppCurrent);$(NetFrameworkCurrent)</TargetFrameworks>
+ <TestRuntime>true</TestRuntime>
+ <EnableDefaultItems>true</EnableDefaultItems>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ReferenceFromRuntime Include="Microsoft.Extensions.Logging.Console" />
+ </ItemGroup>
+
+</Project>
\ No newline at end of file
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.Json;
+using System.Text.RegularExpressions;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Logging.Console;
+using Microsoft.Extensions.Logging.Test.Console;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.Console.Test
+{
+ public class SimpleConsoleFormatterTests : ConsoleFormatterTests
+ {
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ public void Log_WritingScopes_LogsWithCorrectColors()
+ {
+ // Arrange
+ var t = SetUp(
+ new ConsoleLoggerOptions { FormatterName = ConsoleFormatterNames.Simple },
+ new SimpleConsoleFormatterOptions { IncludeScopes = true }
+ );
+ var logger = t.Logger;
+ var sink = t.Sink;
+ var id = Guid.NewGuid();
+ var scopeMessage = "RequestId: {RequestId}";
+
+ // Act
+ using (logger.BeginScope(scopeMessage, id))
+ {
+ logger.Log(LogLevel.Information, 0, _state, null, _defaultFormatter);
+ }
+
+ // Assert
+ Assert.Equal(2, sink.Writes.Count);
+ var write = sink.Writes[0];
+ Assert.Equal(ConsoleColor.Black, write.BackgroundColor);
+ Assert.Equal(ConsoleColor.DarkGreen, write.ForegroundColor);
+ write = sink.Writes[1];
+ Assert.Equal(TestConsole.DefaultBackgroundColor, write.BackgroundColor);
+ Assert.Equal(TestConsole.DefaultForegroundColor, write.ForegroundColor);
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ public void Log_NoLogScope_DoesNotWriteAnyScopeContentToOutput()
+ {
+ // Arrange
+ var t = SetUp(
+ new ConsoleLoggerOptions { FormatterName = ConsoleFormatterNames.Simple },
+ new SimpleConsoleFormatterOptions { IncludeScopes = true }
+ );
+ var logger = t.Logger;
+ var sink = t.Sink;
+
+ // Act
+ logger.Log(LogLevel.Warning, 0, _state, null, _defaultFormatter);
+
+ // Assert
+ Assert.Equal(2, sink.Writes.Count);
+ var write = sink.Writes[0];
+ Assert.Equal(ConsoleColor.Black, write.BackgroundColor);
+ Assert.Equal(ConsoleColor.Yellow, write.ForegroundColor);
+ write = sink.Writes[1];
+ Assert.Equal(TestConsole.DefaultBackgroundColor, write.BackgroundColor);
+ Assert.Equal(TestConsole.DefaultForegroundColor, write.ForegroundColor);
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))]
+ public void Log_SingleLine_LogsWhenMessageIsNotProvided()
+ {
+ // Arrange
+ var t = SetUp(
+ new ConsoleLoggerOptions { FormatterName = ConsoleFormatterNames.Simple },
+ new SimpleConsoleFormatterOptions { SingleLine = true }
+ );
+ var logger = (ILogger)t.Logger;
+ var sink = t.Sink;
+ var exception = new InvalidOperationException("Invalid value");
+
+ // Act
+ logger.LogCritical(eventId: 0, exception: null, message: null);
+ logger.LogCritical(eventId: 0, message: null);
+ logger.LogCritical(eventId: 0, message: null, exception: exception);
+
+ // Assert
+ Assert.Equal(6, sink.Writes.Count);
+ Assert.Equal(
+ "crit: test[0]" + " " + "[null]" + Environment.NewLine,
+ GetMessage(sink.Writes.GetRange(0 * t.WritesPerMsg, t.WritesPerMsg)));
+ Assert.Equal(
+ "crit: test[0]" + " " + "[null]" + Environment.NewLine,
+ GetMessage(sink.Writes.GetRange(1 * t.WritesPerMsg, t.WritesPerMsg)));
+
+ Assert.Equal(
+ "crit: test[0]" + " " + "[null]" + "System.InvalidOperationException: Invalid value" + Environment.NewLine,
+ GetMessage(sink.Writes.GetRange(2 * t.WritesPerMsg, t.WritesPerMsg)));
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.Console.Test
+{
+ internal class TestFormatterOptionsMonitor<TOptions> : IOptionsMonitor<TOptions> where TOptions : ConsoleFormatterOptions
+ {
+ private TOptions _options;
+ private event Action<TOptions, string> _onChange;
+ public TestFormatterOptionsMonitor(TOptions options)
+ {
+ _options = options;
+ }
+
+ public TOptions Get(string name) => _options;
+
+ public IDisposable OnChange(Action<TOptions, string> listener)
+ {
+ _onChange += listener;
+ return null;
+ }
+
+ public TOptions CurrentValue => _options;
+
+ public void Set(TOptions options)
+ {
+ _options = options;
+ _onChange?.Invoke(options, "");
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Text;
+using System.IO;
+using System.Reflection;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Logging.Console;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.Extensions.Logging.Console.Test
+{
+ public class TextWriterExtensionsTests
+ {
+ [Fact]
+ public void WriteColoredMessage_WithForegroundEscapeCode_AndNoBackgroundColorSpecified()
+ {
+ // Arrange
+ var message = "Request received";
+ var expectedMessage = AnsiParser.GetForegroundColorEscapeCode(ConsoleColor.DarkGreen)
+ + message
+ + "\x1B[39m\x1B[22m"; //resets foreground color
+ var textWriter = new StringWriter();
+
+ // Act
+ textWriter.WriteColoredMessage(message, background: null, foreground: ConsoleColor.DarkGreen);
+
+ // Assert
+ Assert.Equal(expectedMessage, textWriter.ToString());
+ }
+
+ [Fact]
+ public void WriteColoredMessage_WithBackgroundEscapeCode_AndNoForegroundColorSpecified()
+ {
+ // Arrange
+ var message = "Request received";
+ var expectedMessage = AnsiParser.GetBackgroundColorEscapeCode(ConsoleColor.Red)
+ + message
+ + "\x1B[49m"; //resets background color
+ var textWriter = new StringWriter();
+
+ // Act
+ textWriter.WriteColoredMessage(message, background: ConsoleColor.Red, foreground: null);
+
+ // Assert
+ Assert.Equal(expectedMessage, textWriter.ToString());
+ }
+
+ [Fact]
+ public void WriteColoredMessage_InOrder_WhenBothForegroundOrBackgroundColorsSpecified()
+ {
+ // Arrange
+ var message = "Request received";
+ var expectedMessage = AnsiParser.GetBackgroundColorEscapeCode(ConsoleColor.Red)
+ + AnsiParser.GetForegroundColorEscapeCode(ConsoleColor.DarkGreen)
+ + "Request received"
+ + "\x1B[39m\x1B[22m" //resets foreground color
+ + "\x1B[49m"; //resets background color
+ var textWriter = new StringWriter();
+
+ // Act
+ textWriter.WriteColoredMessage(message, ConsoleColor.Red, ConsoleColor.DarkGreen);
+
+ // Assert
+ Assert.Equal(expectedMessage, textWriter.ToString());
+ }
+
+ [Fact]
+ public void WriteColoredMessage_NullColors_NoAnsiEmbedded()
+ {
+ // Arrange
+ var message = "Request received";
+ var textWriter = new StringWriter();
+
+ // Act
+ textWriter.WriteColoredMessage(message, null, null);
+
+ // Assert
+ Assert.Equal(message, textWriter.ToString());
+ }
+ }
+}
\ No newline at end of file
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System;
-using Microsoft.Extensions.Logging.Console;
-using Xunit;
-
-namespace Microsoft.Extensions.Logging
-{
- public class AnsiLogConsoleTest
- {
- [Fact]
- public void DoesNotAddNewLine()
- {
- // Arrange
- var systemConsole = new TestAnsiSystemConsole();
- var console = new AnsiLogConsole(systemConsole);
- var message = "Request received";
- var expectedMessage = message;
-
- // Act
- console.Write(message, background: null, foreground: null);
- console.Flush();
-
- // Assert
- Assert.Equal(expectedMessage, systemConsole.Message);
- }
-
- [Fact]
- public void NotCallingFlush_DoesNotWriteData_ToSystemConsole()
- {
- // Arrange
- var systemConsole = new TestAnsiSystemConsole();
- var console = new AnsiLogConsole(systemConsole);
- var message = "Request received";
- var expectedMessage = message + Environment.NewLine;
-
- // Act
- console.WriteLine(message, background: null, foreground: null);
-
- // Assert
- Assert.Null(systemConsole.Message);
- }
-
- [Fact]
- public void CallingFlush_ClearsData_FromOutputBuilder()
- {
- // Arrange
- var systemConsole = new TestAnsiSystemConsole();
- var console = new AnsiLogConsole(systemConsole);
- var message = "Request received";
- var expectedMessage = message + Environment.NewLine;
-
- // Act
- console.WriteLine(message, background: null, foreground: null);
- console.Flush();
- console.WriteLine(message, background: null, foreground: null);
- console.Flush();
-
- // Assert
- Assert.Equal(expectedMessage, systemConsole.Message);
- }
-
- [Fact]
- public void WritesMessage_WithoutEscapeCodes_AndNoForegroundOrBackgroundColorsSpecified()
- {
- // Arrange
- var systemConsole = new TestAnsiSystemConsole();
- var console = new AnsiLogConsole(systemConsole);
- var message = "Request received";
- var expectedMessage = message + Environment.NewLine;
-
- // Act
- console.WriteLine(message, background: null, foreground: null);
- console.Flush();
-
- // Assert
- Assert.Equal(expectedMessage, systemConsole.Message);
- }
-
- [Fact]
- public void WritesMessage_WithForegroundEscapeCode_AndNoBackgroundColorSpecified()
- {
- // Arrange
- var systemConsole = new TestAnsiSystemConsole();
- var console = new AnsiLogConsole(systemConsole);
- var message = "Request received";
- var expectedMessage = GetForegroundColorEscapeCode(ConsoleColor.DarkGreen)
- + message
- + "\x1B[39m\x1B[22m"; //resets foreground color
-
- // Act
- console.WriteLine(message, background: null, foreground: ConsoleColor.DarkGreen);
- console.Flush();
-
- // Assert
- Assert.Equal(expectedMessage + Environment.NewLine, systemConsole.Message);
- }
-
- [Fact]
- public void WritesMessage_WithBackgroundEscapeCode_AndNoForegroundColorSpecified()
- {
- // Arrange
- var systemConsole = new TestAnsiSystemConsole();
- var console = new AnsiLogConsole(systemConsole);
- var message = "Request received";
- var expectedMessage = GetBackgroundColorEscapeCode(ConsoleColor.Red)
- + message
- + "\x1B[49m"; //resets background color
-
- // Act
- console.WriteLine(message, background: ConsoleColor.Red, foreground: null);
- console.Flush();
-
- // Assert
- Assert.Equal(expectedMessage + Environment.NewLine, systemConsole.Message);
- }
-
- [Fact]
- public void WriteMessage_InOrder_WhenBothForegroundOrBackgroundColorsSpecified()
- {
- // Arrange
- var systemConsole = new TestAnsiSystemConsole();
- var console = new AnsiLogConsole(systemConsole);
- var message = "Request received";
- var expectedMessage = GetBackgroundColorEscapeCode(ConsoleColor.Red)
- + GetForegroundColorEscapeCode(ConsoleColor.DarkGreen)
- + "Request received"
- + "\x1B[39m\x1B[22m" //resets foreground color
- + "\x1B[49m" //resets background color
- + Environment.NewLine;
-
- // Act
- console.WriteLine(message, background: ConsoleColor.Red, foreground: ConsoleColor.DarkGreen);
- console.Flush();
-
- // Assert
- Assert.Equal(expectedMessage, systemConsole.Message);
- }
-
- private class TestAnsiSystemConsole : IAnsiSystemConsole
- {
- public string Message { get; private set; }
-
- public void Write(string message)
- {
- Message = message;
- }
- }
-
- private static string GetForegroundColorEscapeCode(ConsoleColor color)
- {
- switch (color)
- {
- case ConsoleColor.Red:
- return "\x1B[31m";
- case ConsoleColor.DarkGreen:
- return "\x1B[32m";
- case ConsoleColor.DarkYellow:
- return "\x1B[33m";
- case ConsoleColor.Gray:
- return "\x1B[37m";
- default:
- return "\x1B[39m";
- }
- }
-
- private static string GetBackgroundColorEscapeCode(ConsoleColor color)
- {
- switch (color)
- {
- case ConsoleColor.Red:
- return "\x1B[41m";
- default:
- return "\x1B[49m";
- }
- }
- }
-}
</PropertyGroup>
<ItemGroup>
<Compile Include="$(CommonPath)System\HexConverter.cs" Link="Common\System\HexConverter.cs" />
+ <Compile Include="$(CommonPath)System\Text\Json\PooledByteBufferWriter.cs" Link="Common\System\Text\Json\PooledByteBufferWriter.cs" />
<Compile Include="System\Text\Json\BitStack.cs" />
<Compile Include="System\Text\Json\Document\JsonDocument.cs" />
<Compile Include="System\Text\Json\Document\JsonDocument.DbRow.cs" />
<Compile Include="System\Text\Json\Serialization\MemberAccessor.cs" />
<Compile Include="System\Text\Json\Serialization\MetadataPropertyName.cs" />
<Compile Include="System\Text\Json\Serialization\ParameterRef.cs" />
- <Compile Include="System\Text\Json\Serialization\PooledByteBufferWriter.cs" />
<Compile Include="System\Text\Json\Serialization\PreserveReferenceHandler.cs" />
<Compile Include="System\Text\Json\Serialization\PreserveReferenceResolver.cs" />
<Compile Include="System\Text\Json\Serialization\PropertyRef.cs" />
{
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
- public static void ThrowOutOfMemoryException_BufferMaximumSizeExceeded(uint capacity)
- {
- throw new OutOfMemoryException(SR.Format(SR.BufferMaximumSizeExceeded, capacity));
- }
-
- [DoesNotReturn]
- [MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowArgumentException_DeserializeWrongType(Type type, object value)
{
throw new ArgumentException(SR.Format(SR.DeserializeWrongType, type, value.GetType()));