1

これは、Jython コードですべての例外をトラップするためのこれまでの試みです。最も難しいのは、Java クラスのメソッドをオーバーライドするときに例外をトラップすることです。以下の「vigil」デコレーター (EDT/イベント ディスパッチ スレッドのステータスが正しいかどうかもテストします) を使用すると、最初の行を見つけることができます。コードがスローされる場所...メソッド自体を識別できます。しかし、ラインではありません。

さらに、スタック フレームを Python および Java スタックからトレース バックすることは、私にはまったく不可能です。明らかに、これらの層と「プロキシ」クラスの層が存在するように見えます。これは、Jython の仕組みの必然的な部分であることは間違いありません。私よりずっと頭のいい人がこの質問に興味を持ってくれたらいいのに!

注意: これは "vigil" デコレータの使用例です。

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

...そして、これらは私が何かをトラップするために使用する3つの機能です...

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 もちろん、オーバーライドされたすべての Java メソッドを try ... except... でトップアンドテールにすることは可能ですが、それのどこが楽しいのでしょうか? 真剣に、それでも例外がスローされた行を見つけることができません...

4

2 に答える 2

1

Java 例外を Python 例外にマップするために Jython の socket モジュールで使用されるデコレータの例を次に示します。vigil多くの作業を行っているため、デコレータをあまり詳しく読んでいませんでしたが、役立つ場合に備えてここに配置しています。

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

ここで見られるほとんどのことは、それが Java 例外 ( java.lang.Exception) であるかどうかに基づいてディスパッチしていることです。java.lang.Throwableいずれにせよ の場合に何ができるかは不明ですが、これは に一般化できると思いますjava.lang.Error。確かに、ソケットエラーに対応するものは何もありません!

上記のデコレーターは、_map_exception関数を使用して Java 例外をラップ解除します。ご覧のとおり、ここでは非常にアプリケーション固有です。

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)

コードにはいくつかの不格好さがあり、改善の余地があると確信していますが、全体的には、装飾されたコードをはるかに簡単に追跡できるようになります. 例:

@raises_java_exception
def gethostname():
    return str(InetAddress.getLocalHost().getHostName())
于 2015-06-04T03:50:14.180 に答える
0

ジム・ベイカーの答えは興味深いです...しかし、私が欲しかったのは、あらゆる種類の例外が発生したときに可能な最大量のスタックトレース情報を文書化する総合的な sthg です。CPython はマルチスレッドではないため、そのスタック トレースは Runnables に対応する必要がありません。私は Jython/Python の専門家ではありませんが、「純粋な Python」コード (つまり、Java クラスを使用しない) で常にスタック全体を取得できるかどうかを知ることはできません。

しかし、私が取得したかったことの 1 つは、Jython での Runnable の実行につながったアクティビティでした。そして、Runnable がその Runnable を実行するなどにつながったアクティビティは、最初のスレッドに戻ります。以下の私のソリューションは、Jim の回答だけでなく doublep のコメントからもインスピレーションを得て、作成時にスタック トレースのリストを格納する新しい Jython クラス TraceableRunnable を作成します。

Java または Python スタイルのいずれかの例外が発生すると、実行の開始時点まですべてがログに記録されます (Runnable の代わりに TraceableRunnable を体系的に使用している場合)。

TraceableRunner サブクラスの各 run() コードも、ある時点でこの呼び出しを行う必要があります。

self.record_thread()

...うまくいけば、これはあまりにも面倒な押し付けではありません...

(NB、真に「成長した」実装では、この呼び出しが行われたことを確認する必要があります... これは、適切に洗練された Pythonic 手法によって実行できると確信しています。これは、単体テストまたは何かによって失敗します。また、この辞書エントリを削除するには、run() コードの最後で呼び出しを要求する必要があるかもしれません...)

これはキャッチとロギングのコードです:

class TraceableRunnable( java.lang.Runnable ):
    thread_to_tr_dic = {}
    def __init__( self  ):
        # no need to call super's __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

適切なプログラマーからの改善を歓迎します...!

于 2015-06-10T06:01:00.390 に答える