Async losing context
See original GitHub issueI am losing context in serilog when doing the following
public async Task<byte[]> TestAsync(TestQuery query)
{
using (LogContext.PushProperty("CorrelationId", query.CorrelationId))
{
var builder = new Foo(this.logger);
return await builder.DoSomething(query);
}
}
When I say lose context I mean that if DoSomething continues to log then the early messages have the correlation id and the later ones don’t.
If instead I do the following all is good - i.e. the correlation id appears on all downstream messages.
public async Task<byte[]> TestAsync(TestQuery query)
{
var lgr = this.logger.ForContext("CorrelationId", query.CorrelationId);
var builder = new Foo(lgr);
return await builder.DoSomething(query);
}
Issue Analytics
- State:
- Created 8 years ago
- Comments:22 (8 by maintainers)
Top Results From Across the Web
Awaited async Task loses HttpContext.Current
In this application, we do not call ConfigureAwait(false) anywhere, so as far as I'm aware there's no reason why we should lose the...
Read more >HttpContext is lost when executing async methods #110
I want to cache the result of a method with LazyCache. That method uses other classes which are instantiated with dependency injection.
Read more >Async/await - Pitfall 2 - Synchronisation - Marc Costello
When you await a task in the .net framework, once the task is complete, ... Current } // Capture what you need prior...
Read more >Scheduled Job loses Context after api call
I have a standard Command and Schedule Task items in Sitecore that is being executed based on interval. I am trying to track...
Read more >Spring Security Context Propagation with @Async
In this tutorial, we are going to focus on the propagation of the Spring Security principal with @Async. By default, the Spring Security ......
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Couldn’t we use this class to get around this problem?
https://msdn.microsoft.com/en-us/library/dn906268(v=vs.110).aspx
Hello all,
I think we’ve run into an issue related to this one lately and would like to share some thoughts.
The problem
In our work environment we have a bunch of Web API applications. In one of them, a very simple one, we decided to do request authentication straight in the controller itself (not handler or anything like that) - well in a collaborator, which is called from the controller to be precise. We also have decorated this collaborator call with another one, which is responsible for adding an access token, upon which we authenticate, to the Serilog
LogContext
using thePushProperty
method. We thought that usingLogContext
there would be nice, because we can add the value where we extract it and it will just be there if we log something, right? Well, not entirely 😃 We discovered the problem, when an exception was thrown somewhere in further Controller code (so after adding to theLogContext
) - this exception should be handled by our customExceptionHandler
implementation and logged in our customExceptionLogger
(these are both pretty standard Web API services). So inExceptionLogger
we called the SerilogILogger
, but theLogEvent
did not contain the access token added to theLogContext
in the controller.The investigation
Now, we were not entirely sure if this was Serilog issue, our issue, Web API issue, so we started to google around and peek in the source code of Serilog and Web API a bit. The fact is that Serilog documentation provides only usages of
LogContext.PushProperty
in ausing
clause, in which this should work with no problems. So maybe the problem is that we wanted to use it in a more sophisticated way - calling thePushProperty
method in the controller stack and disposing until the request is disposed. However logging an exception in Web APIExceptionLogger
with all the information gathered previously in Handlers or Controllers doesn’t seem like a very odd scenario, does it? 😃So what we know after some investigation:
LogContext
usesCallContext
LogicalSetData
andLogicalGetData
methods under the hood.CallContext
, according to the documentation, is a “specialized collection object similar to a Thread Local Storage for method calls and provides data slots that are unique to each logical thread of execution. The slots are not shared across call contexts on other logical threads. Objects can be added to the CallContext as it travels down and back up the execution code path, and examined by various objects along the path.” And this is true for synchronous calls - as can be seen below in the reproduction section.CallContext
will be problematic - it looks like the values are copied over when invoking a method, so you can get, read and use values added in the specific class in its collaborators, but not the other way around - as also can be seen in the reproduction section.CallContext
in a scenario like that is not a way to go at all.The reproduction
I’ve written a couple of tests using Serilog
LogContext
to showcase various scenarios, but this can be done easily the same withCallContext
directly. Anyway, I’ve created a simple Serilog sink to actually capture the log event that was passed.I’ve also created two classes only for testing purposes - the names are a bit Web API inspired 😃 So I have a
Controller
class with two methods - one adding a property toLogContext
only, and one adding a property and actually logging. I also add the property to a collection, to make sure it won’t be disposed nor garbage collected.And I have a similar
Handler
class, that can either only add a property (and then asks theController
to log) or add a property and log (in this case it asksController
only to add its own property).Now this is how this behaves in a synchronous world:
All this tests are passing. So:
LogContext
in theController
and log there, it will be okay.LogContext
in theHandler
and call theController
to log, it will be okay.LogContext
in theController
and log in theHandler
after that, it will be okay.Now this looks pretty straightforward, however it changes when we get to introduce asynchronous stuff. So my
Controller
andHandler
now look like this:And the tests:
The output of this tests is that the third one is failing. So:
LogContext
in theController
and log there, it will be okay. This shows, that theCallContext
is preserved after the awaited call.LogContext
in theHandler
and call theController
to log, it will be okay. This shows, that theCallContext
is copied over to the next method call, just as in synchronous scenario.LogContext
in theController
and log in theHandler
after that, it will fail. This shows, that theCallContext
is not copied over on the way back - in theHandler
you get just those values, which you had already before the await (something similar can be found in async/await best practices https://msdn.microsoft.com/en-us/magazine/jj991977.aspx - “by default, when an incomplete Task is awaited, the current ‘context’ is captured and used to resume the method when the Task completes.”).The solution or workaround
Not sure if what we did for now is a solution or workaround, but we decided to basically use the
LogContext.PushProperty
method as it is in the documentation. So we limit ourselves not to add values toLogContext
anywhere in the app, but just before theILogger
call. This guarantees, that the values will be there. Of course in our scenario this means, that we need to provide the access token somehow to the class responsible for making the logging call, but this is something we can do in various ways, all of them probably connected to usingHttpContext.Current
- we can either extract it directly from theHttpContext.Current.Request
headers, or still have a decorator extracting the token and putting it inHttpContext.Current.Items
in one place and then take it from there just before the logging call, to put it inLogContext
.This works for us right now, and I’m not sure if anything can be done on Serilog side to avoid problems like that - that’s probably a question for @nblumhardt 😃 In this very scenario using
HttpContext
instead ofCallContext
would work, but that’s probably not something you’d like to include in the codebase just like that. But maybe in a separate package for Web? Or maybe give a chance to choose somehow in the configuration the storage forLogContext
properties? Thinking loudly here, but with Web API being still relatively popular, and used along with a custom globalExceptionHandler
and/orExceptionLogger
it might be a cause of problems for at least some people, like it was for us 😃PS We’ve also investigated how log4net handles stuff like that. There are 3 types of contexts you can add properties to in log4net - none of them would support a scenario like we had. There is a
GlobalContext
which is preserved all the time,ThreadLogicalContext
which also relies onCallContext
so will behave in a similar way like SerilogLogContext
, andThreadContext
, which is connected to current managed thread, so will have it’s own problems in ASP .NET application.