Uploaded image for project: 'Solder'
  1. Solder
  2. SOLDER-154

Include the ability to filter stack traces

    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

      Gliffy Diagrams

        Activity

        Hide
        dan.j.allen Dan Allen added a comment - - edited

        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)
         */
        public interface ExceptionStackFrameFilter<T extends Throwable>
        {
           public StackFrameFilterResult process(StackFrame frame);
        }
         
        public interface StackFrame
        {
           StackElement getStackElement();
           StackTraceElement getStackTraceElement();
           void mark(String tag);
           StackFrame getMarkedFrame(String tag);
           boolean isMarkSet(String tag);
           void clearMark(String tag);
           void setStackTraceElement(StackTraceElement element);
           IterationStatus getIterationStatus();
        }
         
        public enum StackFrameFilterResult
        {
           /**
            * 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)
         */
        public interface ExceptionStackFilter
        {
           public StackFilterResult process(StackElement element);
        }
         
        public interface StackElement
        {
           Throwable getThrowable();
           void setThrowable(Throwable throwable);
           IterationStatus getIterationStatus();
        }
         
        public enum StackFilterResult
        {
           /**
            * 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
        }
        

        Show
        dan.j.allen Dan Allen added a comment - - edited 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) */ public interface ExceptionStackFrameFilter<T extends Throwable> { public StackFrameFilterResult process(StackFrame frame); }   public interface StackFrame { StackElement getStackElement(); StackTraceElement getStackTraceElement(); void mark(String tag); StackFrame getMarkedFrame(String tag); boolean isMarkSet(String tag); void clearMark(String tag); void setStackTraceElement(StackTraceElement element); IterationStatus getIterationStatus(); }   public enum StackFrameFilterResult { /** * 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) */ public interface ExceptionStackFilter { public StackFilterResult process(StackElement element); }   public interface StackElement { Throwable getThrowable(); void setThrowable(Throwable throwable); IterationStatus getIterationStatus(); }   public enum StackFilterResult { /** * 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 }
        Hide
        dan.j.allen Dan Allen added a comment - - edited

        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;
           }
        }
        

        Show
        dan.j.allen Dan Allen added a comment - - edited 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; } }
        Hide
        dan.j.allen Dan Allen added a comment -

        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.

        Show
        dan.j.allen Dan Allen added a comment - 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.
        Hide
        dan.j.allen Dan Allen added a comment -

        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.

        Show
        dan.j.allen Dan Allen added a comment - 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.
        Hide
        dan.j.allen Dan Allen added a comment -

        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

        { qualifiers, bean }

        (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

        Show
        dan.j.allen Dan Allen added a comment - 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 { qualifiers, bean } (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
        Hide
        lightguard Jason Porter added a comment - - edited

        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.

        Show
        lightguard Jason Porter added a comment - - edited 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.
        Hide
        lightguard Jason Porter added a comment -

        What goes into IterationStatus? I have public int getIndex() but it seems like there should be more than that.

        Show
        lightguard Jason Porter added a comment - What goes into IterationStatus? I have public int getIndex() but it seems like there should be more than that.
        Hide
        lightguard Jason Porter added a comment -

        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.

        Show
        lightguard Jason Porter added a comment - 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.
        Hide
        lightguard Jason Porter added a comment -

        Implemented

        Show
        lightguard Jason Porter added a comment - Implemented

          People

          • Assignee:
            lightguard Jason Porter
            Reporter:
            lightguard Jason Porter
          • Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development