Closed NickCraver closed 5 years ago
I have an odd perspective on this. My desire is to ship go errors straight to the db, and view them via opserver. A few things I'd love to see for that, recognizing I'm not going to be a top-level supported use case:
I like what you're doing, @NickCraver
For the LastLoggedDate
, I've got a version of that done already. I could port it over to work with your v2 setup if you like.
One other thing that I've done for my own use (though I have thought about releasing it as an independent add-on package) is I created an ErrorStore
that doesn't actually store the error, but relies on a user created object to provide the ability to store and retrieve the errors. That probably sounds confusing but my use case for this is that I wanted to be able to store the errors from within my EntityFramework context (which is why I called my custom ErrorStore
"EntityErrorStore", even though it doesn't actually use or depend on EntityFramework and could instead be used to store Errors using whatever method the user could think of.) Besides the EntityErrorStore
class, it also defines two interfaces: IPersistenceProvider
and IPersistedError
. IPersistenceProvider
defines the methods that need to be implemented by the user created class that handles storing/retrieving the errors to/from the ultimate store (an EF context, in my case). The methods largely imitate the abstract methods of ErrorStore
, except that instead of returning Error
objects, they return IPersistedError
objects. IPersistedError
is a slimmed down version of Error
(GUID
, FullJson
, ErrorHash
, ApplicationName
, DuplicateCount
, LastLoggedDate
, DeletionDate
and IsProtected
, with only the GUID
and FullJson
properties being absolutely required). IPersistedError
is then implemented by my POCO EF CodeFirst representation of the error in the database. At runtime, the EntityErrorStore
is configured as the default store and is given a reference to the implementation of IPersistenceProvider
(though it will also try to find a suitable type in the ExecutingAssembly if it hasn't been given one before it needs to use it). For any method on the IPersistenceProvider
called by EntityErrorStore
where it gets back an IPersistedError
, it will create an Error
object using the FullJson
property and then it sets the other properties based off of the other properties of the IPersistedError
(IE DuplicateCount
, IsProtected
, etc. that are stored outside of the Json).
Sorry for the above wall of text. I just couldn't think of a simpler way of explaining it. Let me know if you're interested in it though. :wink:
@captncraig I think the story there would have to be go sending an error to an endpoint (I'd be happy to package an ASP.NET Core endpoint in the Exceptional repo) that logs to the DB, accepting JSON (or Opserver could do this). In short, it doesn't make sense not to have .NET in the flow if we're using it to view errors anyway. That and Go's packaging story is a complete trainwreck, so there's no "update the library" when the schema changes, it's a lot more work, a lot more practical skew, and just more problems overall. But throwing JSON fields that rarely change: that we can work with. It's actually how we supposed Node.js back in the day.
There's a related issue here: .NET will have n
storage providers. There already exists SQL, MySQL, Postgres, and I'll likely add Elastic and Redis in v2. Aiming directly at one of these doesn't make Exceptional multi-platform, but having an HTTP API would.
The stack trace aspect is an interesting one, as that'll need some handling. The prettification is very much for .NET format stack traces, conventions, classes, etc. It's not pluggable nor could it be. The overall pluggability of which prettifier to use is a thing maybe, but the error would have to indicate which platform it was from somehow. I'd hate to record ".NET" a billion times in a table for most shops, but an enum doesn't work. Recognizing stacks or conventions seems brittle, thoughts?
As for the individual questions:
@CptRobby I've got the date bits locally, it's pretty simple - not worried about that piece. Thanks though!
I admit I'm not following the rest - the abstraction is what ErrorStore
should already provide, what can't be done with just an ErrorStore? It controls the serialization, de-serialization, storage, etc. Why couldn't all that be done just implementing a new store? I'm sure I'm missing something...help me out here?
@NickCraver No problem. I just thought I'd offer to help on that part first since it was a low hanging branch for me. Got any other pieces I could try my hand at? :smile:
Regarding the rest, sorry about the confusion. The key point that I used was the IPersistedError
interface. That allowed me to create a POCO class (which I just called PersistedError
) in my collection of Entities that simply implemented that interface (inheriting from Error
wouldn't work very well because there are a lot of other properties that I would have to get EntityFramework to ignore). This provides for an abstraction layer where my class that implements IPersistenceProvider
handles all the communication with the database through my DbContext
(EntityFramework starting point) while the EntityErrorStore
provides all of the ErrorStore
functionality by communicating with my IPersistenceProvider
.
I probably could have just done it all from within my website's code without the abstraction layer. Then I would just have a class that inherited from ErrorStore
that knows how to use my DbContext
to get PersistedError
objects and then convert them to Error
objects and all of that. It would be very tightly coupled, obviously. And there would be the downside that I couldn't configure it in web.config because the ErrorStore.GetErrorStores
method only finds classes that are in assemblies that match the file name pattern "StackExchange.Exceptional*.dll". The way I did it, they were in a separate assembly called "StackExchange.Exceptional.Entity.dll", and I could just configure it in web.config with <ErrorStore type="Entity" />
.
The reason I wanted to use EntityFramework for storing the errors was twofold. First, I wanted to tweak the structure of the table a bit without having to modify the SQL strings in the SqlErrorStore
. And secondly, I just prefered having a single pathway to the database, controlled by a single connection string.
Hopefully that helps explain why I did it the way I did. I'm not expecting you to take this and do the same thing, that would probably be pointless. If you did want to take anything away from this though, you could change the ErrorStore
methods (GetError
, GetAllErrors
, etc) to use an interface instead of the Error
class. And when you need to make sure you have an Error
object, you can just pass it to a method that would check to see if it is an Error
then just return it as a cast, otherwise create a new Error
from the FullJson
property and set the other properties based off of the passed in IError
. This would remove some of the need for the abstraction layer mentioned above.
I know though that it would largely be pointless for the majority of users who just use the built in error stores, so I'm not really expecting anything. Just sharing with you how I've been using it. :wink:
@CptRobby Understood! I've just push a commit that changes things up. The original reasoning was based on current plans for ASP.NET Core and AppDomain which have pulled a 180 since. I changed the implementation to look at all assemblies in the domain for any inheritors of ErrorStore
. I hope that'll make life much easier for your scenario in v2 :)
@NickCraver I saw the commit. My only question regarding the change is how it would behave with the MySqlErrorStore
(for example). If there are no references to StackExchange.Exceptional.MySql.dll from a website project, would it still be loaded in the AppDomain just because of it being in the website's bin folder?
The situation I'm thinking about (convoluted yes, but not unreasonably so) is a user who has their website all set up and running. It currently is using the SqlErrorStore
and is just configured in web.config. The user decides to switch to using a MySql database for storing the errors and isn't interested in changing anything else. So the MySql database is set up, StackExchange.Exceptional.MySql.dll is dropped into the bin folder and web.config is edited. With the way the stores are currently loaded, I know that this would work. Would it work with the change that you just made?
I honestly don't know the answer. I tried searching to see how assemblies are loaded in ASP.NET, but got conflicting answers. Obviously if the AppDomain
just loads all assemblies from the bin folder when it starts up, then it would start working as soon as the AppDomain
is reloaded, which would probably happen as a result of editing web.config.
Also, this question needs to be considered for native .NET applications. One of the results that I found said that assemblies are loaded differently in ASP.NET from the way they're loaded in native .NET applications. If in either case the assemblies aren't just loaded automatically without any code references, then maybe just add a section before the point where the loaded assemblies are searched to make sure that any files in the current folder that match "StackExchange.Exceptional.*.dll" are loaded.
Just found a really well written blog post by Rick Strahl that seems to answer the question definitively: https://weblog.west-wind.com/posts/2012/Nov/03/Back-to-Basics-When-does-a-NET-Assembly-Dependency-get-loaded
In that article, it says that "ASP.NET pre-loads all assemblies referenced in the GAC assembly list and the /bin folder." But for .NET applications, the JIT compiler avoids loading any assemblies until the very moment they are needed. This means that to enable the easy drop-in approach for .NET applications, we should probably insert the following code:
var path = Path.GetDirectoryName(typeof(ErrorStore).Assembly.Location);
//Note that I added another . before the *, since we know that we already have
//StackExchange.Exceptional.dll loaded
foreach (var filename in Directory.GetFiles(path, "StackExchange.Exceptional.*.dll"))
{
Assembly.LoadFrom(filename);
}
This will ensure that StackExchange.Exceptional.MySql.dll (or any other assembly following the naming convention) is loaded even though it isn't referenced in code.
I really like the idea of having an API that we could use to log from other platforms (ie mobile apps). Our devs have been asking for this for a while to centralize our error logging.
One question though, where would this "API" (or single endpoint rather?) live? In an application that uses Exceptional? Or would we essentially spin up a new instance of it to act as just an API?
I'd be happy to help contribute to this if it's a route you'd like to pursue.
While not straightforward, depending on how you were planning on the Category
field being implemented, I could see that possibly being used for the "source" (as referenced in this comment but that doesn't really fix the issue of having it recorded millions of times.
@CptRobby I concur, addressed in 346bbde2dc96cba03bec437e48abcf31c350a62f - thanks!
Can we implement a custom module or a simple module which we can add it into dashboard page with simple information
@minhhungit What type of module are you talking about exactly? Just saying that you could add it into a dashboard page doesn't help since there are numerous types of dashboards (including StackExchange's Opserver).
And are you asking for an API to allow you to build a custom module or are you asking to be given a module ready to go?
@CptRobby some APIs that we can create a simple panel to contain a list top error as table, and link it to exception main page when we click on them (we also should have setting to disable the link) Dashboard is just a simple html page, lol
Thanks for the help everyone (especially contributors!), 2.0 alpha packages are now up on NuGet: https://www.nuget.org/packages?q=StackExchange.Exceptional
Special props to @StefanKert on lots of work for ASP.NET Core configuration and @CptRobby for fixes in 2.0 as well.
Just a heads up, your example and documents claim you can call app.UseExceptional(Configuration.GetSection("Exceptional"));
without the second parameter to further apply configuration in code, but the method actually throws an error if the second parameter is null.
In order to exclusively pull the configuration from the appsettings file , you have to use
app.UseExceptional(Configuration.GetSection("Exceptional"), config => { });
I'm actually trying to re-add Exceptional to my project, since I was unable to keep it when I ported an existing project from ASP.NET to ASP.NET Core 1.1. It'll be nice to have it back again.
Also, this works.
app.UseExceptional(Configuration.GetSection("Exceptional"), config => { config.UseExceptionalPageOnThrow = true; });
but this does not
app.UseExceptional(Configuration.GetSection("Exceptional"), config => { });
"Exceptional": {
"UseExceptionalPageOnThrow": true
}
@nickalbrecht Thanks! I fixed both of those just now, they'll appear on NuGet shortly. Please keep sending any issues you hit!
On a more feedback/suggestion note, versus bug - is it possible to use a Func<HttpRequest, bool>
for a UseExceptionalPageOnThrow
check (or some variation of it), to allow the possibility of letting certain users view the Exceptional error page, otherwise it falls through to the standard friendly error handler (or whatever else is setup)? It'd be awesome to be able to call IAuthorizationService.AuthorizeAsync() and pass the Exception as the Resource parameter to see if they are are allowed to view details for that type of error. Though that might require the signature to be something like Func<HttpRequest, Exception, bool>
unless Exceptional could just take the Policy name as the parameter and it calls AuthorizeAsync() (which would add a dependency of Microsoft.AspNetCore.Authorization
.
I know the MiniProfiler uses Func<HttpRequest, bool>
for checking things like ResultsListAuthorize
, ShouldProfile
, ResultsAuthorize
, etc.
The use case for me is that I have razor views for certain tasks stored in a database that an end user can alter (email templates for example), and it'd be nice to let them see their own errors when the error is due to the dynamic compilation, but not let them see exceptions core to the application we provide to them.
It would be fantastic if v2 offered a better display on mobile devices. So often I'm trying to troubleshoot production issues while away from my computer and it would be nice to not have to pinch zoom and be able to get to the information I need faster. A thought here is to embed bootstrap and use the grid layout. A more in depth option would be to leverage jQuery Data Tables (which has a bootstrap version), but provides some additional functionality such as sorting and searching, but would come at the cost of more dependencies.
On Tue, Aug 15, 2017 at 1:55 PM, Nick Albrecht notifications@github.com wrote:
On a more feedback/suggestion note, versus bug - is it possible to use a Func<HttpRequest, bool> for a UseExceptionalPageOnThrow check (or some variation of it), to allow the possibility of letting certain users view the Exceptional error page, otherwise it falls through to the standard friendly error handler (or whatever else is setup)? It'd be awesome to be able to call IAuthorizationService.AuthorizeAsync() and pass the Exception as the Resource parameter to see if they are are allowed to view details for that type of error. Though that might require the signature to be something like Func<HttpRequest, Exception, bool> unless Exceptional could just take the Policy name as the parameter and it calls AuthorizeAsync() (which would add a dependency of Microsoft.AspNetCore.Authorization.
I know the MiniProfiler uses Func<HttpRequest, bool> for checking things like ResultsListAuthorize, ShouldProfile, ResultsAuthorize, etc.
The use case for me is that I have razor views for certain tasks stored in a database that an end user can alter (email templates for example), and it'd be nice to let them see their own errors when the error is due to the dynamic compilation, but not let them see exceptions core to the application we provide to them.
— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/NickCraver/StackExchange.Exceptional/issues/85#issuecomment-322586121, or mute the thread https://github.com/notifications/unsubscribe-auth/AAU-504X_iVS1oHtLnJqBPPriIeeYjwvks5sYgWygaJpZM4OZe7z .
I've got the Exceptions list working but nothing else. Any of the child 'KnownRoutes', I was getting a 404 error on.
Here's what I have, since it contributed to my problem, and might help others.
I have an ErrorsController that handles feedback for BadRequest (400), Forbidden (403), and NotFound (404), etc. I've added a new action for Exceptional as suggested
[AllowAnonymous]
[Route("~/errors/[action]")]
public class ErrorsController : Controller
{
public async Task Exceptions() => await StackExchange.Exceptional.ExceptionalMiddleware.HandleRequestAsync(HttpContext);
[Route("~/NotFound", Name = "NotFound")]
[Route("~/errors/404", Name = "NotFoundCode")]
public new IActionResult NotFound()
{
return View("Error_404");
}
/* All my other actions here */
}
And this is wired up in the Startup.cs under Configure()
app.UseExceptional(Configuration.GetSection("Exceptional"), config => { config.UseExceptionalPageOnThrow = true; });
app.UseStatusCodePagesWithReExecute("/errors/{0}");
I can view the exceptions at ~/errors/exceptions/
, but since CSS and JS failed, there's no styling on the page and no JS works. Clicking on any of the errors redirects me to a url that looks like ~/errors/exceptions/info?guid={ID HERE}
but that's a 404 as well.
Not sure what's wrong here?
UPDATE:
Because I had a route attribute on my ErrorController that customized the route, I essentially broke the routing for Exceptional beyond the root Exception list. If you want to do the same as what I did above, be sure to decorate your Exceptions that Exceptional asks for, with a catch all at the end. For example, mine now looks like this.
[Route("~/errors/exceptions/{*resource}")]
public async Task Exceptions() => await StackExchange.Exceptional.ExceptionalMiddleware.HandleRequestAsync(HttpContext);
Using the word 'resource' in the route is not important, it just needs to have something there. It's the *
that is important to mark anything after the action name as optional. Just using {*}
without a name is not valid.
@ctorx The problem is that introduces a dependency on Bootstrap and jQuery (already used). If you need to customize the look of these pages you're better off to implement your own handler for the call to view exceptions to replace the call to public async Task Exceptions() => await StackExchange.Exceptional.ExceptionalMiddleware.HandleRequestAsync(HttpContext);
you would place in your controller.
Since the logic for rendering the page's content is in StackExchange.Exceptional.Shared
and needed to be generic, you'd even be able to customize it more by overriding it since you be able to more tightly couple it to ASP.NET or ASP.NET Core depending on what you're using. Like using RazorPages.
UPDATE: This got me curious, so I went and implemented this with ASPNET Core, it's RIDICULOUSLY easy to do with razor pages. I'm actually really surprised.
It is not PERFECT. I didn't place a version number at the bottom of the page, and the ellipse character is messed up. It also ONLY replaces the main List page, Info would be a separate page, and all of the other known routes for JS are still handled internally to Exceptional. But still.
https://gist.github.com/nickalbrecht/8f0c8f8cf82906049eb1e266f81e6f59
Note: This is not a new list. I basically translated @NickCraver's StringBuilder implementation out to a cshtml file
It's also worth pointing out that while this is denoted as 2.0, a lot of the changes are only refactorings to get a reusable Shared
package, and have the AspNet
/AspNetCore
packages take care of wiring up to their own respective pipelines. It doesn't use some of the features you might expect to be available when using AspNetCore
. For example, there is no instance information registered with DI, settings are still stored on a static property on the Settings
class called Current
. Also, be aware that the AspNet Core implementation is a middleware. It will not record exception information recorded by calling the methods on an instance of Microsoft.Extensions.Logging. ILogger<>
. Instead there are extension methods that hang off of Exception instances, Log()
.
This is not to criticize, more to point out to others who have been using AspNet Core for a while, and might expect these aspects to be available.
Would it be possible to look at exposing the Or alternatively (and likely a better separation of concerns) separate the behavior of wiring up the middleware for recording exceptions from the Exceptional variation of the developer exception page so they are distinct? Then we could do something like this.ConfigSettings
type to not be internal? Or provide a way to populate an instance of Settings without having to call the builder extension methods that also wireup the middleware? I tried to implement my own take on wiring up the middle ware to try and test getting the UseExceptionalPageOnThrow logic to use more than just a boolean, but it's made more complicated by the class being internal, and no way to populate the settings without it. As it stands, all I can think of is to call UseExceptional, remove the ExceptionalMiddleware immediately afterwards, and then add my own just so that I can override the Invoke()
method to flesh out that if statement.
app.UseExceptional(Configuration.GetSection("Exceptional"));
app.UseExceptionalPageOnThrow();
I'm trying all of this on AspNetCore, not sure if its a contributing factor. Wanted to mention a few other bugs before I head home.
Clicking on the lock icon to protect an exception and clicking the delete icon both work, but the response results in a Javascript error. Error details are from Chrome. IE gives an error too though it complains about expecting ';'
VM146:1 Uncaught SyntaxError: Unexpected token :
at ir (js?v=bqZI4yofKeBK6AnXz7kIBjLmjYvI+Ywn4rLXWJPjJb6r7hqqKEyE2XgzVKLAO64cyzfdMoVjHL0l3nWHkF08lQ==:2)
at Function.globalEval (js?v=bqZI4yofKeBK6AnXz7kIBjLmjYvI+Ywn4rLXWJPjJb6r7hqqKEyE2XgzVKLAO64cyzfdMoVjHL0l3nWHkF08lQ==:2)
at text script (js?v=bqZI4yofKeBK6AnXz7kIBjLmjYvI+Ywn4rLXWJPjJb6r7hqqKEyE2XgzVKLAO64cyzfdMoVjHL0l3nWHkF08lQ==:2)
at ho (js?v=bqZI4yofKeBK6AnXz7kIBjLmjYvI+Ywn4rLXWJPjJb6r7hqqKEyE2XgzVKLAO64cyzfdMoVjHL0l3nWHkF08lQ==:2)
at b (js?v=bqZI4yofKeBK6AnXz7kIBjLmjYvI+Ywn4rLXWJPjJb6r7hqqKEyE2XgzVKLAO64cyzfdMoVjHL0l3nWHkF08lQ==:2)
at XMLHttpRequest.<anonymous> (js?v=bqZI4yofKeBK6AnXz7kIBjLmjYvI+Ywn4rLXWJPjJb6r7hqqKEyE2XgzVKLAO64cyzfdMoVjHL0l3nWHkF08lQ==:2)
Clicking on the "Clear all non-protected errors" link at the bottom of the page results in an InvalidOperationException
Incorrect Content-Type
at Microsoft.AspNetCore.Http.Features.FormFeature.ReadForm()
at StackExchange.Exceptional.ExceptionalMiddleware.<HandleRequestAsync>d__6.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at XXX.Controllers.ErrorsController.<Exceptions>d__0.MoveNext()
Lastly, once you protect an exception, you can't un-protect it.
@nickalbrecht Thanks for all the feedback, lots of good stuff here. Would you mind filing the "things I'd expect in ASP.NET Core" as an issue? Let's enumerate those, I agree things like ILogger
and such should be implemented and having a good list of these types of issues would be awesome.
I'll think about the Func
for the exception page, the problem there is that Settings
has to be platform agnostic (since the HttpContext
primitives and such differ). I agree that's a totally valid use case, will gather some ideas on what we can do.
@ctorx I'm all for a mobile view. And also one in Opserver, I just don't know what it looks like. It's not a technical issue, but a design one...if someone can come up with a good design that works (including long error message strings, maybe truncated on mobile), we can get to work. I don't think it'd take much, there's just not that much HTML or styling here.
I think the libraries are likely drastic overkill for the weight here, but a nice layout is totally doable, if we can get a picture of it to build.
I'm happy to work on it and I have some ideas. I was thinking of taking the approach suggested by the other Nick in this thread and going with the addon approach, in a separate Repo. Let me know if you have a different suggestion.
On Aug 16, 2017 3:25 PM, "Nick Craver" notifications@github.com wrote:
@ctorx https://github.com/ctorx I'm all for a mobile view. And also one in Opserver, I just don't know what it looks like. It's not a technical issue, but a design one...if someone can come up with a good design that works (including long error message strings, maybe truncated on mobile), we can get to work. I don't think it'd take much, there's just not that much HTML or styling here.
I think the libraries are likely drastic overkill for the weight here, but a nice layout is totally doable, if we can get a picture of it to build.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/NickCraver/StackExchange.Exceptional/issues/85#issuecomment-322916585, or mute the thread https://github.com/notifications/unsubscribe-auth/AAU-54eVNBRX5gvij7TumFXpyV7s0aMIks5sY2xKgaJpZM4OZe7z .
@ctorx I'm not sure how an addon makes sense here. It should be the same or similar HTML with a dynamic layout based on screensize, probably. Getting very complicated just makes little sense here, and doesn't bode well for cross-platform. IMO, this should be in the core repo...but again: tech isn't the issue, just design in. Code makes little sense before a picture here.
@NickCraver I agree, there's not much here, and it will take little effort to make a mobile friendly display. Going responsive IMO is the best path forward. As I said before, I'd be happy to put something together, but I'd like to know if there is a preference to go vanilla HTML/CSS here or take on a dependency of a framework such as bootstrap?
On Wed, Aug 16, 2017 at 5:28 PM, Nick Craver notifications@github.com wrote:
@ctorx https://github.com/ctorx I'm not sure how an addon makes sense here. It should be the same or similar HTML with a dynamic layout based on screensize, probably. Getting very complicated just makes little sense here, and doesn't bode well for cross-platform. IMO, this should be in the core repo...but again: tech isn't the issue, just design in. Code makes little sense before a picture here.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/NickCraver/StackExchange.Exceptional/issues/85#issuecomment-322935735, or mute the thread https://github.com/notifications/unsubscribe-auth/AAU-537yBcHjTq-2xPgucX8j9vqiScGgks5sY4k4gaJpZM4OZe7z .
@ctorx Definitely not a framework dependency, it's just dramatic overkill for the goal. Making something responsive isn't hard, if you look at the entirely of the stylin for the 2 pages, this is it.
We also include the styles inline when rendering an error page on-occurrence directly to the user in ASP.NET (we can't depend on another request or the pathing there), bootstrap would be huge to do that (and be 99% wasted). With that in mind, using bootstrap for only the listing page is an even weirder/less useful dependency.
The tech side is easy here, just need a picture/layout that works well here and the CSS/design will be fairly trivial in comparison.
I think the detail page is mostly fine - a few tweaks on font sizing and dealing with the stack trace are probably the only real changes needed.
The list page is a bit trickier because you're displaying a lot of tabular data horizontally. You can easily stack elements but you lose the contextual helpers from the thead that tell the user what they're looking at.
I don't think it's a good idea to hide columns to achieve mobile friendliness either.
As I said, I'm happy to help on this, but I also don't want to waste my time working on something that's just going to get redone. It sounded like you wanted to see something, visually, so I put this together, using only CSS (no changes on underlying html).
I think making the pages mobile friendly is great (ideally html and css only), but I don't think heavy customization should be in the scope of Exceptional. If someone wants to skin them to line up with their application, or making them use something like bootstrap or theme X, like they can override the pages used and make their own. It's not complicated at all. Granted in my experimenting, I was using RazorPages, which is unique to ASPNET Core 2, I have not tried with ASP.NET MVC.
I think separating the Exceptional error logging the Exceptional Developer page is a the way to go though. The two aren't really related to one another. This way if someone wants to customize the developer page logic (when do they see friendly errors, when do they see the full developer page) they can do it without having to reengineer the way the error logging functions. My use case is valid, but I'm not certain its something that should be natively handled by Exceptional, versus maybe a small section in documentation explaining how to accomplish the task, should the developer wish to do it.
I'll look at creating an issue with the ASP.NET Core related expections
I know originally it was mentioned that Exceptional would require ASP.NET Core 2.0, so that it'd be able to log exceptions at the application level, instead of just just logging exceptions inside of the middleware request pipeline. Thus work on Exceptional was on hold. Is that still the case? I don't see any mention of 2.0, and all of the references seem to be 1.1.2 versions of the MVC packages?
@nickalbrecht Not on hold, just working on MiniProfiler this week and ASP.NET Core 2 was just released...so wasn't an option before. Next alpha will ref 2.x packages in the netstandard2.0
build.
I'm getting a really weird fatal error in the sample ASP.NET Core project with Exceptional. I tried to make some changes in a fork, couldn't get it to stop crashing. Ended up flipping back to the master branch and ditching my changes. Even just using the original fork I'm getting the same behavior.
Update: I can reproduce this on one system, but not on another (both are clean clones of the repository). Will try to look into it further later
ASP.NET Core Web Server log
Samples.AspNetCore> Hosting environment: Development
Samples.AspNetCore> Content root path: C:\Users\Nick\Documents\GitHub\StackExchange.Exceptional\samples\Samples.AspNetCore
Samples.AspNetCore> Now listening on: http://localhost:28235
Samples.AspNetCore> Application started. Press Ctrl+C to shut down.
Samples.AspNetCore> Assert Failure
Samples.AspNetCore> Expression: [Recursive resource lookup bug]
Samples.AspNetCore> Description: Infinite recursion during resource lookup within System.Private.CoreLib. This may be a bug in System.Private.CoreLib, or potentially in certain extensibility points such as assembly resolve events or CultureInfo names. Resource name: ArgumentNull_Generic
Samples.AspNetCore> Stack Trace:
Samples.AspNetCore> at System.SR.InternalGetResourceString(String key)
Samples.AspNetCore>
Samples.AspNetCore> at System.SR.GetResourceString(String resourceKey, String defaultString)
Samples.AspNetCore>
Samples.AspNetCore> at System.ArgumentNullException..ctor(String paramName)
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(Assembly assembly)
Samples.AspNetCore>
Samples.AspNetCore> at System.Reflection.Assembly.LoadFromResolveHandler(Object sender, ResolveEventArgs args)
Samples.AspNetCore>
Samples.AspNetCore> at System.AppDomain.OnAssemblyResolveEvent(RuntimeAssembly assembly, String assemblyFullName)
Samples.AspNetCore>
Samples.AspNetCore> at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks, IntPtr ptrLoadContextBinder)
Samples.AspNetCore>
Samples.AspNetCore> at System.Reflection.RuntimeAssembly.InternalGetSatelliteAssembly(String name, CultureInfo culture, Version version, Boolean throwOnFileNotFound, StackCrawlMark& stackMark)
Samples.AspNetCore>
Samples.AspNetCore> at System.Resources.ManifestBasedResourceGroveler.GetSatelliteAssembly(CultureInfo lookForCulture, StackCrawlMark& stackMark)
Samples.AspNetCore>
Samples.AspNetCore> at System.Resources.ManifestBasedResourceGroveler.GrovelForResourceSet(CultureInfo culture, Dictionary`2 localResourceSets, Boolean tryParents, Boolean createIfNotExists, StackCrawlMark& stackMark)
Samples.AspNetCore>
Samples.AspNetCore> at System.Resources.ResourceManager.InternalGetResourceSet(CultureInfo requestedCulture, Boolean createIfNotExists, Boolean tryParents, StackCrawlMark& stackMark)
Samples.AspNetCore>
Samples.AspNetCore> at System.Resources.ResourceManager.InternalGetResourceSet(CultureInfo culture, Boolean createIfNotExists, Boolean tryParents)
Samples.AspNetCore>
Samples.AspNetCore> at System.Resources.ResourceManager.GetString(String name, CultureInfo culture)
Samples.AspNetCore>
Samples.AspNetCore> at System.SR.InternalGetResourceString(String key)
Samples.AspNetCore>
Samples.AspNetCore> at System.SR.GetResourceString(String resourceKey, String defaultString)
Samples.AspNetCore>
Samples.AspNetCore> at System.ArgumentNullException..ctor(String paramName)
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(Assembly assembly)
Samples.AspNetCore>
Samples.AspNetCore> at System.Reflection.Assembly.LoadFromResolveHandler(Object sender, ResolveEventArgs args)
Samples.AspNetCore>
Samples.AspNetCore> at System.AppDomain.OnAssemblyResolveEvent(RuntimeAssembly assembly, String assemblyFullName)
Samples.AspNetCore>
Samples.AspNetCore> at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks, IntPtr ptrLoadContextBinder)
Samples.AspNetCore>
Samples.AspNetCore> at System.Reflection.RuntimeAssembly.InternalGetSatelliteAssembly(String name, CultureInfo culture, Version version, Boolean throwOnFileNotFound, StackCrawlMark& stackMark)
Samples.AspNetCore>
Samples.AspNetCore> at System.Resources.ManifestBasedResourceGroveler.GetSatelliteAssembly(CultureInfo lookForCulture, StackCrawlMark& stackMark)
Samples.AspNetCore>
Samples.AspNetCore> at System.Resources.ManifestBasedResourceGroveler.GrovelForResourceSet(CultureInfo culture, Dictionary`2 localResourceSets, Boolean tryParents, Boolean createIfNotExists, StackCrawlMark& stackMark)
Samples.AspNetCore>
Samples.AspNetCore> at System.Resources.ResourceManager.InternalGetResourceSet(CultureInfo requestedCulture, Boolean createIfNotExists, Boolean tryParents, StackCrawlMark& stackMark)
Samples.AspNetCore>
Samples.AspNetCore> at System.Resources.ResourceManager.InternalGetResourceSet(CultureInfo culture, Boolean createIfNotExists, Boolean tryParents)
Samples.AspNetCore>
Samples.AspNetCore> at System.Resources.ResourceManager.GetString(String name, CultureInfo culture)
Samples.AspNetCore>
Samples.AspNetCore> at System.SR.InternalGetResourceString(String key)
Samples.AspNetCore>
Samples.AspNetCore> at System.SR.GetResourceString(String resourceKey, String defaultString)
Samples.AspNetCore>
Samples.AspNetCore> at System.Diagnostics.StackTrace.ToString(TraceFormat traceFormat)
Samples.AspNetCore>
Samples.AspNetCore> at System.String.Concat(Object arg0, Object arg1, Object arg2)
Samples.AspNetCore>
Samples.AspNetCore> at StackExchange.Exceptional.Error..ctor(Exception e, String category, String applicationName, Boolean rollupPerServer, Dictionary`2 initialCustomData) in C:\Users\Nick\Documents\GitHub\StackExchange.Exceptional\src\StackExchange.Exceptional.Shared\Error.cs:line 76
Samples.AspNetCore>
Samples.AspNetCore> Application is shutting down...
Samples.AspNetCore> at StackExchange.Exceptional.Extensions.<LogAsync>d__1.MoveNext() in C:\Users\Nick\Documents\GitHub\StackExchange.Exceptional\src\StackExchange.Exceptional.AspNetCore\Extensions.cs:line 95
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at StackExchange.Exceptional.Extensions.LogAsync(Exception ex, HttpContext context, String category, Boolean rollupPerServer, Dictionary`2 customData, String applicationName)
Samples.AspNetCore>
Samples.AspNetCore> at Samples.AspNetCore.Controllers.TestController.<Throw>d__0.MoveNext() in C:\Users\Nick\Documents\GitHub\StackExchange.Exceptional\samples\Samples.AspNetCore\Controllers\TestController.cs:line 12
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at Samples.AspNetCore.Controllers.TestController.Throw()
Samples.AspNetCore>
Samples.AspNetCore> at lambda_method(Closure , Object , Object[] )
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.Extensions.Internal.ObjectMethodExecutor.ExecuteAsync(Object target, Object[] parameters)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeActionMethodAsync>d__12.MoveNext()
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync()
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextActionFilterAsync>d__10.MoveNext()
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextActionFilterAsync>d__10.MoveNext()
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync()
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextActionFilterAwaitedAsync>d__11.MoveNext()
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAwaitedAsync()
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Controller.<OnActionExecutionAsync>d__27.MoveNext()
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Controller.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ControllerActionFilter.<OnActionExecutionAsync>d__4.MoveNext()
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ControllerActionFilter.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeInnerFilterAsync>d__14.MoveNext()
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync()
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeNextResourceFilter>d__22.MoveNext()
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeFilterPipelineAsync>d__17.MoveNext()
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeAsync>d__15.MoveNext()
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Mvc.Internal.MvcRouteHandler.<>c__DisplayClass8_0.<RouteAsync>b__0(HttpContext c)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Builder.RouterMiddleware.<Invoke>d__4.MoveNext()
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
Samples.AspNetCore>
Samples.AspNetCore> at StackExchange.Exceptional.ExceptionalMiddleware.<Invoke>d__5.MoveNext() in C:\Users\Nick\Documents\GitHub\StackExchange.Exceptional\src\StackExchange.Exceptional.AspNetCore\ExceptionalMiddleware.cs:line 56
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at StackExchange.Exceptional.ExceptionalMiddleware.Invoke(HttpContext context)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Server.IISIntegration.IISMiddleware.<Invoke>d__11.MoveNext()
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Server.IISIntegration.IISMiddleware.Invoke(HttpContext httpContext)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersMiddleware.Invoke(HttpContext context)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware.<Invoke>d__3.MoveNext()
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware.Invoke(HttpContext httpContext)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Hosting.Internal.HostingApplication.ProcessRequestAsync(Context context)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.Frame`1.<ProcessRequestsAsync>d__2.MoveNext()
Samples.AspNetCore>
Samples.AspNetCore> at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
Samples.AspNetCore>
Samples.AspNetCore> at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<>c__DisplayClass4_0.<OutputAsyncCausalityEvents>b__0()
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Server.Kestrel.Internal.System.IO.Pipelines.Pipe.<>c.<.cctor>b__67_3(Object o)
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.LoggingThreadPool.<>c__DisplayClass6_0.<Schedule>b__0()
Samples.AspNetCore>
Samples.AspNetCore> at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.LoggingThreadPool.<RunAction>b__3_0(Object o)
Samples.AspNetCore>
Samples.AspNetCore> at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
Samples.AspNetCore>
Samples.AspNetCore> at System.Threading.ThreadPoolWorkQueue.Dispatch()
The steps to reproduce are odd though. I just run the current master
branch in VS2017 (Update 3). Click on the Exceptions
menu item, navigate back, then click on Throw
. It crashes the process. It will wait for a while and then the browser gets a "HTTP Error 502.3 - Bad Gateway" response code.
However, if you click Throw first, without going to the Exceptions list beforehand, it all works great, and I can navigate between the two links freely after that. o.O
@NickCraver Is the release version Exceptional 2.0 planned to be compatible with ASP.NET Core 1.1, even though it'd be middleware exception handling only, or was that more just to get a head start on things with what was available until ASP.NET Core 2.0 was released?
@ctorx Really like the mobile view. I was wondering if we could get mobile into Exceptional, because of the same reasons.
@nickalbrecht I tried to reproduce this bug, but there is now exception thrown. Do you have any changes in your local repo?
The steps I took to reproduce:
everything works fine for me.
I also played around with the Settings and tried to come up with something that is a little more aspnetcore like:
public static IServiceCollection AddExceptional(this IServiceCollection services, IConfiguration configuration, Action<Settings> configureSettings = null)
{
_ = services ?? throw new ArgumentNullException(nameof(services));
var configSettings = new ConfigSettings(configuration);
return services.Configure<Settings>(x => {
configSettings.Populate(x);
configureSettings?.Invoke(x);
});
}
So basically the enduser would have to AddExceptional()
in his/her Configure method to get the Settings into the DI container.
The Middleware method would then look like this:
public static IApplicationBuilder UseExceptional(this IApplicationBuilder builder)
{
_ = builder ?? throw new ArgumentNullException(nameof(builder));
return builder.UseMiddleware<ExceptionalMiddleware>();
}
So basically the Settings will be injected into the ExceptionMiddleware
if the AddExceptional is called. If this is a solution that makes sense, we need to agree on what happens if the dev doesn´t call AddExceptional
. Should we make this compulsory or optional?
Basically the call would look like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddExceptional(Configuration.GetSection("Exceptional"), settings =>
{
//settings.ApplicationName = "Samples.AspNetCore";
settings.UseExceptionalPageOnThrow = Environment.IsDevelopment();
});
}
If this makes sense to you I´d be glad to fill a PR
EDIT:
If we want to pass around the static Settings.Current
instance we probably have to add Settings
as Singleton to our ServiceContainer
public static IServiceCollection AddExceptional(this IServiceCollection services, IConfiguration configuration, Action<Settings> configureSettings = null)
{
_ = services ?? throw new ArgumentNullException(nameof(services));
var configSettings = new ConfigSettings(configuration);
configSettings.Populate(Settings.Current);
configureSettings?.Invoke(Settings.Current);
return services.AddSingleton(Settings.Current);
}
But in this case we have to inject Settings
directly instead of injecting IOptions<Settings>
.
Also I really like the way other aspnetcore libs let you build up settings. For example IdentityServer/IdentityServer4 is configured like this:
I bet it would be cool to introduce a similar configuration for Exceptional too. Probably something similar to this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddExceptional(Configuration.GetSection("Exceptional"), settings =>
{
//settings.ApplicationName = "Samples.AspNetCore";
settings.UseExceptionalPageOnThrow = Environment.IsDevelopment();
})
.AddLogFilterSettings(new List<LogFilter>())
.AddIgnoreSettings(new List<IgnoreSettings>())
.AddEmailErrorNotifier(); //This would throw if no emailsettings are available.
}
@StefanKert Being able to chain all the Add*()
methods to build up the settings is just syntax sugar really. It doesn't really make any difference one way or another, flexibility-wise, if the setup for Exceptional is done using the Action<Settings> configureSettings
parameter or using Add*()
extension methods as a pattern. I'm not sure what the use case would be to warrant the extra complexity?
Keep in mind it applies the configuration in steps too. The default of Exceptional settings first, then the appsettings file, then the delegates for further customizing the setup. No single source is the defacto authority on what configuration to use. If you pass in strongly typed lists instead of a delegate, you now have to handle how to merge the list you provided with the values it already has. What if their are duplicates? It adds a bit of complexity to be able to have that syntax.
As for the exception, It's reproducible every time for me. I deleted my Exceptional folder, cloned it fresh from github, and I get the same problem. Not sure what the issue is. I'll try and replicate it on my home PC later and see if it's something I can get on more than one system.
On a note of suggestion for the ASPNET Core implementation, I noticed that the AntiForgeryTokens stored in the cookie by ASPNET Core appears to be using dynamically generated names. For example, .AspNetCore.Antiforgery.AzZM2ugLSco
. Because of the exact matching behavior for cookies, you'll never be able to mask antiforgery values if the name changes like this. I realize any attempt at handling this would have to be done in the Shared library, but perhaps we could introduce some sort of wildcard matching for cookie/form-field names?
Doesn't seem like the logic to get the IP works in v2. I noticed it was always recording it as 0.0.0.0 on my machine, but I chalked that up to bit being a dev environment at the time. I pushed a copy to a staging environment this afternoon, and it's being recorded as 0.0.0.0 there too.
ApplicationName is used to filter the root Exceptions list when multiple applications are using a centralized error store, but it is not enforced when viewing any exception's Info page. This means the error details for any logged error can be reached through any other application using the same store so long as you know the ID for the error.
Expected Behavior: If the ApplicationName of the current Exceptional's Settings context does not match that of the error requested and retrieved from the store, it should return NotFound (404) for the result. (Same for any of the other routes accepting JSON requests to protect, clear etc)
@nickalbrecht Well I think it would really remove some complexity and many other frameworks use this procedure of configuring too. So this is something that is well known to many aspnetcore devs. Like you correctly mentioned there are steps for the configuration:
default -> settings file -> lambda expression
Correct, but each one in this line replaces the item before, so with the Add* methods this would replace the other settings. I would also replace the Lists as a whole because last setting wins. Maybe I´ve got another, better example for the use of this extension methods:
What really makes a difference if you put it in settings is the chosen Errorstore
. Is it JSON or SQL or MySQL you name it. Introducing a Extension method that would handle this settings for you would really lower the barrier of getting Exceptional setup. So if I use my example from above:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddExceptional(Configuration.GetSection("Exceptional"), settings =>
{
//settings.ApplicationName = "Samples.AspNetCore";
settings.UseExceptionalPageOnThrow = Environment.IsDevelopment();
})
.AddSqlErrorStore("ConnectionString")); //This would throw if no emailsettings are available.
}
The pro for this would be that the user gets implicit information on the needed params for the chosen ErrorStore
. No need to look into the docs.
Doesn't seem like the logic to get the IP works in v2. I noticed it was always recording it as 0.0.0.0 on my machine, but I chalked that up to bit being a dev environment at the time. I pushed a copy to a staging environment this afternoon, and it's being recorded as 0.0.0.0 there too.
Got the same issue. I will try to reproduce this and give further information.
@StefanKert Can't reproduce the Exceptional bug with a clean checkout on my home PC, but I still can at work. I'll try and see if I can find out any further info later, very strange since the environments SHOULD be identical, obviously something isn't. I've cleared the packages cache, update VS2017 to the most recent on both systems, not entirely sure what else I should try to clean out on the work PC.
Is it possible for v2 have much better filtering compatibilities, i.e. filtering all possible fields that appear on error page. I need to filter out HTTP_AUTHORIZATION
header, and cannot find a way to do it properly
There are already ways to filter cookies and form fields. I don't imagine it'd be difficult to add headers to that list? That'd be something better answered by the 'other' Nick :-P
The other thing I noticed, is that we seem to lose line numbers in the stack trace with Exceptional 2.0?
However, I just tried with Microsoft's developer page, and the stack trace there doesn't seem to have line numbers in the traditional sense either. They have a collapsable section with the line in question, as well as the few around it. But the call stack does not. (at least not the call stack they display in HTML)
It looks like a good chunk of the classes in the Microsoft.AspNetCore.Diagnostics
package they use are marked as internal, not that I think we need all of that. But there may be some tidbits in there to give some insights on how to get line numbers for the stack trace. Sadly this would mean a divergence from the traditional ASP.NET and how it's stack traces are done in Exceptional. Just early observations but worth mentioning.
@alfeg There may be some confusion here, the filters are at log time (so for example, we never store a password). Are you saying you want to filter headers as well on the logging phase? If so, that's a reasonable request, yeah sure.
@ctorx Sorry I missed this (I've been really spread thin across many projects). I think this is a great state on mobile. What I imagine is changing the base HTML to use <div>
+ display: table-row;
, etc. for the current table view and transform easily to mobile...but I'm open to options here.
I agree using a div layout would be a better direction, but if time is short, the styles I created work ASIS and I'd be happy to put fourth a pull request.
The styles are not complex, so making them work in a DIV scenario should also be trivial. Is this something you're looking for help on? If so, I can start on migrating the current table layout to divs instead.
On Wed, Sep 6, 2017 at 3:24 PM, Nick Craver notifications@github.com wrote:
@ctorx https://github.com/ctorx Sorry I missed this (I've been really spread thin across many projects). I think this is a great state on mobile. What I imagine is changing the base HTML to use
+ display: table-row;, etc. for the current table view and transform easily to mobile...but I'm open to options here.— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/NickCraver/StackExchange.Exceptional/issues/85#issuecomment-327628843, or mute the thread https://github.com/notifications/unsubscribe-auth/AAU-59T-tval31izHGAYKZE4Wckp5G3Mks5sfxuHgaJpZM4OZe7z .
@ctorx Yes on help! Mirroring current setup exactly with <div>
s + styles would be phase 1. There are big changes to the settings structure will affect a lot of pages (and everything, really) - I just opened #108 for tracking this. Once merged (next day or two I hope), that should eliminate most merge conflicts there.
@NickCraver Great, I'll keep an eye out for the merge and get started once it's complete.
On Wed, Sep 6, 2017 at 4:26 PM, Nick Craver notifications@github.com wrote:
@ctorx https://github.com/ctorx Yes on help! Mirroring current setup exactly with
s + styles would be phase 1. There are big changes to the settings structure will affect a lot of pages (and everything, really)
- I just opened #108 https://github.com/NickCraver/StackExchange.Exceptional/pull/108 for tracking this. Once merged (next day or two I hope), that should eliminate most merge conflicts there.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/NickCraver/StackExchange.Exceptional/issues/85#issuecomment-327639246, or mute the thread https://github.com/notifications/unsubscribe-auth/AAU-5zMfTqVVOkzijVMAzIL7jJpNGmjLks5sfyojgaJpZM4OZe7z .
@ctorx Merged into master
:)
This is a planning post for V2, which I've already started into in the v2 branch (needs VS 2017 15.3 Preview to work with). I've made pretty sweeping changes to settings locations to centralize them on the code side (old
web.config
sections still work!), and will continue to do so as I find a good solution for ASP.NET and ASP.NET Core both. I've also been working on the UI, code sharing etc. Let's break it down.Here's a list of what I'm planning for V2:
netstandard2.0
buildStackExchange.Exceptional.Shared
StackExchange.Exceptional.AspNetCore
.UseExceptional()
setup call).Shared
library thatStackExchange.Exceptional
andStackExchange.Exceptional.AspNetCore
depend onStackExchange.Exceptional.Shared
GetCustomData
and where it livesIPNet
(a utility class I created for Opserver).less
files with simpler, more consistent, and purpose-based styles).js
(via Web Essentials) toBundle.js
.Shared
for consistency and DRY between libs<T1,T2>
instead of`2
async
stack tracesUtils.StackTrace.PrettyHtml(string stackTrace)
method)System.Web
dependencyGet
/GetAll
IHttpModule
Log()
/LogAsync()
equivalents.AddLogData()
extension for easily adding custom data*List
suffixes from Delete/Protect methods. Just make them overloads that takeIEnumerable<Guid>
.Data["SQL"]
will be recognized by default) - for example Redis commands, Elastic queries and responses, etc.SQL
still acts as a deserializer set for viewing old exceptionsSQL
column for SQL store scriptsLastLoggedDate
column/property to fix rollup issues, e.g. roll up every 10 minutes (as before) but show the actual last date it occured in the UI, rather than the start of the rollupCategory
(string) field to the exception for a variety of use cases (categorization, severity, etc.)/docs
(GitHub pages, like miniprofiler.com/dotnet (source)That's just an overview of the stuff I already have in queue, for example here are what the new stack traces look like to a user (you can confiure it to looke like C#, VB, or F# syntax):
...and here's what the list page currently looks like:
...so that's what I've been working on. Now...
What functionality would you like to see in V2 for exceptional?
I'm not promising to implement all ideas, but I want to at least (hopefully) make all breaking changes necessary in a major release where they belong. I'm sure there are a lot of other great ideas (I know some co-workers have some that I'm forgetting right now) and I wanted to collect them all here so that none are forgotten. Please, share your ideas!
Also, as always, feedback on any of the above is welcome. I'm just trying to get a release out as personal time allows!