NickCraver / StackExchange.Exceptional

Error handler used for the Stack Exchange network
https://nickcraver.com/StackExchange.Exceptional/
Apache License 2.0
860 stars 170 forks source link

V2 Planning - Ideas and Feedback Needed! #85

Closed NickCraver closed 5 years ago

NickCraver commented 7 years ago

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:

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):

screen shot 2017-07-16 at 19 24 12

...and here's what the list page currently looks like:

screen shot 2017-07-16 at 21 26 15

...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!

captncraig commented 7 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:

  1. Good documentation on database schema, what columns are optional vs required, shema for the json blob if that stille exists, and so forth.
  2. Are the ui bits fairly standalone? Is there a documented api it uses? In an optimal case I could serve some html, replicate some json endpoints (with the same sql queries used on the .net side), and have a working ui in my go app for little effort. If the ui is more .net centric cshtml type stuff though, maybe that's not so easy.
  3. Is the stack trace rendering / source linking general enough that it could handle a go stack trace? If it were extensible enough, I'd certainly be willing to work on a go dialect parser that could handle it, assuming it could handle plugins in that kind of way.
CptRobby commented 7 years ago

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:

NickCraver commented 7 years ago

@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:

  1. Covered above - these will change and it's the change itself that makes this a problem, especially in Go until packaging gets a good story.
  2. They're very much embedded in the lib. A .NET platform can make use of them, but others cannot.
  3. Also above - we'd have to put in a different parser for each platform, which has it's own set of issues. We'd need to be able to identify the platform first, though.
NickCraver commented 7 years ago

@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?

CptRobby commented 7 years ago

@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:

NickCraver commented 7 years ago

@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 :)

CptRobby commented 7 years ago

@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.

CptRobby commented 7 years ago

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.

mmillican commented 7 years ago

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.

NickCraver commented 7 years ago

@CptRobby I concur, addressed in 346bbde2dc96cba03bec437e48abcf31c350a62f - thanks!

minhhungit commented 7 years ago

Can we implement a custom module or a simple module which we can add it into dashboard page with simple information

CptRobby commented 7 years ago

@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?

minhhungit commented 7 years ago

@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

NickCraver commented 6 years ago

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.

nickalbrecht commented 6 years ago

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.

nickalbrecht commented 6 years ago

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
}
NickCraver commented 6 years ago

@nickalbrecht Thanks! I fixed both of those just now, they'll appear on NuGet shortly. Please keep sending any issues you hit!

nickalbrecht commented 6 years ago

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.

ctorx commented 6 years ago

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 .

nickalbrecht commented 6 years ago

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.

nickalbrecht commented 6 years ago

@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

nickalbrecht commented 6 years ago

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 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. 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.

app.UseExceptional(Configuration.GetSection("Exceptional"));
app.UseExceptionalPageOnThrow();
nickalbrecht commented 6 years ago

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.

NickCraver commented 6 years ago

@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.

NickCraver commented 6 years ago

@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.

ctorx commented 6 years ago

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 .

NickCraver commented 6 years ago

@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.

ctorx commented 6 years ago

@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 .

NickCraver commented 6 years ago

@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.

ctorx commented 6 years ago

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).

list-page

nickalbrecht commented 6 years ago

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

nickalbrecht commented 6 years ago

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?

NickCraver commented 6 years ago

@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.

nickalbrecht commented 6 years ago

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

nickalbrecht commented 6 years ago

@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?

StefanKert commented 6 years ago

@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:

  1. Checkout master branch
  2. Start AspNetCore-Sample with VS 2017 (Update 3.1)
  3. Go to Exceptions Page
  4. Navigate back
  5. Go to Throws Page

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 ExceptionMiddlewareif 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 Settingsas 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 Settingsdirectly 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:

https://github.com/IdentityServer/IdentityServer4/blob/906315524e507876908302020a3a1eb307f0ba5a/src/Host/Startup.cs#L23-L42

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.
}
nickalbrecht commented 6 years ago

@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?

nickalbrecht commented 6 years ago

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.

nickalbrecht commented 6 years ago

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)

StefanKert commented 6 years ago

@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.

nickalbrecht commented 6 years ago

@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.

alfeg commented 6 years ago

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

nickalbrecht commented 6 years ago

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

nickalbrecht commented 6 years ago

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.

NickCraver commented 6 years ago

@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.

NickCraver commented 6 years ago

@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.

ctorx commented 6 years ago

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 .

NickCraver commented 6 years ago

@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.

ctorx commented 6 years ago

@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)

— 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 .

NickCraver commented 6 years ago

@ctorx Merged into master :)