1""" 2Low level debugging helper for PyObjC. 3 4Allows you to log Python and ObjC (via atos) stack traces for NSExceptions 5raised. 6 7General guidelines for use: 8 9- It's typically only useful when you log EVERY exception, because Foundation 10 and AppKit will swallow most of them. This means that you should never 11 use this module in a release build. 12 13- Typical use involves only calling installDebuggingHandler or 14 installVerboseDebuggingHandler. It may be removed at any time by calling 15 removeDebuggingHandler. 16""" 17 18from Foundation import NSObject, NSLog 19import objc 20import os 21import sys 22 23import traceback 24from ExceptionHandling import NSExceptionHandler, NSLogUncaughtExceptionMask, NSLogAndHandleEveryExceptionMask, NSStackTraceKey 25 26DEFAULTMASK = NSLogUncaughtExceptionMask 27EVERYTHINGMASK = NSLogAndHandleEveryExceptionMask 28 29 30__all__ = [ 31 'LOGSTACKTRACE', 'DEFAULTVERBOSITY', 'DEFAULTMASK', 'EVERYTHINGMASK', 32 'installDebuggingHandler', 'installVerboseDebuggingHandler', 33 'installPythonExceptionHandler', 'removeDebuggingHandler', 34 'handlerInstalled', 35] 36 37def isPythonException(exception): 38 return (exception.userInfo() or {}).get(u'__pyobjc_exc_type__') is not None 39 40def nsLogPythonException(exception): 41 userInfo = exception.userInfo() 42 NSLog(u'*** Python exception discarded!\n' + 43 ''.join(traceback.format_exception( 44 userInfo[u'__pyobjc_exc_type__'], 45 userInfo[u'__pyobjc_exc_value__'], 46 userInfo[u'__pyobjc_exc_traceback__'], 47 )).decode('utf8')) 48 # we logged it, so don't log it for us 49 return False 50 51def nsLogObjCException(exception): 52 userInfo = exception.userInfo() 53 stack = userInfo.get(NSStackTraceKey) 54 if not stack or not os.path.exists('/usr/bin/atos'): 55 return True 56 pipe = os.popen('/usr/bin/atos -p %d %s' % (os.getpid(), stack)) 57 stacktrace = pipe.readlines() 58 stacktrace.reverse() 59 NSLog(u"%@", u"*** ObjC exception '%s' (reason: '%s') discarded\n" % ( 60 exception.name(), exception.reason(), 61 ) + 62 u'Stack trace (most recent call last):\n' + 63 ''.join([(' '+line) for line in stacktrace]).decode('utf8') 64 ) 65 return False 66 67LOGSTACKTRACE = 1 << 0 68DEFAULTVERBOSITY = 0 69 70class PyObjCDebuggingDelegate(NSObject): 71 verbosity = objc.ivar('verbosity', 'i') 72 73 def initWithVerbosity_(self, verbosity): 74 self = self.init() 75 self.verbosity = verbosity 76 return self 77 78 def exceptionHandler_shouldLogException_mask_(self, sender, exception, aMask): 79 try: 80 if isPythonException(exception): 81 if self.verbosity & LOGSTACKTRACE: 82 nsLogObjCException(exception) 83 return nsLogPythonException(exception) 84 elif self.verbosity & LOGSTACKTRACE: 85 return nsLogObjCException(exception) 86 else: 87 return False 88 except: 89 print >>sys.stderr, "*** Exception occurred during exception handler ***" 90 traceback.print_exc(sys.stderr) 91 return True 92 exceptionHandler_shouldLogException_mask_ = objc.selector(exceptionHandler_shouldLogException_mask_, signature='c@:@@I') 93 94 def exceptionHandler_shouldHandleException_mask_(self, sender, exception, aMask): 95 return False 96 exceptionHandler_shouldHandleException_mask_ = objc.selector(exceptionHandler_shouldHandleException_mask_, signature='c@:@@I') 97 98def installExceptionHandler(verbosity=DEFAULTVERBOSITY, mask=DEFAULTMASK): 99 """ 100 Install the exception handling delegate that will log every exception 101 matching the given mask with the given verbosity. 102 """ 103 # we need to retain this, cause the handler doesn't 104 global _exceptionHandlerDelegate 105 delegate = PyObjCDebuggingDelegate.alloc().initWithVerbosity_(verbosity) 106 NSExceptionHandler.defaultExceptionHandler().setExceptionHandlingMask_(mask) 107 NSExceptionHandler.defaultExceptionHandler().setDelegate_(delegate) 108 _exceptionHandlerDelegate = delegate 109 110def installPythonExceptionHandler(): 111 """ 112 Install a verbose exception handling delegate that logs every exception 113 raised. 114 115 Will log only Python stack traces, if available. 116 """ 117 installExceptionHandler(verbosity=DEFAULTVERBOSITY, mask=EVERYTHINGMASK) 118 119def installVerboseExceptionHandler(): 120 """ 121 Install a verbose exception handling delegate that logs every exception 122 raised. 123 124 Will log both Python and ObjC stack traces, if available. 125 """ 126 installExceptionHandler(verbosity=LOGSTACKTRACE, mask=EVERYTHINGMASK) 127 128def removeExceptionHandler(): 129 """ 130 Remove the current exception handler delegate 131 """ 132 NSExceptionHandler.defaultExceptionHandler().setDelegate_(None) 133 NSExceptionHandler.defaultExceptionHandler().setExceptionHandlingMask_(0) 134 135def handlerInstalled(): 136 """ 137 Is an exception handler delegate currently installed? 138 """ 139 return NSExceptionHandler.defaultExceptionHandler().delegate() is not None 140