-
Notifications
You must be signed in to change notification settings - Fork 23
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
Enrichers are not thread-safe #14
Comments
I'm not sure if it going to happen because |
It happens inside the same request context. The code I posted should be understood as being inside of an ASP.NET action (e.g. |
It seems the main purpose of using HttpContext.Items is to cache the LogEventProperty. I think it might be worth looking into |
I cannot understand what's the relation between |
Maybe the problem is easier to understand if I simplify the example a bit: public async Task<IActionResult> Get()
{
// Here _logger is a logger enriched with the ClientIpEnricher/ClientAgentEnricher
var task1 = Task.Run(() =>
{
_logger.Information("Log message one");
});
var task2 = Task.Run(() =>
{
_logger.Information("Log message two");
});
await Task.WhenAll(task1, task2);
return Ok();
} The above code creates a situation where logger is being called in a multi-threaded way which in the
I've noticed at least two kinds of exceptions that can be thrown:
And:
|
@basvd You are correct. I'm afraid theoretically this is a bug, when the first log of the request happens in some concurrent tasks. But I'm wondering if you have ever get this bug in a real-case scenario? Have you seen this bug in production? The workaround that you have suggested (to have a log before concurrent tasks) would almost always happens since the ASP.Net Core itself have some logs. Unless they are disabled which I guess are rarely the case! @mo-esmp To fix this bug I guess we could put a
Yes, the purpose of the PR was to reduce the |
@Kahbazi I have indeed seen this bug in real application code. The example that I describe in my original post is based on that scenario. Apparently no log is written before the controller action is called and so the running tasks will be the first to write a log, but I do agree that it is unlikely (me and my team have built an entire application with this enricher without ever running into the issue). I understand the possible solutions are not ideal in terms of complexity. The workaround/solution in application code is quite simple: Just log something before doing anything multi-threaded. And I'm fine with that. If I can think of a non-complex alternative to using the HttpContext I will consider making a pull request. |
Changing code at line 53 would fix the problem, however, any suggestion to do it in a more performant way? lock(locker)
{
if (httpContext.Items[IpAddressItemKey] is LogEventProperty logEventProperty)
return;
var ipAddressProperty = new LogEventProperty(IpAddressPropertyName, new ScalarValue(ipAddress));
httpContext.Items.Add(IpAddressItemKey, ipAddressProperty);
logEvent.AddPropertyIfAbsent(ipAddressProperty);
} |
I just realized that another solution (for projects using Just posting this here for anyone who runs into the issue looking for solutions: app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("ClientIp", httpContext.Connection.RemoteIpAddress);
diagnosticContext.Set("ClientAgent", httpContext.Request.Headers.UserAgent);
};
}); Note that if you use Edit: This will only enrich the request logs, so it does work differently from ClientInfo, which enriches all the logs during the request. |
I think with the above code then installing this package is pointless. |
Couldn't this be solved with static AsyncLocal storage instead of storing it on the HttpContext.Items? HttpContextAccessor uses AsyncLocal internally, so they would follow each other either way. |
The enrichers provided by ClientInfo are not thread-safe. This is a problem when concurrent tasks are trying to log inside the scope of a request. For example in the following situation:
Because the HttpClients are logging they both use a logger that is trying to enrich the event with some client info (e.g. using
ClientIpEnricher
). If the property (e.g.Serilog_ClientIp
) is not yet available on theHttpContext.Items
then both instances will try to write it concurrently, which may cause various exceptions to be thrown depending on when and how the conflicting access occurs.In theory this problem should only occur if the property isn't already on the
HttpContext.Items
during the concurrent phase. In other words, if the first logs of the request are written in a concurrent situation then it might occur. This means a workaround fix would be to write a log message before going concurrent (e.g. with_logger.Information("workaround fix")
before the tasks in the snippet above). In my own tests this indeed seems to work.I think there is an (implicit) expectation that Serilog enrichers are thread-safe (as mentioned here: serilog/serilog#1144) so that's why I report this as a bug.
The text was updated successfully, but these errors were encountered: