-
Notifications
You must be signed in to change notification settings - Fork 5
Capture NServiceBus endpoint startup logs before logger is available #239
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
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
4d089a8
Set up deferred logging
mikeminutillo 644a3c8
Switch to functions logger before flushing
mikeminutillo ebbd115
Flush deferred logger at startup
mikeminutillo 197fd5d
Combine loggers
mikeminutillo 3247258
Tests
mikeminutillo 9588185
Address review comments
mikeminutillo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| namespace ServiceBus.Tests | ||
| { | ||
| using System; | ||
| using System.Collections.Concurrent; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.Extensions.Logging; | ||
| using NServiceBus.AzureFunctions.InProcess.ServiceBus; | ||
| using NServiceBus.Logging; | ||
| using NUnit.Framework; | ||
| using LogLevel = Microsoft.Extensions.Logging.LogLevel; | ||
|
|
||
| [TestFixture] | ||
| class LoggingTests | ||
| { | ||
| [Test] | ||
| public void Always_returns_same_logger() | ||
| { | ||
| var loggerA = FunctionsLoggerFactory.Instance.GetLogger("A"); | ||
| var loggerB = FunctionsLoggerFactory.Instance.GetLogger("B"); | ||
|
|
||
| Assert.AreSame(loggerA, loggerB); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Captures_logs_before_handed_logger() | ||
| { | ||
| var logger = FunctionsLoggerFactory.Instance.GetLogger("logger"); | ||
| logger.Info("Deferred message"); | ||
|
|
||
| var fakeLogger = new FakeLogger(); | ||
|
|
||
| FunctionsLoggerFactory.Instance.SetCurrentLogger(fakeLogger); | ||
|
|
||
| Assert.AreEqual(1, fakeLogger.CapturedLogs.Count); | ||
| fakeLogger.CapturedLogs.TryDequeue(out var capturedLog); | ||
| Assert.AreEqual("Deferred message", capturedLog.message); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Forwards_logs_after_handed_logger() | ||
| { | ||
| var logger = FunctionsLoggerFactory.Instance.GetLogger("logger"); | ||
|
|
||
| var fakeLogger = new FakeLogger(); | ||
|
|
||
| FunctionsLoggerFactory.Instance.SetCurrentLogger(fakeLogger); | ||
|
|
||
| logger.Info("Forwarded message"); | ||
|
|
||
| Assert.AreEqual(1, fakeLogger.CapturedLogs.Count); | ||
| fakeLogger.CapturedLogs.TryDequeue(out var capturedLog); | ||
| Assert.AreEqual("Forwarded message", capturedLog.message); | ||
| } | ||
|
|
||
| [Test] | ||
| public void Only_first_logger_gets_deferred_messages() | ||
| { | ||
| var logger = FunctionsLoggerFactory.Instance.GetLogger("logger"); | ||
| logger.Info("Deferred message"); | ||
|
|
||
| var firstLogger = new FakeLogger(); | ||
| var secondLogger = new FakeLogger(); | ||
|
|
||
| FunctionsLoggerFactory.Instance.SetCurrentLogger(firstLogger); | ||
| FunctionsLoggerFactory.Instance.SetCurrentLogger(secondLogger); | ||
|
|
||
| Assert.AreEqual(1, firstLogger.CapturedLogs.Count); | ||
| Assert.AreEqual(0, secondLogger.CapturedLogs.Count); | ||
| } | ||
|
|
||
| [Test] | ||
| public async Task Concurrent_loggers_are_isolated() | ||
| { | ||
| var logger = FunctionsLoggerFactory.Instance.GetLogger("logger"); | ||
| var firstLoggerTask = Execute(logger, 1); | ||
| var secondLoggerTask = Execute(logger, 2); | ||
|
|
||
| await Task.WhenAll(firstLoggerTask, secondLoggerTask); | ||
|
|
||
| var firstLogger = firstLoggerTask.Result; | ||
| var secondLogger = secondLoggerTask.Result; | ||
|
|
||
| Assert.AreEqual(1, firstLogger.CapturedLogs.Count); | ||
| Assert.AreEqual(1, secondLogger.CapturedLogs.Count); | ||
|
|
||
| firstLogger.CapturedLogs.TryDequeue(out var firstLog); | ||
| Assert.AreEqual("Running task 1", firstLog.message); | ||
|
|
||
| secondLogger.CapturedLogs.TryDequeue(out var secondLog); | ||
| Assert.AreEqual("Running task 2", secondLog.message); | ||
|
|
||
| async Task<FakeLogger> Execute(ILog log, int n) | ||
| { | ||
| await Task.Yield(); | ||
| var fakeLogger = new FakeLogger(); | ||
| FunctionsLoggerFactory.Instance.SetCurrentLogger(fakeLogger); | ||
| log.Info($"Running task {n}"); | ||
| return fakeLogger; | ||
| } | ||
| } | ||
|
|
||
| class FakeLogger : ILogger | ||
| { | ||
| public ConcurrentQueue<(LogLevel level, EventId eventId, Exception exception, string message)> CapturedLogs | ||
| { | ||
| get; | ||
| } = new ConcurrentQueue<(LogLevel level, EventId eventId, Exception exception, string message)>(); | ||
|
|
||
|
|
||
| public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, | ||
| Func<TState, Exception, string> formatter) | ||
| => CapturedLogs.Enqueue((logLevel, eventId, exception, formatter(state, exception))); | ||
|
|
||
| public bool IsEnabled(LogLevel logLevel) => true; | ||
|
|
||
| public IDisposable BeginScope<TState>(TState state) => throw new NotImplementedException(); | ||
| } | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a potential race condition in a case where a caller entered the if condition and then concurrently the logger is set and the queues are emptied before the caller enters the enqueue method, essentially causing a "log loss"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The concrete logger comes from an async local. If another thread sets it then it will be in that threads async local context so it should not affect this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm not sure that's what I mean, here's what I was thinking about:
Thread1(T1): checks
concreteLogger, value isnull, enters if condition.Thread2(T2): calls
SetCurrentLoggerT2: Sets logger value to logger instance
T2: Subsequentlly calls
FlushT2: Flush empties all
defferedMessageLogsentriesT2: completes
T1: adds log entry to
defferedMessageLogsas part of the previously entered if-block.defferedMessageLogsisn't read anymore.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We used to flush on every log call which would resolve this problem. I can put that back. Given that the deferred logger will usually be empty, this shoud be quick. We'd create three enumerators, for every log call which we can probably avoid if we check the queue length before enumerating them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, because it's a queue we only ever
TryDequeue. There's no enumerator created. This should be quick.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not really sure that's worthwhile though. Arguably, there shouldn't really be log statements being created while the logger is being replaced as we haven't started processing messages yet and the endpoint started up already, so maybe this is something that doesn't necessarily need to be fixed. The only potential log statements would come from sort of background job which shouldn't really happen without an active message pump.