-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Enabling console logger queue length and mode to be configurable #70186
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3a45e15
0da2fd1
ace65f0
00d3e8d
1ae1440
e963406
99601ed
49a27e0
c559455
a4f9176
b55de82
a3d550a
60f5f3b
f200c8c
f74dd41
b4458be
5ded943
10fbf54
263e5f3
a369c54
04d9fb5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<LogMessageEntry> _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<LogMessageEntry> _messageQueue = new BlockingCollection<LogMessageEntry>(_maxQueuedMessages); | ||
| lock (_messageQueue) | ||
| { | ||
| _maxQueuedMessages = value; | ||
| Monitor.PulseAll(_messageQueue); | ||
maryamariyan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| } | ||
| 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; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why don't we need a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no thread would be waiting on full mode to change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what about my comment here #70186 (comment)?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh seems like this comment is related, #70186 (comment) |
||
| 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<LogMessageEntry>(); | ||
| FullMode = fullMode; | ||
| MaxQueueLength = maxQueueLength; | ||
| Console = console; | ||
| ErrorConsole = errorConsole; | ||
| // Start Console message queue processor | ||
|
|
@@ -35,59 +78,128 @@ public ConsoleLoggerProcessor(IConsole console, IConsole errorConsole) | |
|
|
||
| public virtual void EnqueueMessage(LogMessageEntry message) | ||
maryamariyan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| 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(); | ||
maryamariyan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| 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; | ||
| } | ||
|
|
||
maryamariyan marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So after the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To elaborate more
Those facts bring the following concerns:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @JanKrivanek if you can provide a sample code to demonstrate the concerns, you may open a new issue and we can track looking at that there. Thanks for your feedback.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am seeing you already logged #79812. We'll try to look at that at some point.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sample repro: https://github.com/JanKrivanek/ConsoleLogging Showing that messages are being dropped in 7.0 version of logging. |
||
| { | ||
| _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 | ||
| { | ||
| _outputThread.Join(1500); // with timeout in-case Console is locked by user input | ||
| } | ||
| catch (ThreadStateException) { } | ||
| } | ||
|
|
||
| private void CompleteAdding() | ||
| { | ||
| lock (_messageQueue) | ||
| { | ||
| _isAddingCompleted = true; | ||
| Monitor.PulseAll(_messageQueue); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| { | ||
| /// <summary> | ||
| /// Determines the console logger behavior when the queue becomes full. | ||
| /// </summary> | ||
| public enum ConsoleLoggerQueueFullMode | ||
| { | ||
| /// <summary> | ||
| /// Blocks the logging threads once the queue limit is reached. | ||
| /// </summary> | ||
| Wait, | ||
| /// <summary> | ||
| /// Drops new log messages when the queue is full. | ||
| /// </summary> | ||
| DropWrite | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.