2015-05-29 23 views
1

這是我迄今爲止嘗試在Jython代碼中捕獲所有異常的嘗試。我發現,最困難的事情是在從Java類中覆蓋方法時捕獲異常:使用下面的「守夜」修飾器(它也測試EDT/Event Despatch Thread狀態是否正確),您可以找到第一行代碼被拋出的地方...所以你可以確定方法本身。但不是線路。在Jython中全面捕捉異常

此外,通過Python和Java堆棧追溯堆棧幀完全超出了我。顯然,似乎有這些「代理」類的層和層,毫無疑問,Jython機制是不可避免的一部分。如果有人比我更聰明地對這個問題感興趣,那會很棒!

NB這是一個如何使用「守夜」裝飾爲例:

​​

......這些都是三個功能我試圖捕獲的東西用...

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當然可以通過try ... top-and-tail重寫每一個Java方法...除了...但是在哪裏有趣呢?嚴重的是,即使這樣做,我無法找到排除異常的線......

+0

使用'except:'(即不指定類型)和通過'sys.exc_info()'訪問異常可能會給出更好的結果。 – doublep

+0

嘿,好的提示,謝謝! –

回答

0

吉姆·貝克的回答很有趣......但我想要的是STHG全面當任何類型的異常引發其中記錄的堆棧跟蹤信息的最大可能量。 CPython不是多線程的,它的堆棧跟蹤不需要處理Runnables。對於Jython/Python專家來說,我是不夠的,不知道您是否可以始終以「純Python」代碼(即不使用Java類)獲取整個堆棧。

但我想獲得的一件事是導致運行Jython中的Runnable的活動。而導致Runnable運行Runnable等的活動,直接返回到第一個線程。我的解決方案如下,從Jim的回答以及doublep的評論中獲得靈感,創建了一個新的Jython類TraceableRunnable,它將在創建時存儲堆棧跟蹤列表。

當引發異常(Java或Python風格)時,會將所有事件記錄回運行開始(如果系統地使用TraceableRunnable而不是Runnable)。

每個run()的一個子類TraceableRunner的代碼也有在某些時候做這個調用:

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 

歡迎來自適當的程序員改進...!

1

下面是Jython中的套接字模塊中用於將Java異常映射到Python異常的裝飾器示例。我沒看過你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) 

有一個在代碼中的一些clunkiness,我敢肯定,改進的餘地,但總體來說它肯定使任何裝飾的代碼更容易跟蹤。例如:

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