Jython Exception Traps

This is my attempt so far to catch all the exceptions in the Jython code. The most difficult thing I have found is capture exceptions, when you redefine a method from the Java class: using the "vigil" decorator below (which also checks the EDT / Event Despatch Thread state), you can find out the first line where the code is thrown .. so that you can define the method itself. But not a line.

In addition, stack frame tracing through Python and Java stacks is completely outside of me. Obviously, these layers and proxy class layers seem to be an integral part of Jython's mechanics. It would be great if someone was much smarter than me interested in this issue!

NB is an example of how to use the vigil decorator:

class ColumnCellRenderer( javax.swing.table.DefaultTableCellRenderer ): @vigil( True ) # means a check is done that the thread is the EDT, as well as intercepting Python Exceptions and Java Throwables... def getTableCellRendererComponent( self, table, value, isSelected, hasFocus, row, column): super_comp = self.super__getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column) super_comp.foreground = java.awt.Color.black super_comp.font = main_frame_self.m_plain_font ... 

... and these are the three functions that I use in an attempt to capture material ...

 def custom_hook(type_of_e, e, tb ): """ Method to catch Python-style BaseExceptions, using the command sys.excepthook = custom_hook. The first thing this method needs to do in Jython is to determine whether this is a Java java.lang.Throwable or not. If it is this JThrowable must be handled by the code which caters for B{uncaught Java Throwables}. """ try: if 'tb' not in locals(): tb = None logger.error("Python custom_hook called...\ntype of e: %s\ne: %s\ntb: %s" % ( unicode( type_of_e ), unicode( e ), unicode( tb ) )) msg = ''.join( traceback.format_exception(type_of_e, e, tb )) logger.error( "traceback:\n" + msg ) except BaseException, e: logger.error( "exception in Python custom_hook!:\n%s" % e ) raise e sys.excepthook = custom_hook class JavaUncaughtExceptHandler( java.lang.Thread.UncaughtExceptionHandler ): """ java.lang.Class to catch any Java Throwables thrown by the app. """ def uncaughtException( self, thread, throwable ): try: ''' NB getting the Java stack trace like this seems to produce a very different trace from throwable.printStackTrace()... why? ''' # we want a single log message exception_msg = "\n*** uncaught Java Exception being logged in %s:\n" % __file__ baos = java.io.ByteArrayOutputStream() ps = java.io.PrintStream(baos) throwable.printStackTrace( ps ) # remove multiple lines from Java stack trace message java_stack_trace_lines = unicode( baos.toString( "ISO-8859-1" )).splitlines() java_stack_trace_lines = filter( None, java_stack_trace_lines ) normalised_java_stack_trace = '\n'.join( java_stack_trace_lines ) exception_msg += normalised_java_stack_trace + '\n' python_traceback_string = traceback.format_exc() exception_msg += "Python traceback:\n%s" % python_traceback_string logger.error( exception_msg ) except (BaseException, java.lang.Throwable ), e: logger.error( "*** exception in Java exception handler:\ntype %s\n%s" % ( type( e ), unicode( e ) ) ) raise e # NB printStackTrace causes the custom_hook to be invoked... (but doesn't print anything) java.lang.Thread.setDefaultUncaughtExceptionHandler( JavaUncaughtExceptHandler() ) def vigil( *args ): """ Decorator with two functions. 1. to check that a method is being run in the EDT or a non-EDT thread; 2. to catch any Java Throwables which otherwise would not be properly caught and documented: in particular, with normal Java error-trapping in Jython it seems impossible to determine the line number at which an Exception was thrown. This at least records the line at which a Java java.lang.Throwable was thrown. """ if len( args ) != 1: raise Exception( "vigil: wrong number of args (should be 1, value: None/True/False): %s" % str( args )) req_edt = args[ 0 ] if req_edt and type( req_edt ) is not bool: raise Exception( "vigil: edt_status is wrong type: %s, type %s" % ( req_edt, type( req_edt )) ) def real_decorator( function ): if not hasattr( function, '__call__' ): raise Exception( "vigil: function %s does not have __call__ attr, type %s" % ( function, type( function )) ) # NB info about decorator location can't be got when wrapper called, so record it at this point penultimate_frame = traceback.extract_stack()[ -2 ] decorator_file = penultimate_frame[ 0 ] decorator_line_no = penultimate_frame[ 1 ] def wrapper( *args, **kvargs ): try: # TODO is it possible to get the Python and/or Java stack trace at this point? if req_edt and javax.swing.SwingUtilities.isEventDispatchThread() != req_edt: logger.error( "*** vigil: wrong EDT value, should be %s\nfile %s, line no %s, function: %s\n" % ( "EDT" if req_edt else "non-EDT", decorator_file, decorator_line_no, function )) return function( *args, **kvargs ) except ( BaseException, java.lang.Throwable ), e: ''' NB All sorts of problems if a vigil-protected function throws an exception: 1) just raising e means you get a very short stack trace... 2) if you list the stack trace elements here you get a line (seemingly inside the function where the exception occurred) but often the wrong line! 3) Python/Java stack frames: how the hell does it all work??? 4) want a single error message to be logged ''' msg = "*** exception %s caught by vigil in file %s\nin function starting line %d" % ( e, decorator_file, decorator_line_no ) logger.error( msg ) frame = inspect.currentframe() # the following doesn't seem to work... why not? python_stack_trace = traceback.format_stack(frame) python_stack_string = "Python stack trace:\n" for el in python_stack_trace[ : -1 ]: python_stack_string += el logger.error( python_stack_string ) if isinstance( e, java.lang.Throwable ): # NB problems with this stack trace: although it appears to show the # correct Java calling pathway, it seems that the line number of every file and method # is always shown as being the last line, wherever the exception was actually raised. # Possibly try and get hold of the actual Pyxxx objects ... (?) java_stack_trace = e.stackTrace java_stack_string = "Java stack trace:\n" for el in java_stack_trace: java_stack_string += " %s\n" % unicode( el ) logger.error( java_stack_string ) raise e return wrapper return real_decorator 

