--- /dev/null
+# Written by Brendan O'Connor, brenocon@gmail.com, www.anyall.org\r
+# * Originally written Aug. 2005\r
+# * Posted to gist.github.com/16173 on Oct. 2008\r
+\r
+# Copyright (c) 2003-2006 Open Source Applications Foundation\r
+#\r
+# Licensed under the Apache License, Version 2.0 (the "License");\r
+# you may not use this file except in compliance with the License.\r
+# You may obtain a copy of the License at\r
+#\r
+# http://www.apache.org/licenses/LICENSE-2.0\r
+#\r
+# Unless required by applicable law or agreed to in writing, software\r
+# distributed under the License is distributed on an "AS IS" BASIS,\r
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+# See the License for the specific language governing permissions and\r
+# limitations under the License.\r
+\r
+import re, sys, types\r
+\r
+"""\r
+Have all your function & method calls automatically logged, in indented outline\r
+form - unlike the stack snapshots in an interactive debugger, it tracks call\r
+structure & stack depths across time!\r
+\r
+It hooks into all function calls that you specify, and logs each time they're\r
+called. I find it especially useful when I don't know what's getting called\r
+when, or need to continuously test for state changes. (by hacking this file)\r
+\r
+Originally inspired from the python cookbook: \r
+http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/198078\r
+\r
+Currently you can\r
+ - tag functions or individual methods to be autologged\r
+ - tag an entire class's methods to be autologged\r
+ - tag an entire module's classes and functions to be autologged\r
+\r
+TODO:\r
+ - allow tagging of ALL modules in the program on startup?\r
+\r
+CAVEATS:\r
+ - certain classes barf when you logclass() them -- most notably,\r
+ SWIG-generated wrappers, and perhaps others.\r
+\r
+USAGE: see examples on the bottom of this file.\r
+\r
+\r
+Viewing tips\r
+============\r
+\r
+If your terminal can't keep up, try xterm or putty, they seem to be highest\r
+performance. xterm is available for all platforms through X11...\r
+\r
+Also try: (RunChandler > log &); tail -f log\r
+\r
+Also, you can "less -R log" afterward and get the colors correct.\r
+\r
+If you have long lines, less -RS kills wrapping, enhancing readability. Also\r
+can chop at formatAllArgs().\r
+\r
+If you want long lines to be chopped realtime, try piping through less::\r
+\r
+ RunChandler | less -RS\r
+\r
+but then you have to hit 'space' lots to prevent chandler from freezing.\r
+less's 'F' command is supposed to do this correctly but doesn't work for me.\r
+"""\r
+\r
+\r
+#@@@ should use the standard python logging system?\r
+log = sys.stdout\r
+\r
+# Globally incremented across function calls, so tracks stack depth\r
+indent = 0\r
+indStr = ' '\r
+\r
+\r
+# ANSI escape codes for terminals.\r
+# X11 xterm: always works, all platforms\r
+# cygwin dosbox: run through |cat and then colors work\r
+# linux: works on console & gnome-terminal\r
+# mac: untested\r
+\r
+\r
+BLACK = "\033[0;30m"\r
+BLUE = "\033[0;34m"\r
+GREEN = "\033[0;32m"\r
+CYAN = "\033[0;36m"\r
+RED = "\033[0;31m"\r
+PURPLE = "\033[0;35m"\r
+BROWN = "\033[0;33m"\r
+GRAY = "\033[0;37m"\r
+BOLDGRAY = "\033[1;30m"\r
+BOLDBLUE = "\033[1;34m"\r
+BOLDGREEN = "\033[1;32m"\r
+BOLDCYAN = "\033[1;36m"\r
+BOLDRED = "\033[1;31m"\r
+BOLDPURPLE = "\033[1;35m"\r
+BOLDYELLOW = "\033[1;33m"\r
+WHITE = "\033[1;37m"\r
+\r
+NORMAL = "\033[0m"\r
+\r
+\r
+def indentlog(message):\r
+ global log, indStr, indent\r
+ print >>log, "%s%s" %(indStr*indent, message)\r
+ log.flush()\r
+\r
+def shortstr(obj):\r
+ """\r
+ Where to put gritty heuristics to make an object appear in most useful\r
+ form. defaults to __str__.\r
+ """\r
+ if "wx." in str(obj.__class__) or obj.__class__.__name__.startswith("wx"):\r
+ shortclassname = obj.__class__.__name__\r
+ ##shortclassname = str(obj.__class__).split('.')[-1]\r
+ if hasattr(obj, "blockItem") and hasattr(obj.blockItem, "blockName"):\r
+ moreInfo = "block:'%s'" %obj.blockItem.blockName\r
+ else:\r
+ moreInfo = "at %d" %id(obj)\r
+ return "<%s %s>" % (shortclassname, moreInfo)\r
+ else:\r
+ return str(obj)\r
+\r
+def formatAllArgs(args, kwds):\r
+ """\r
+ makes a nice string representation of all the arguments\r
+ """\r
+ allargs = []\r
+ for item in args:\r
+ allargs.append('%s' % shortstr(item))\r
+ for key,item in kwds.items():\r
+ allargs.append('%s=%s' % (key,shortstr(item)))\r
+ formattedArgs = ', '.join(allargs)\r
+ if len(formattedArgs) > 150:\r
+ return formattedArgs[:146] + " ..."\r
+ return formattedArgs\r
+\r
+\r
+def logmodules(listOfModules):\r
+ for m in listOfModules: \r
+ bindmodule(m)\r
+\r
+def logmodule(module, logMatch=".*", logNotMatch="nomatchasfdasdf"):\r
+ """ \r
+ WARNING: this seems to break if you import SWIG wrapper classes\r
+ directly into the module namespace ... logclass() creates weirdness when\r
+ used on them, for some reason.\r
+ \r
+ @param module: could be either an actual module object, or the string\r
+ you can import (which seems to be the same thing as its\r
+ __name__). So you can say logmodule(__name__) at the end\r
+ of a module definition, to log all of it.\r
+ """\r
+ \r
+ allow = lambda s: re.match(logMatch, s) and not re.match(logNotMatch, s)\r
+ \r
+ if isinstance(module, str):\r
+ d = {}\r
+ exec "import %s" % module in d\r
+ import sys\r
+ module = sys.modules[module]\r
+ \r
+ names = module.__dict__.keys()\r
+ for name in names:\r
+ if not allow(name): continue\r
+ \r
+ value = getattr(module, name)\r
+ if isinstance(value, type):\r
+ setattr(module, name, logclass(value))\r
+ print>>log,"autolog.logmodule(): bound %s" %name\r
+ elif isinstance(value, types.FunctionType):\r
+ setattr(module, name, logfunction(value))\r
+ print>>log,"autolog.logmodule(): bound %s" %name\r
+\r
+def logfunction(theFunction, displayName=None):\r
+ """a decorator."""\r
+ if not displayName: displayName = theFunction.__name__\r
+\r
+ def _wrapper(*args, **kwds):\r
+ global indent\r
+ argstr = formatAllArgs(args, kwds)\r
+ \r
+ # Log the entry into the function\r
+ indentlog("%s%s%s (%s) " % (BOLDRED,displayName,NORMAL, argstr))\r
+ log.flush()\r
+ \r
+ indent += 1\r
+ returnval = theFunction(*args,**kwds)\r
+ indent -= 1\r
+ \r
+ # Log return\r
+ ##indentlog("return: %s"% shortstr(returnval)\r
+ return returnval\r
+ return _wrapper\r
+\r
+def logmethod(theMethod, displayName=None):\r
+ """use this for class or instance methods, it formats with the object out front."""\r
+ if not displayName: displayName = theMethod.__name__\r
+ def _methodWrapper(self, *args, **kwds):\r
+ "Use this one for instance or class methods"\r
+ global indent\r
+\r
+ argstr = formatAllArgs(args, kwds) \r
+ selfstr = shortstr(self)\r
+ \r
+ #print >> log,"%s%s. %s (%s) " % (indStr*indent,selfstr,methodname,argstr)\r
+ indentlog("%s.%s%s%s (%s) " % (selfstr, BOLDRED,theMethod.__name__,NORMAL, argstr))\r
+ log.flush()\r
+ \r
+ indent += 1\r
+ \r
+ if theMethod.__name__ == 'OnSize':\r
+ indentlog("position, size = %s%s %s%s" %(BOLDBLUE, self.GetPosition(), self.GetSize(), NORMAL))\r
+\r
+ returnval = theMethod(self, *args,**kwds)\r
+\r
+ indent -= 1\r
+ \r
+ return returnval\r
+ return _methodWrapper\r
+\r
+\r
+def logclass(cls, methodsAsFunctions=False, \r
+ logMatch=".*", logNotMatch="asdfnomatch"):\r
+ """ \r
+ A class "decorator". But python doesn't support decorator syntax for\r
+ classes, so do it manually::\r
+ \r
+ class C(object):\r
+ ...\r
+ C = logclass(C)\r
+ \r
+ @param methodsAsFunctions: set to True if you always want methodname first\r
+ in the display. Probably breaks if you're using class/staticmethods?\r
+ """\r
+\r
+ allow = lambda s: re.match(logMatch, s) and not re.match(logNotMatch, s) and \\r
+ s not in ('__str__','__repr__')\r
+ \r
+ namesToCheck = cls.__dict__.keys()\r
+ \r
+ for name in namesToCheck:\r
+ if not allow(name): continue\r
+ # unbound methods show up as mere functions in the values of\r
+ # cls.__dict__,so we have to go through getattr\r
+ value = getattr(cls, name)\r
+ \r
+ if methodsAsFunctions and callable(value):\r
+ setattr(cls, name, logfunction(value)) \r
+ elif isinstance(value, types.MethodType):\r
+ #a normal instance method\r
+ if value.im_self == None: \r
+ setattr(cls, name, logmethod(value))\r
+ \r
+ #class & static method are more complex.\r
+ #a class method\r
+ elif value.im_self == cls: \r
+ w = logmethod(value.im_func, \r
+ displayName="%s.%s" %(cls.__name__, value.__name__))\r
+ setattr(cls, name, classmethod(w))\r
+ else: assert False\r
+\r
+ #a static method\r
+ elif isinstance(value, types.FunctionType):\r
+ w = logfunction(value, \r
+ displayName="%s.%s" %(cls.__name__, value.__name__))\r
+ setattr(cls, name, staticmethod(w))\r
+ return cls\r
+\r
+class LogMetaClass(type):\r
+ """\r
+ Alternative to logclass(), you set this as a class's __metaclass__.\r
+ \r
+ It will not work if the metaclass has already been overridden (e.g.\r
+ schema.Item or zope.interface (used in Twisted) \r
+ \r
+ Also, it should fail for class/staticmethods, that hasnt been added here\r
+ yet. \r
+ """\r
+ \r
+ def __new__(cls,classname,bases,classdict):\r
+ logmatch = re.compile(classdict.get('logMatch','.*'))\r
+ lognotmatch = re.compile(classdict.get('logNotMatch', 'nevermatchthisstringasdfasdf'))\r
+ \r
+ for attr,item in classdict.items():\r
+ if callable(item) and logmatch.match(attr) and not lognotmatch.match(attr):\r
+ classdict['_H_%s'%attr] = item # rebind the method\r
+ classdict[attr] = logmethod(item) # replace method by wrapper\r
+\r
+ return type.__new__(cls,classname,bases,classdict) \r
+\r
+\r
+ \r
+# ---------------------------- Tests and examples ----------------------------\r
+\r
+if __name__=='__main__':\r
+ print; print "------------------- single function logging ---------------"\r
+ @logfunction\r
+ def test():\r
+ return 42\r
+ \r
+ test()\r
+\r
+ print; print "------------------- single method logging -----------------"\r
+ class Test1(object):\r
+ def __init__(self):\r
+ self.a = 10\r
+ \r
+ @logmethod\r
+ def add(self,a,b): return a+b\r
+ \r
+ @logmethod\r
+ def fac(self,val):\r
+ if val == 1:\r
+ return 1\r
+ else:\r
+ return val * self.fac(val-1)\r
+ \r
+ @logfunction\r
+ def fac2(self, val):\r
+ if val == 1:\r
+ return 1\r
+ else:\r
+ return val * self.fac2(val-1) \r
+ \r
+ t = Test1()\r
+ t.add(5,6)\r
+ t.fac(4)\r
+ print "--- tagged as @logfunction, doesn't understand 'self' is special:"\r
+ t.fac2(4)\r
+ \r
+\r
+ print; print """-------------------- class "decorator" usage ------------------"""\r
+ class Test2(object):\r
+ #will be ignored\r
+ def __init__(self):\r
+ self.a = 10\r
+ def ignoreThis(self): pass\r
+ \r
+\r
+ def add(self,a,b):return a+b\r
+ def fac(self,val):\r
+ if val == 1:\r
+ return 1\r
+ else:\r
+ return val * self.fac(val-1)\r
+ \r
+ Test2 = logclass(Test2, logMatch='fac|add')\r
+\r
+ t2 = Test2()\r
+ t2.add(5,6)\r
+ t2.fac(4)\r
+ t2.ignoreThis()\r
+ \r
+ \r
+ print; print "-------------------- metaclass usage ------------------"\r
+ class Test3(object):\r
+ __metaclass__ = LogMetaClass\r
+ logNotMatch = 'ignoreThis'\r
+ \r
+ def __init__(self): pass\r
+ \r
+ def fac(self,val):\r
+ if val == 1:\r
+ return 1\r
+ else:\r
+ return val * self.fac(val-1)\r
+ def ignoreThis(self): pass\r
+ t3 = Test3()\r
+ t3.fac(4)\r
+ t3.ignoreThis()\r
+\r
+ print; print "-------------- testing static & classmethods --------------"\r
+ class Test4(object):\r
+ @classmethod\r
+ def cm(cls, a, b): \r
+ print cls\r
+ return a+b\r
+\r
+ def im(self, a, b): \r
+ print self\r
+ return a+b\r
+ \r
+ @staticmethod\r
+ def sm(a,b): return a+b\r
+\r
+ Test4 = logclass(Test4)\r
+\r
+ Test4.cm(4,3)\r
+ Test4.sm(4,3)\r
+\r
+ t4 = Test4()\r
+ t4.im(4,3)\r
+ t4.sm(4,3)\r
+ t4.cm(4,3)\r
+ \r
+ #print; print "-------------- static & classmethods: where to put decorators? --------------"\r
+ #class Test5(object):\r
+ #@classmethod\r
+ #@logmethod\r
+ #def cm(cls, a, b): \r
+ #print cls\r
+ #return a+b\r
+ #@logmethod\r
+ #def im(self, a, b): \r
+ #print self\r
+ #return a+b\r
+ \r
+ #@staticmethod\r
+ #@logfunction\r
+ #def sm(a,b): return a+b\r
+\r
+\r
+ #Test5.cm(4,3)\r
+ #Test5.sm(4,3)\r
+\r
+ #t5 = Test5()\r
+ #t5.im(4,3)\r
+ #t5.sm(4,3)\r
+ #t5.cm(4,3) \r