-
Notifications
You must be signed in to change notification settings - Fork 9.8k
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
HttpContext.Request.Method is empty #3627
Comments
It’s likely you’re accessing the HttpContext between requests. Are you using the IHttpContextAccessor in your application? See aspnet/KestrelHttpServer#2591 |
We do in a only 2 places but we never write on it. Only read. |
We put the following code in an middleware that runs early in the pipeline. It fixes it but we obviously we don't want to keep this:
|
Actually regarding your previous comments, I analyzed how we use IHttpContextAccessor. |
Doesn’t matter if you write or read, you are accessing it outside of the request it’ll cause this issue. Unfortunately, today the HttpContext is never null so that check won’t work. 2.1 introduced an optimization that made this happen but it’s basislly danger territory. We’ve made a couple of changes in the next version that will hopefully make the HttpContext property null when accessed outside of the request. On top of that in 3.0 the HttpContext will throw an exception when accessed outside of the requests. For your logggr scenario the best way to go about doing what you want is with a logging scope that copies all of the appropriate properties, that way there’s no chance of using the HttpContext outside of the request scope. |
I removed the code using the HttpAccessor and that fixed the issue. But I must say, this is definitely not intuitive, especially coming from previous asp.net stacks. When you say 'outside of the request', do you mean anywhere in background thread or places where the execution context / async local will not flow? Do you mean that I should create a log scope with It would be great to have this built-in. Sorry for all the questions but the doc seems very spare in that area... |
I can't seem to get it to work.
In my logger I'm trying
but zero scopes are iterated... |
It will going forward, it was an unfortunate consequence on how it was implemented.
What I really mean is using an
Yes and Yes.
There's a default logging scope but it only copies a few properties https://github.com/aspnet/Hosting/blob/5a3c6645667ec24e09d6200fb1e949f2a2417b9a/src/Microsoft.AspNetCore.Hosting/Internal/HostingLoggerExtensions.cs#L95.
That's also correct.
Yes, we should beef up the docs here @pakrym @guardrex.
I'll try to get your a sample. Are you implementing a custom LoggerProvider? What exactly are you doing? |
Still I'm confused about how the context injected in a middleware's
That would be great. However, how do I retrieved this scope / state from my |
It's not the context inject into your middleware, it's the HttpContext property accessed from IHttpContextAccessor that's the problem. You're basically grabbing it out of thin air and expecting it to be null or the "current" HttpContext for the async flow. At the moment, you can end up in a place where the request is over and that property doesn't return null as a result.
Sure you can just BeginScope to pass an object that has a set of name value pairs like I showed before that will be passed to all of the logger providers.
You're missing more of the code, implementing an ILoggerProvider is more involved. May I ask why you are implementing a custom provider instead of just using something like Serilog? |
Maybe I wasn't clear earlier.
In general I like to keep the number of package dependencies and moving parts to a minimum to keep things simple and understand what is going on. I'll post below a simplified version of my Logger/LoggerProvider
|
Maybe this will help https://stackoverflow.com/a/51063132/45091.
That's fair, but Serilog is good, really really good and well implemented by somebody that understand the complexities in logging and has very useful sinks and provider implementations.
There are better ways to do this now. In particular you can use an AsyncLocal to store the state. Also, this is built into the logging system now so you don't need to do this yourself. Since you insist on writing a custom logger provider (which I recommend against), here's an example of a bare minimum implementation: PS: You're supposed to fill the "Do the logging here" section to push this data somewhere. using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.Logging
{
public class SampleLoggerProvider : ILoggerProvider, ISupportExternalScope
{
private IExternalScopeProvider _scopeProvider;
private readonly SampleLoggerProviderOptions _options;
public SampleLoggerProvider(IOptions<SampleLoggerProviderOptions> options)
{
_options = options.Value;
}
public ILogger CreateLogger(string categoryName)
{
return new SampleLogger(categoryName, _scopeProvider, _options);
}
public void Dispose()
{
}
public void SetScopeProvider(IExternalScopeProvider scopeProvider)
{
_scopeProvider = scopeProvider;
}
}
internal class SampleLogger : ILogger
{
private readonly string _categoryName;
private readonly IExternalScopeProvider _scopeProvider;
private readonly SampleLoggerProviderOptions _options;
public SampleLogger(string categoryName, IExternalScopeProvider scopeProvider, SampleLoggerProviderOptions options)
{
_categoryName = categoryName;
_scopeProvider = scopeProvider;
_options = options;
}
public IDisposable BeginScope<TState>(TState state)
{
return _scopeProvider.Push(state);
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
var properties = new Dictionary<string, object>
{
["CategoryName"] = _categoryName,
["Message"] = formatter(state, exception),
["Level"] = logLevel
};
if (exception != null)
{
properties["Exception"] = exception.ToString();
}
if (eventId.Id != 0)
{
properties["EventId"] = eventId.Id.ToString();
}
if (!string.IsNullOrEmpty(eventId.Name))
{
properties["EventName"] = eventId.Name;
}
if (state is IEnumerable<KeyValuePair<string, object>> stateDictionary)
{
foreach (KeyValuePair<string, object> item in stateDictionary)
{
properties[item.Key] = item.Value;
}
}
// Add scopes
_scopeProvider.ForEachScope((value, loggingProps) =>
{
if (value is IEnumerable<KeyValuePair<string, object>> props)
{
foreach (var pair in props)
{
loggingProps[pair.Key] = pair.Value;
}
}
},
properties);
// Do the logging here
}
}
public static class MyLoggerProviderExtensions
{
public static ILoggingBuilder AddSampleLoggerProvider(this ILoggingBuilder builder, Action<SampleLoggerProviderOptions> configure)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, SampleLoggerProvider>());
builder.Services.Configure(configure);
return builder;
}
}
public class SampleLoggerProviderOptions
{
// Put options here
}
} |
Sorry but it doesn't. This explains why the context is in an invalid state in a background thread, but not in the Thank you so much for this sample! I will give it a try |
Your sample works well, thank you. IMHO the API is very weird though. As I mentioned I still don't understand the invalid context in the middleware's Invoke, but feel free to close this issue as you see fit. |
I guess it might be this way for perf reason: aspnet/Logging#723 |
It's invalid in the next request because you accessed the http context between the previous request and the next request in your logger provider implementation. Like I said in all versions <= 2.1, the IHttpContextAccessor.HttpContext may not return null in some cases, so you may end up with an HttpContext thats nulled out internally (which you are likely reading and logging). This in turn corrupts the next request. Here's the relevant excerpt from the stack overflow post if you didn't get through it:
aspnet/KestrelHttpServer#2591 (comment)
Yes, performance is a concern. Glad you got it working scopes are the way to go here. |
Note that you are injecting |
Got it. Looking forward to the fix in a future release. I'm pretty sure it will save other people wondering what's happening.
No actually it is the context itself that is being injected in the middleware: |
I have an app that runs asp.net core 2.1 on the full .net framework.
Recently, while developing locally, I started getting random errors.
Upon investigation, it is due to an empty HttpContext.Request.Method.
In the log instead of
We see
Notice there is no verb?!
We inspect this request inside a middleware
Invoke
methodWhat could cause this property to be empty?
I thought I would try to see the raw request and see the HTTP verb, but I couldn't find a way of seeing the raw request.
If the request really has no verb, I would imagine that asp.net core would throw an exception much earlier?
The text was updated successfully, but these errors were encountered: