Details
-
Type:
Feature Request
-
Status: Resolved (View Workflow)
-
Priority:
Major
-
Resolution: Done
-
Affects Version/s: None
-
Fix Version/s: 3.0.0.CR1
-
Component/s: None
-
Labels:None
-
Affects:Documentation (Ref Guide, User Guide, etc.), Interactive Demo/Tutorial, Compatibility/Configuration
-
Estimated Difficulty:Medium
Description
Catch should draw from ideas such as:
Gliffy Diagrams
Activity
- All
- Comments
- Work Log
- History
- Activity
- Links Hierarchy
Here are some examples.
1. Drop all frames in NoDefClassFoundError
public class NoClassFoundStackFrameFilter implements ExceptionStackFrameFilter<NoClassDefFoundError> |
{
|
@Override |
public StackFrameFilterResult process(StackFrame frame) |
{
|
return StackFrameFilterResult.BREAK; |
}
|
}
|
2. Truncate frames after a threshold (though no indication we did that we did)
public class TruncateExceptionStackFilter implements ExceptionStackFrameFilter<Throwable> |
{
|
@Override |
public StackFrameFilterResult process(StackFrame frame) |
{
|
if (frame.getIterationStatus().getIndex() >= 20) |
{
|
return StackFrameFilterResult.DROP_REMAINING; |
}
|
|
return StackFrameFilterResult.INCLUDE; |
}
|
}
|
3. Remove noise in between fire event and invoke observer
public class InvokeObserverStackFrameFilter implements ExceptionStackFrameFilter<Throwable> |
{
|
@Override |
public StackFrameFilterResult process(StackFrame frame) |
{
|
StackTraceElement el = frame.getStackTraceElement();
|
if (frame.isMarkSet("event.firing")) |
{
|
if (isReflectionInvoke(el)) |
{
|
frame.clearMark("event.firing"); |
return StackFrameFilterResult.DROP; |
}
|
else |
{
|
return StackFrameFilterResult.DROP; |
}
|
}
|
else if (el.getClassName().contains(".BeanManager") && el.getMethodName().equals("fireEvent")) |
{
|
frame.mark("event.firing"); |
return StackFrameFilterResult.INCLUDE; |
}
|
|
return StackFrameFilterResult.INCLUDE; |
}
|
|
private boolean isReflectionInvoke(StackTraceElement candidate) |
{
|
return candidate.getClassName().equals(Method.class.getName()) |
&& candidate.getMethodName().equals("invoke"); |
}
|
}
|
4. Flatten reflections invoke into a single line
public class ReflectionInvokeStackFrameFilter implements ExceptionStackFrameFilter<Throwable> |
{
|
@Override |
public StackFrameFilterResult process(StackFrame frame) |
{
|
if (frame.isMarkSet("reflections.invoke")) |
{
|
if (frame.getStackTraceElement().getMethodName().startsWith("invoke") || |
frame.getStackTraceElement().getClassName().contains("_WeldClientProxy")) |
{
|
return StackFrameFilterResult.DROP; |
}
|
else |
{
|
frame.clearMark("reflections.invoke"); |
}
|
}
|
return StackFrameFilterResult.INCLUDE; |
}
|
}
|
Of course, the range filtering is complicated. It's much simpler to just drop all classes in a package.
Also, we may want to support the case of dropping all previous. So you see a certain class, you know that nothing before it is interesting. For instance, you see javax.faces.webapp.FacesServlet#service() and thus everything before it must be request setup and perhaps filters.
I was thinking this could be quite useful for purging a stack trace for exceptions that simply don't need one. For instance, if the BeanManager can't be found, there is nothing in the stack trace that is really helping you there...it's a misconfiguration problem. Same would go for an XML file that can't be parsed. The message of the exception is really all you should need. The rest is just internal details. Obviously, this would be up to the discretion of the application writer.
Some discussion that led to the proposed API:
(11:42:06) Dan: I realized that if we had something like a PrepareCaughtException event
(11:42:20) Dan: then in that event, it would be possible to apply filtering or mapping
(11:42:25) Dan: and then do
(11:42:33) Dan: PrepareCaughtException#setException()
(11:42:43) Dan: just like ProcessAnnotatedType#setAnnotatedType()
(11:43:12) Dan: so that's one place to do global filtering, and is interesting because then you actually pare down the exception that even needs to be handled
(11:43:20) Dan: potentially, as an option
(11:43:30) Jason: Cool idea
(11:43:40) Dan: or you can replace causes w/ an app equivalent
(11:43:55) Dan: for instance persistence can provide some mappings between a SQLExeption and something like InvalidTableException
(11:44:00) Dan: and so forth
(11:44:23) Jason: That would basically handle your mapping
(11:44:28) Dan: then, you could always use the same api for filtering the exception at any time, since it's reusable
(11:44:30) Dan: yep
(11:44:32) Dan: exactly
(11:44:53) Dan: I was thinking whether we needed a post-process
(11:44:57) Jason: What would that event need on it?
(11:44:59) Dan: maybe let's wait to see if there is some need
(11:45:14) Dan: I'm thinking it just has the Exception
(11:45:17) Jason: We fire it once?
(11:45:23) Dan: and the unwrapped stack
(11:45:26) Jason: At the start of catch process?
(11:45:33) Dan: yes
(11:45:35) Dan: yes
(11:46:01) Dan: I thought about ProcessCaughtException, but I don't like how that implies handling
(11:46:07) Dan: I like Prepare
(11:46:10) Dan: or PreProcess
(11:46:21) Dan: this is really interesting
(11:46:30) Dan: it totally changes the game
(11:46:36) Dan: because we are saying, not only do you have these handlers
(11:46:51) Dan: and do they get to deal with the exception causes individually (like a handler for SQLException)
(11:47:02) Dan: but you can tune the exception getting fed into the system
(11:47:06) Dan: I'd even say
(11:47:09) Dan: you could add qualifiers
(11:47:32) Dan: btw, the qualifiers should be available to the CaughtExeption, I was thinking about that
(11:47:34) Dan: if they aren't already
(11:47:49) Dan: something imo missing from the eventing system in cdi
(11:47:57) Jason: hm?
(11:48:01) Jason: which qualifiers?
(11:48:19) Dan: consider this...your observer is called
(11:48:26) Dan: in our case handlers, but I'm going to talk in general
(11:48:31) Dan: you don't really know why
(11:48:47) Dan: you know because it at least had a subset of qualifiers, or matched any
(11:49:00) Dan: but you don't know what qualifiers were actually on the original event
(11:49:12) Dan: we can provide that info in CaughtException
(11:49:30) Jason: Ah
(11:49:42) Dan: just some info...why not share it
(11:49:53) Dan: I think this is solved in CDI
(11:50:00) Dan: by allowing you to inject the Event object
(11:50:11) Dan: when you observe...so like this
(11:50:40) Dan: @Observes(@Updated Document document, Event<Documentg firingEvent)
(11:50:44) Dan: interesting for two reasons
(11:51:00) Dan: one, you have access to information about the firing
(11:51:08) Dan: you can turn around and fire the event again
(11:51:47) Jason: true
(11:51:49) Dan: rats, Event doesn't have getQualifiers() on it
(11:51:51) Jason: But then you'll be called again
(11:51:52) Dan: it probably should
(11:51:56) Dan: well, likely
(11:52:07) Dan: you would use it as a parent, add a qualifier and go again, something like that
(11:52:14) Dan: kind of as a pass on
(11:52:25) Dan: I don't know...you could also just have this
(11:52:28) Dan: EventSource
(11:52:37) Dan: EventSource
(11:53:02) Dan: anyway, for us, we just expose it from CaughtException
(11:53:28) Dan: but that's a side note
(11:53:36) Dan: back to the pre-processor
(11:53:49) Dan: ah, I had an idea about the statelessness of the filter
(11:54:02) Dan: the key here is not providing the index or anything
(11:54:15) Dan: but providing a state object
(11:54:29) Dan: or, a status object, not sure the correct term
(11:54:39) Dan: a visitlog
(11:54:49) Dan: and it has like last frame
(11:54:54) Dan: you can set a mark
(11:54:59) Dan: maybe a fold marker
(11:55:05) Dan: things like that
(11:55:08) Dan: then you can say like
(11:55:37) Dan: visitlog.isMarkSet()
(11:55:46) Dan: or getMarkedFrame()
(11:56:00) Dan: that way, the range information is provided to the method
(11:56:24) Jason: Hm
(11:56:35) Jason: We'd need a reset or something
(11:56:41) Dan: so let me give you the api I'm thinking
(11:56:45) Jason: And they could also give us the replace info
(11:56:49) Dan: yeah ![]()
(11:56:51) Dan: exactly
(11:56:53) Dan: like this
(11:56:57) Dan: visitlog.replaceFrame()
(11:57:07) Dan: and then you still return a result
(11:57:27) Dan: like SKIP, CONTINUE, TERMINATE
This is really different than what is in the chat log. At first it seems like we were talking about the ExceptionStack event (which is in HEAD currently), then we moved into the actual issue, which I agree should be part of Catch, but it's like a side API that can be used outside of any handlers. We also need something that will print the stack trace, either it'll print a string, or take an PrintStream and print to it.
What goes into IterationStatus? I have public int getIndex() but it seems like there should be more than that.
I may be able to use the ExceptionStack object, but it has some odd things going on because it's mutable. I still may be able to use it, but I need another object that will contain the corrected stack trace and indexes where the exception is wrapped and re-thrown.
I should be able to find those indexes with either their size minus two (which seems to be right, but more testing is needed) or I can iterate through both traces (the current exception and the next wrapped) and find out where they're equal, once I have that I'll know the stack index where they diverge slightly for the wrap.
Implemented
I played around with some ideas and we may even be able to use this for the exception mapping as well. Here are some prototype APIs.
/*** A filter for processing the stack frames of a single exception (type parameter allows us to focus on a specific exception type)*/{}{StackElement getStackElement();StackTraceElement getStackTraceElement();StackFrame getMarkedFrame(String tag);IterationStatus getIterationStatus();}{* Include this frame*/INCLUDE,* Drop this frame*/DROP,* Include this frame, but skip any remaining frames in this cause*/DROP_REMAINING,* Drop this frame and any remaining frames in this cause*/BREAK,* Stops processing any remaining frames or causes*/TERMINATE,* Include this frame, then stop processing any remaining frames or causes*/TERMINATE_AFTER}/*** A filter for processing the chained exceptions (i.e., the causes)*/{}{Throwable getThrowable();IterationStatus getIterationStatus();}{* Include this throwable*/INCLUDE,* Drop this throwable, go to next in chain (if any)*/DROP,* Drop any remaining throwables in the chain*/DROP_REMAINING,* Drop throwable and any remaining in the chain*/TERMINATE}