PS, of course, it is possible for every overridden Java method using try ... except ... but where is the fun in this? Seriously, even doing this, I can't find the line where the exception is thrown ...

+1
source share
2 answers

Jim Baker's answer is interesting ... but I wanted it to be comprehensive, which documents as much information as possible about the stack trace when an exception occurs. CPython is not multi-threaded, its stack trace should not cope with Runnables. I am missing a Jython / Python expert to find out if you can always get the whole stack in "pure Python" code (i.e. Don't use Java classes).

But one of the things I wanted to get was the activity that led to the launch of Runnable in Jython. And the activity that led to Runnable by running Runnable, etc., returned to the very first thread. My solution below, inspired by Jim's answer, but also from the doublep comment, creates a new Jython TraceableRunnable class that will save a list of stack traces when created.

When an exception occurs, whether in the Java or Python style, it writes everything back to the start of the run (if you systematically use TraceableRunnable instead of Runnable).

Each run () code of the TraceableRunner subclass must also make this call at some point:

 self.record_thread() 

... I hope this is not too tiring to impose ...

(NB in ​​a truly “adult” implementation that you would like to verify that this call was made ... I am sure that this can be done using some sophisticated Pythonic technique, otherwise using unit testing or something. You can also require a call at the very end of the run () code to delete this entry in the dictionary ...)

