How to capture and log errors in program.cs with ASP.NET Core?
.NET Core version:ASP.NET Core 3.x
OS:windows server 2012 R2
I am getting an error on my website with Invalid non-ASCII or control character in header: 0x914. My website has URLs with non-ASCII characters.
my code in www.iaspnetcore.com:
public virtual IActionResult SetLanguage(string culture, string returnUrl = "")
{
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
new CookieOptions
{
Expires = DateTimeOffset.UtcNow.AddYears(1)
}
);
CultureInfo.CurrentUICulture = new CultureInfo(culture);
return Redirect(returnUrl);
}
Inspecting the std out logs for my site, I see exceptions like this:
fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[0] An unhandled exception has occurred: Invalid non-ASCII or control character in header: 0x0914
System.InvalidOperationException: Invalid non-ASCII or control character in header: 0x0914 at
Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.FrameHeaders.ThrowInvalidHeaderCharacter(Char ch) at
Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.FrameHeaders.ValidateHeaderCharacters(String headerCharacters) at
Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.FrameHeaders.ValidateHeaderCharacters(StringValues headerValues) at
Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.FrameResponseHeaders.SetValueFast(String key, StringValues value)
It seems that when a redirect happens, the code in Microsoft.AspNetCore.Http.Internal.DefaultHttpResponse.Redirect is setting a Location header, which is probably one of these URLs with a non-ASCII character
Step 1: Add .CaptureStartupErrors(true) in program.cs
Please see CaptureStartupErrors and the method .CaptureStartupErrors(true) that will help you find error.
Here is my usual config for NetCore Web Apps:
public static IWebHost BuildWebHost(string[] args) =>
WebHost .CreateDefaultBuilder(args)
.CaptureStartupErrors(true)
.UseKestrel()
.UseIISIntegration()
.UseStartup<Startup>()
.UseAzureAppServices()
.Build();
Step 2: write ErrorLoggingMiddleware log error into Database
public class ErrorLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
private readonly DiagnosticSource _diagnosticSource;
private readonly IWorkContext _workContext;
public ErrorLoggingMiddleware(RequestDelegate next,
DiagnosticSource diagnosticSource,
ILogger logger,
IWorkContext workContext)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
if (workContext == null)
{
throw new ArgumentNullException(nameof(workContext));
}
_next = next;
_diagnosticSource = diagnosticSource;
_logger = logger;
_workContext = workContext;
}
public async Task Invoke(HttpContext context)
{
var ip = "127.0.0.1";
ip = context.Request.Headers["X-Forwarded-For"].ToString();
if (!string.IsNullOrEmpty(ip))
{
ip = context.Connection.RemoteIpAddress.ToString();
}
try
{
await _next(context);
}
catch (Exception ex)
{
Log log = new Log();
log.LogLevel = LogLevel.Error;
log.ReferrerUrl = context.Request.Headers["referer"].ToString() + " " + context.Connection.RemoteIpAddress.ToString();
log.ShortMessage = "error find:by ErrorLoggingMiddleware";
log.FullMessage = context.Request.Headers["User-Agent"]+ "err message:" +ex.Message +"ex.StackTrace:"+ex.StackTrace;
log.PageUrl = context.Request.Host.ToString() + context.Request.Path.ToString() + context.Request.QueryString.ToString();
log.IpAddress = ip;
log.CreatedOnUtc = DateTime.UtcNow;
_logger.InsertLog(log);
var error = context.Features.Get();
if (error != null)
{
// This error would not normally be exposed to the client
//await context.Response.WriteAsync("Error: " + HtmlEncoder.Default.Encode(error.Error.Message) + "\r\n");
//await context.Response.WriteAsync("Error: " + HtmlEncoder.Default.Encode(ex.StackTrace) + "\r\n");
}
System.Diagnostics.Debug.WriteLine($"The following error happened: {ex.Message}");
throw; // Don't stop the error
}
}
}
Step 3: change
return Redirect(returnUrl);
to
return Redirect(Uri.EscapeUriString(returnUrl));.