diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.cs b/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.cs index aa9077333e6c54..dabec63ae00d39 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.cs @@ -62,6 +62,8 @@ public ConsoleLoggerOptions() { } [System.ObsoleteAttribute("ConsoleLoggerOptions.IncludeScopes has been deprecated. Use ConsoleFormatterOptions.IncludeScopes instead.")] public bool IncludeScopes { get { throw null; } set { } } public Microsoft.Extensions.Logging.LogLevel LogToStandardErrorThreshold { get { throw null; } set { } } + public int MaxQueueLength { get { throw null; } set { } } + public Microsoft.Extensions.Logging.Console.ConsoleLoggerQueueFullMode QueueFullMode { get { throw null; } set { } } [System.ObsoleteAttribute("ConsoleLoggerOptions.TimestampFormat has been deprecated. Use ConsoleFormatterOptions.TimestampFormat instead.")] public string? TimestampFormat { get { throw null; } set { } } [System.ObsoleteAttribute("ConsoleLoggerOptions.UseUtcTimestamp has been deprecated. Use ConsoleFormatterOptions.UseUtcTimestamp instead.")] @@ -77,6 +79,11 @@ public ConsoleLoggerProvider(Microsoft.Extensions.Options.IOptionsMonitor [System.ObsoleteAttribute("ConsoleLoggerOptions.UseUtcTimestamp has been deprecated. Use ConsoleFormatterOptions.UseUtcTimestamp instead.")] public bool UseUtcTimestamp { get; set; } + + private ConsoleLoggerQueueFullMode _queueFullMode = ConsoleLoggerQueueFullMode.Wait; + /// + /// Gets or sets the desired console logger behavior when the queue becomes full. Defaults to Wait. + /// + public ConsoleLoggerQueueFullMode QueueFullMode + { + get => _queueFullMode; + set + { + if (value != ConsoleLoggerQueueFullMode.Wait && value != ConsoleLoggerQueueFullMode.DropWrite) + { + throw new ArgumentOutOfRangeException(SR.Format(SR.QueueModeNotSupported, nameof(value))); + } + _queueFullMode = value; + } + } + + internal const int DefaultMaxQueueLengthValue = 2500; + private int _maxQueuedMessages = DefaultMaxQueueLengthValue; + + /// + /// Gets or sets the maximum number of enqueued messages. Defaults to 2500. + /// + public int MaxQueueLength + { + get => _maxQueuedMessages; + set + { + if (value <= 0) + { + throw new ArgumentOutOfRangeException(SR.Format(SR.MaxQueueLengthBadValue, nameof(value))); + } + + _maxQueuedMessages = value; + } + } } } diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProcessor.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProcessor.cs index 8797a5058d4801..ff48c3880c418c 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProcessor.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProcessor.cs @@ -2,8 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Metrics; using System.Runtime.Versioning; using System.Threading; @@ -12,16 +14,57 @@ namespace Microsoft.Extensions.Logging.Console [UnsupportedOSPlatform("browser")] internal class ConsoleLoggerProcessor : IDisposable { - private const int _maxQueuedMessages = 1024; + private readonly Queue _messageQueue; + private volatile int _messagesDropped; + private bool _isAddingCompleted; + private int _maxQueuedMessages = ConsoleLoggerOptions.DefaultMaxQueueLengthValue; + public int MaxQueueLength + { + get => _maxQueuedMessages; + set + { + if (value <= 0) + { + throw new ArgumentOutOfRangeException(SR.Format(SR.MaxQueueLengthBadValue, nameof(value))); + } - private readonly BlockingCollection _messageQueue = new BlockingCollection(_maxQueuedMessages); + lock (_messageQueue) + { + _maxQueuedMessages = value; + Monitor.PulseAll(_messageQueue); + } + } + } + private ConsoleLoggerQueueFullMode _fullMode = ConsoleLoggerQueueFullMode.Wait; + public ConsoleLoggerQueueFullMode FullMode + { + get => _fullMode; + set + { + if (value != ConsoleLoggerQueueFullMode.Wait && value != ConsoleLoggerQueueFullMode.DropWrite) + { + throw new ArgumentOutOfRangeException(SR.Format(SR.QueueModeNotSupported, nameof(value))); + } + + lock (_messageQueue) + { + // _fullMode is used inside the lock and is safer to guard setter with lock as well + // this set is not expected to happen frequently + _fullMode = value; + Monitor.PulseAll(_messageQueue); + } + } + } private readonly Thread _outputThread; public IConsole Console { get; } public IConsole ErrorConsole { get; } - public ConsoleLoggerProcessor(IConsole console, IConsole errorConsole) + public ConsoleLoggerProcessor(IConsole console, IConsole errorConsole, ConsoleLoggerQueueFullMode fullMode, int maxQueueLength) { + _messageQueue = new Queue(); + FullMode = fullMode; + MaxQueueLength = maxQueueLength; Console = console; ErrorConsole = errorConsole; // Start Console message queue processor @@ -35,53 +78,113 @@ public ConsoleLoggerProcessor(IConsole console, IConsole errorConsole) public virtual void EnqueueMessage(LogMessageEntry message) { - if (!_messageQueue.IsAddingCompleted) + // cannot enqueue when adding is completed + if (!Enqueue(message)) { - try - { - _messageQueue.Add(message); - return; - } - catch (InvalidOperationException) { } + WriteMessage(message); } + } - // Adding is completed so just log the message + // internal for testing + internal void WriteMessage(LogMessageEntry entry) + { try { - WriteMessage(message); + IConsole console = entry.LogAsError ? ErrorConsole : Console; + console.Write(entry.Message); + } + catch + { + CompleteAdding(); } - catch (Exception) { } } - // for testing - internal void WriteMessage(LogMessageEntry entry) + private void ProcessLogQueue() { - IConsole console = entry.LogAsError ? ErrorConsole : Console; - console.Write(entry.Message); + while (TryDequeue(out LogMessageEntry message)) + { + WriteMessage(message); + } } - private void ProcessLogQueue() + public bool Enqueue(LogMessageEntry item) { - try + lock (_messageQueue) { - foreach (LogMessageEntry message in _messageQueue.GetConsumingEnumerable()) + while (_messageQueue.Count >= MaxQueueLength && !_isAddingCompleted) + { + if (FullMode == ConsoleLoggerQueueFullMode.DropWrite) + { + _messagesDropped++; + return true; + } + + Debug.Assert(FullMode == ConsoleLoggerQueueFullMode.Wait); + Monitor.Wait(_messageQueue); + } + + if (!_isAddingCompleted) { - WriteMessage(message); + Debug.Assert(_messageQueue.Count < MaxQueueLength); + bool startedEmpty = _messageQueue.Count == 0; + if (_messagesDropped > 0) + { + _messageQueue.Enqueue(new LogMessageEntry( + message: SR.Format(SR.WarningMessageOnDrop + Environment.NewLine, _messagesDropped), + logAsError: true + )); + + _messagesDropped = 0; + } + + // if we just logged the dropped message warning this could push the queue size to + // MaxLength + 1, that shouldn't be a problem. It won't grow any further until it is less than + // MaxLength once again. + _messageQueue.Enqueue(item); + + // if the queue started empty it could be at 1 or 2 now + if (startedEmpty) + { + // pulse for wait in Dequeue + Monitor.PulseAll(_messageQueue); + } + + return true; } } - catch + + return false; + } + + public bool TryDequeue(out LogMessageEntry item) + { + lock (_messageQueue) { - try + while (_messageQueue.Count == 0 && !_isAddingCompleted) + { + Monitor.Wait(_messageQueue); + } + + if (_messageQueue.Count > 0 && !_isAddingCompleted) { - _messageQueue.CompleteAdding(); + item = _messageQueue.Dequeue(); + if (_messageQueue.Count == MaxQueueLength - 1) + { + // pulse for wait in Enqueue + Monitor.PulseAll(_messageQueue); + } + + return true; } - catch { } + + item = default; + return false; } } public void Dispose() { - _messageQueue.CompleteAdding(); + CompleteAdding(); try { @@ -89,5 +192,14 @@ public void Dispose() } catch (ThreadStateException) { } } + + private void CompleteAdding() + { + lock (_messageQueue) + { + _isAddingCompleted = true; + Monitor.PulseAll(_messageQueue); + } + } } } diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs index abed24fa2e11ea..6bbb9b7e3f46cb 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProvider.cs @@ -43,10 +43,6 @@ public ConsoleLoggerProvider(IOptionsMonitor options, IEnu _options = options; _loggers = new ConcurrentDictionary(); SetFormatters(formatters); - - ReloadLoggerOptions(options.CurrentValue); - _optionsReloadToken = _options.OnChange(ReloadLoggerOptions); - IConsole? console; IConsole? errorConsole; if (DoesConsoleSupportAnsi()) @@ -59,7 +55,14 @@ public ConsoleLoggerProvider(IOptionsMonitor options, IEnu console = new AnsiParsingLogConsole(); errorConsole = new AnsiParsingLogConsole(stdErr: true); } - _messageQueue = new ConsoleLoggerProcessor(console, errorConsole); + _messageQueue = new ConsoleLoggerProcessor( + console, + errorConsole, + options.CurrentValue.QueueFullMode, + options.CurrentValue.MaxQueueLength); + + ReloadLoggerOptions(options.CurrentValue); + _optionsReloadToken = _options.OnChange(ReloadLoggerOptions); } [UnsupportedOSPlatformGuard("windows")] @@ -123,6 +126,9 @@ private void ReloadLoggerOptions(ConsoleLoggerOptions options) #pragma warning restore CS0618 } + _messageQueue.FullMode = options.QueueFullMode; + _messageQueue.MaxQueueLength = options.MaxQueueLength; + foreach (KeyValuePair logger in _loggers) { logger.Value.Options = options; diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerQueueFullMode.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerQueueFullMode.cs new file mode 100644 index 00000000000000..d17e3662a73f70 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerQueueFullMode.cs @@ -0,0 +1,20 @@ +// 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 +{ + /// + /// Determines the console logger behavior when the queue becomes full. + /// + public enum ConsoleLoggerQueueFullMode + { + /// + /// Blocks the logging threads once the queue limit is reached. + /// + Wait, + /// + /// Drops new log messages when the queue is full. + /// + DropWrite + } +} diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.Logging.Console/src/Resources/Strings.resx index 5ef4b889b114c3..c3d2473f20cefa 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/Resources/Strings.resx +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/Resources/Strings.resx @@ -120,4 +120,13 @@ Cannot allocate a buffer of size {0}. + + {0} is not a supported queue mode value. + + + {0} must be larger than zero. + + + {0} message(s) dropped because of queue size limit. Increase the queue size or decrease logging verbosity to avoid this. You may change `ConsoleLoggerQueueFullMode` to stop dropping messages. + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleFormatterTests.cs b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleFormatterTests.cs index 8c4b68ba6193b8..6da081c4e86ab0 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleFormatterTests.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleFormatterTests.cs @@ -38,7 +38,9 @@ internal static (ConsoleLogger Logger, ConsoleSink Sink, ConsoleSink ErrorSink, var errorSink = new ConsoleSink(); var console = new TestConsole(sink); var errorConsole = new TestConsole(errorSink); - var consoleLoggerProcessor = new TestLoggerProcessor(console, errorConsole); + var bufferMode = options == null ? ConsoleLoggerQueueFullMode.Wait : options.QueueFullMode; + var maxQueueLength = options == null ? ConsoleLoggerOptions.DefaultMaxQueueLengthValue : options.MaxQueueLength; + var consoleLoggerProcessor = new TestLoggerProcessor(console, errorConsole, bufferMode, maxQueueLength); var formatters = new ConcurrentDictionary(ConsoleLoggerTest.GetFormatters(simpleOptions, systemdOptions, jsonOptions).ToDictionary(f => f.Name)); diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs index 739978be7d069e..a89c53f5322a88 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Reflection; using System.Text.Encodings.Web; using System.Text.Json; using Microsoft.Extensions.Logging.Abstractions; @@ -15,7 +16,7 @@ using Microsoft.Extensions.Options; using Xunit; -namespace Microsoft.Extensions.Logging.Test +namespace Microsoft.Extensions.Logging.Console.Test { public class ConsoleLoggerExtensionsTests { @@ -356,6 +357,48 @@ public void AddConsole_NullFormatterNameUsingSystemdFormat_AnyDeprecatedProperti Assert.Equal("HH:mm:ss ", formatter.FormatterOptions.TimestampFormat); } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [InlineData(-1)] + [InlineData(0)] + public void AddConsole_MaxQueueLengthSetToNegativeOrZero_Throws(int invalidMaxQueueLength) + { + var configs = new[] { + new KeyValuePair("Console:MaxQueueLength", invalidMaxQueueLength.ToString()), + }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection(configs).Build(); + + IServiceProvider serviceProvider = new ServiceCollection() + .AddLogging(builder => builder + .AddConfiguration(configuration) + .AddConsole(o => { }) + ) + .BuildServiceProvider(); + + // the configuration binder throws TargetInvocationException when setting options property MaxQueueLength throws exception + Assert.Throws(() => serviceProvider.GetRequiredService()); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public void AddConsole_MaxQueueLengthLargerThanZero_ConfiguredProperly() + { + var configs = new[] { + new KeyValuePair("Console:MaxQueueLength", "12345"), + }; + var configuration = new ConfigurationBuilder().AddInMemoryCollection(configs).Build(); + + var loggerProvider = new ServiceCollection() + .AddLogging(builder => builder + .AddConfiguration(configuration) + .AddConsole(o => { }) + ) + .BuildServiceProvider() + .GetRequiredService(); + + var consoleLoggerProvider = Assert.IsType(loggerProvider); + var logger = (ConsoleLogger)consoleLoggerProvider.CreateLogger("Category"); + Assert.Equal(12345, logger.Options.MaxQueueLength); + } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void AddConsole_NullFormatterName_UsingSystemdFormat_IgnoreFormatterOptionsAndUseDeprecatedInstead() { diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerProcessorTests.cs b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerProcessorTests.cs new file mode 100644 index 00000000000000..0dce61f7884d26 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerProcessorTests.cs @@ -0,0 +1,175 @@ +// 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.Diagnostics; +using System.IO; +using System.Linq; +using Microsoft.Extensions.Logging.Test.Console; +using Xunit; + +namespace Microsoft.Extensions.Logging.Console.Test +{ + public class ConsoleLoggerProcessorTests + { + private const string _loggerName = "test"; + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public void LogAfterDisposeWritesLog() + { + // Arrange + var sink = new ConsoleSink(); + var console = new TestConsole(sink); + var processor = new ConsoleLoggerProcessor(console, null!, ConsoleLoggerQueueFullMode.Wait, 1024); + + var logger = new ConsoleLogger(_loggerName, loggerProcessor: processor, + new SimpleConsoleFormatter(new TestFormatterOptionsMonitor(new SimpleConsoleFormatterOptions())), + null, new ConsoleLoggerOptions()); + Assert.Null(logger.Options.FormatterName); + UpdateFormatterOptions(logger.Formatter, logger.Options); + + // Act + processor.Dispose(); + logger.LogInformation("Logging after dispose"); + + // Assert + Assert.Equal(2, sink.Writes.Count); + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [InlineData(-1)] + [InlineData(0)] + public static void MaxQueueLength_SetInvalid_Throws(int invalidMaxQueueLength) + { + // Arrange + var sink = new ConsoleSink(); + var console = new TestConsole(sink); + var processor = new ConsoleLoggerProcessor(console, null!, ConsoleLoggerQueueFullMode.Wait, 1024); + + // Act & Assert + Assert.Throws(() => processor.MaxQueueLength = invalidMaxQueueLength); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public static void FullMode_SetInvalid_Throws() + { + // Arrange + var sink = new ConsoleSink(); + var console = new TestConsole(sink); + var processor = new ConsoleLoggerProcessor(console, null!, ConsoleLoggerQueueFullMode.Wait, 1024); + + // Act & Assert + Assert.Throws(() => processor.FullMode = (ConsoleLoggerQueueFullMode)10); + } + + [OuterLoop] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [InlineData(true)] + [InlineData(false)] + public void CheckForNotificationWhenQueueIsFull(bool okToDrop) + { + // Arrange + var sink = new ConsoleSink(); + var console = new TestConsole(sink); + var errorConsole = new TimesWriteCalledConsole(); + string queueName = nameof(CheckForNotificationWhenQueueIsFull) + (okToDrop ? "InDropWriteMode" : "InWaitMode"); + var fullMode = okToDrop ? ConsoleLoggerQueueFullMode.DropWrite : ConsoleLoggerQueueFullMode.Wait; + var processor = new ConsoleLoggerProcessor(console, errorConsole, fullMode, maxQueueLength: 1); + var formatter = new SimpleConsoleFormatter(new TestFormatterOptionsMonitor( + new SimpleConsoleFormatterOptions())); + + var logger = new ConsoleLogger(_loggerName, processor, formatter, null, new ConsoleLoggerOptions()); + Assert.Null(logger.Options.FormatterName); + UpdateFormatterOptions(logger.Formatter, logger.Options); + string messageTemplate = string.Join(", ", Enumerable.Range(1, 100).Select(x => "{A" + x + "}")); + object[] messageParams = Enumerable.Range(1, 100).Select(x => (object)x).ToArray(); + + // Act + for (int i = 0; i < 20000; i++) + { + logger.LogInformation(messageTemplate, messageParams); + } + + // Assert + if (okToDrop) + { + Assert.True(errorConsole.TimesWriteCalled > 0); + } + else + { + Assert.Equal(0, errorConsole.TimesWriteCalled); + } + } + + private class TimesWriteCalledConsole : IConsole + { + public volatile int TimesWriteCalled; + public void Write(string message) + { + TimesWriteCalled++; + } + } + + private class WriteThrowingConsole : IConsole + { + public void Write(string message) + { + throw new InvalidOperationException(); + } + } + + [OuterLoop] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public void ThrowDuringProcessLog_ShutsDownGracefully() + { + var console = new TimesWriteCalledConsole(); + var writeThrowingConsole = new WriteThrowingConsole(); + var processor = new ConsoleLoggerProcessor( + console, + writeThrowingConsole, + ConsoleLoggerQueueFullMode.Wait, + 1024); + + var formatter = new SimpleConsoleFormatter( + new TestFormatterOptionsMonitor( + new SimpleConsoleFormatterOptions())); + + var logger = new ConsoleLogger(_loggerName, processor, formatter, null, new ConsoleLoggerOptions() + { + LogToStandardErrorThreshold = LogLevel.Error + }); + + Assert.Null(logger.Options.FormatterName); + UpdateFormatterOptions(logger.Formatter, logger.Options); + logger.LogInformation("Process 1st log normally using {DesiredConsole}", nameof(TimesWriteCalledConsole)); + logger.LogInformation("Process 2nd log normally using {DesiredConsole}", nameof(TimesWriteCalledConsole)); + while (console.TimesWriteCalled != 2); // wait until the logs are processed + Assert.Equal(2, console.TimesWriteCalled); + logger.LogError("Causing exception to throw in {ClassName} using {DesiredConsole}", nameof(ConsoleLoggerProcessor), nameof(WriteThrowingConsole)); + logger.LogInformation("After the write logic threw exception, {ClassName} stopped gracefully, no longer processing next logs", nameof(ConsoleLoggerProcessor)); + Assert.Equal(2, console.TimesWriteCalled); + } + + private static void UpdateFormatterOptions(ConsoleFormatter formatter, ConsoleLoggerOptions deprecatedFromOptions) + { +#pragma warning disable CS0618 + // kept for deprecated apis: + if (formatter is SimpleConsoleFormatter defaultFormatter) + { + defaultFormatter.FormatterOptions.ColorBehavior = deprecatedFromOptions.DisableColors ? + LoggerColorBehavior.Disabled : LoggerColorBehavior.Enabled; + 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; + } +#pragma warning restore CS0618 + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerTest.cs b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerTest.cs index b611d6cc952509..0994980590122b 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerTest.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerTest.cs @@ -47,12 +47,12 @@ private static (ConsoleLogger Logger, ConsoleSink Sink, ConsoleSink ErrorSink, F var errorSink = new ConsoleSink(); var console = new TestConsole(sink); var errorConsole = new TestConsole(errorSink); - var consoleLoggerProcessor = new TestLoggerProcessor(console, errorConsole); var formatters = new ConcurrentDictionary(GetFormatters().ToDictionary(f => f.Name)); ConsoleFormatter? formatter = null; var loggerOptions = options ?? new ConsoleLoggerOptions(); + var consoleLoggerProcessor = new TestLoggerProcessor(console, errorConsole, loggerOptions.QueueFullMode, loggerOptions.MaxQueueLength); Func levelAsString; int writesPerMsg; switch (loggerOptions.Format) @@ -424,7 +424,7 @@ public void AddConsole_IsOutputRedirected_ColorDisabled() var sink = new ConsoleSink(); var console = new TestConsole(sink); var loggerOptions = new ConsoleLoggerOptions(); - var consoleLoggerProcessor = new TestLoggerProcessor(console, null!); + var consoleLoggerProcessor = new TestLoggerProcessor(console, null!, loggerOptions.QueueFullMode, loggerOptions.MaxQueueLength); var loggerProvider = new ServiceCollection() .AddLogging(builder => builder @@ -1139,26 +1139,6 @@ public void WriteCore_NullMessageWithNullException(LogLevel level) Assert.Empty(sink.Writes); } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] - public void LogAfterDisposeWritesLog() - { - // Arrange - var sink = new ConsoleSink(); - var console = new TestConsole(sink); - var processor = new ConsoleLoggerProcessor(console, null!); - var formatters = new ConcurrentDictionary(GetFormatters().ToDictionary(f => f.Name)); - - var logger = new ConsoleLogger(_loggerName, loggerProcessor: processor, formatters[ConsoleFormatterNames.Simple], null, new ConsoleLoggerOptions()); - Assert.Null(logger.Options.FormatterName); - UpdateFormatterOptions(logger.Formatter, logger.Options); - // Act - processor.Dispose(); - logger.LogInformation("Logging after dispose"); - - // Assert - Assert.True(sink.Writes.Count == 2); - } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void IsEnabledReturnsCorrectValue() { @@ -1193,6 +1173,20 @@ public void ConsoleLoggerOptions_InvalidFormat_Throws() Assert.Throws(() => new ConsoleLoggerOptions() { Format = (ConsoleLoggerFormat)10 }); } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [InlineData(-1)] + [InlineData(0)] + public void ConsoleLoggerOptions_SetInvalidMaxQueueLength_Throws(int invalidMaxQueueLength) + { + Assert.Throws(() => new ConsoleLoggerOptions() { MaxQueueLength = invalidMaxQueueLength }); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public void ConsoleLoggerOptions_SetInvalidBufferMode_Throws() + { + Assert.Throws(() => new ConsoleLoggerOptions() { QueueFullMode = (ConsoleLoggerQueueFullMode)10 }); + } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void ConsoleLoggerOptions_DisableColors_IsReadFromLoggingConfiguration() { @@ -1300,6 +1294,25 @@ public void ConsoleLoggerOptions_LogAsErrorLevel_IsAppliedToLoggers() Assert.Equal(LogLevel.Error, logger.Options.LogToStandardErrorThreshold); } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + public void ConsoleLoggerOptions_UpdateQueueOptions_UpdatesConsoleLoggerProcessorProperties() + { + // Arrange + var monitor = new TestOptionsMonitor(new ConsoleLoggerOptions()); + var loggerProvider = new ConsoleLoggerProvider(monitor); + var logger = (ConsoleLogger)loggerProvider.CreateLogger("Name"); + + // Act & Assert + Assert.Equal(ConsoleLoggerQueueFullMode.Wait, logger.Options.QueueFullMode); + Assert.Equal(ConsoleLoggerOptions.DefaultMaxQueueLengthValue, logger.Options.MaxQueueLength); + monitor.Set(new ConsoleLoggerOptions() { + QueueFullMode = ConsoleLoggerQueueFullMode.DropWrite, + MaxQueueLength = 10 + }); + Assert.Equal(ConsoleLoggerQueueFullMode.DropWrite, logger.Options.QueueFullMode); + Assert.Equal(10, logger.Options.MaxQueueLength); + } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void ConsoleLoggerOptions_UseUtcTimestamp_IsAppliedToLoggers() { @@ -1404,7 +1417,8 @@ private string CreateHeader(ConsoleLoggerFormat format, int eventId = 0) internal class TestLoggerProcessor : ConsoleLoggerProcessor { - public TestLoggerProcessor(IConsole console, IConsole errorConsole) : base(console, errorConsole) + public TestLoggerProcessor(IConsole console, IConsole errorConsole, ConsoleLoggerQueueFullMode fullMode, int maxQueueLength) + : base(console, errorConsole, fullMode, maxQueueLength) { } diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj index 5f182cf1cc3b17..c1f8f924a4b2ee 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/Microsoft.Extensions.Logging.Console.Tests.csproj @@ -1,10 +1,10 @@ - true $(NetCoreAppCurrent);$(NetFrameworkMinimum) true true + true