This is the trap and registration code:

 class TraceableRunnable( java.lang.Runnable ): thread_to_tr_dic = {} def __init__( self ): # no need to call super __init__: Runnable is a Java *interface* caller_thread = java.lang.Thread.currentThread() self.frame_stack_list = [] if hasattr( caller_thread, 'frame_stack_list' ): self.frame_stack_list = copy.deepcopy( caller_thread.frame_stack_list ) self.frame_stack_list.append( traceback.extract_stack() ) def record_thread( self ): TraceableRunnable.thread_to_tr_dic[ java.lang.Thread.currentThread() ] = self class EDTException( Exception ): pass def vigil( *args ): """ Decorator with two functions. 1. to check that a method is being run in the EDT or a non-EDT thread 2. to catch any exceptions """ if len( args ) != 1: raise Exception( "vigil: wrong number of args (should be 1, value: None/True/False): %s" % str( args )) req_edt = args[ 0 ] if req_edt and type( req_edt ) is not bool: raise Exception( "vigil: edt_status is wrong type: %s, type %s" % ( req_edt, type( req_edt )) ) def process_exception( exc, python = True ): tb_obj = sys.exc_info()[ 2 ] msg = "Exception thrown message %s\nfamily %s, type: %s\n" % ( str( exc ), "Python" if python else "Java", type( exc )) msg += "traceback object part:\n" ex_tb = traceback.extract_tb( tb_obj ) # first is frame in vigil ex_tb = ex_tb[ 1 : ] if not ex_tb: msg += " none\n" else: tb_strings = traceback.format_list( ex_tb ) for tb_string in tb_strings: msg += tb_string curr_thread = java.lang.Thread.currentThread() if curr_thread in TraceableRunnable.thread_to_tr_dic: runnable = TraceableRunnable.thread_to_tr_dic[ curr_thread ] # duck-typing, obviously... although redundant test, as only TraceableRunnables should be in the dictionary... if hasattr( runnable, 'frame_stack_list' ): msg += "\nOLDER STACKS:\n" for frame_stack in runnable.frame_stack_list: msg += "\nframe stack id: %d\n" % id( frame_stack ) frame_stack = frame_stack[ : -1 ] if not frame_stack: msg += " no frames\n" else: # most recent call first: reverse array... stack_strings = traceback.format_list( reversed( frame_stack )) for stack_string in stack_strings: msg += stack_string logger.error( msg ) def real_decorator( function ): if not hasattr( function, '__call__' ): raise Exception( "vigil: function %s does not have __call__ attr, type %s" % ( function, type( function )) ) # NB info about decorator location can't be got when wrapper called, so record it at this point penultimate_frame = traceback.extract_stack()[ -2 ] decorator_file = penultimate_frame[ 0 ] decorator_line_no = penultimate_frame[ 1 ] def wrapper( *args, **kvargs ): try: if req_edt is not None and javax.swing.SwingUtilities.isEventDispatchThread() != req_edt: msg = \ "vigil: wrong EDT value, should be %s\nfile %s\nline no %s, function: %s" % \ ( "EDT" if req_edt else "non-EDT", decorator_file, decorator_line_no, function ) raise EDTException( msg ) return function( *args, **kvargs ) except BaseException, e: # we cannot know how calling code will want to deal with an EDTException if type( e ) is EDTException: raise e process_exception( e ) except java.lang.Throwable, t: process_exception( t, False ) return wrapper return real_decorator 

I welcome improvements from the right programmers ...!

0
source

Here is an example of a decorator used in a socket module in Jython to display Java exceptions in Python exceptions. I have not read your vigil decorator too closely since it worked a lot, but I put it here if it can help:

 def raises_java_exception(method_or_function): """Maps java socket exceptions to the equivalent python exception. Also sets _last_error on socket objects so as to support SO_ERROR. """ @wraps(method_or_function) def handle_exception(*args, **kwargs): is_socket = len(args) > 0 and isinstance(args[0], _realsocket) try: try: return method_or_function(*args, **kwargs) except java.lang.Exception, jlx: raise _map_exception(jlx) except error, e: if is_socket: args[0]._last_error = e[0] raise else: if is_socket: args[0]._last_error = 0 return handle_exception 

Basically what we see here, we are talking about whether this is a Java exception ( java.lang.Exception ) or not. I suppose this could be generalized to java.lang.Throwable , although it is not clear what can be done with java.lang.Error in any case. Of course, nothing matches socket errors!

The aforementioned decorator, in turn, uses the _map_exception function to deploy a Java exception. This is a pretty specific application, as you can see:

 def _map_exception(java_exception): if isinstance(java_exception, NettyChannelException): java_exception = java_exception.cause # unwrap if isinstance(java_exception, SSLException) or isinstance(java_exception, CertificateException): cause = java_exception.cause if cause: msg = "%s (%s)" % (java_exception.message, cause) else: msg = java_exception.message py_exception = SSLError(SSL_ERROR_SSL, msg) else: mapped_exception = _exception_map.get(java_exception.__class__) if mapped_exception: py_exception = mapped_exception(java_exception) else: py_exception = error(-1, 'Unmapped exception: %s' % java_exception) py_exception.java_exception = java_exception return _add_exception_attrs(py_exception) 

There is some stupidity in the code, and I am sure that there is room for improvement, but overall it certainly makes any decorated code much easier to follow. Example:

 @raises_java_exception def gethostname(): return str(InetAddress.getLocalHost().getHostName()) 
+1
source

All Articles