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