1 # Written by Brendan O'Connor, brenocon@gmail.com, www.anyall.org
\r
2 # * Originally written Aug. 2005
\r
3 # * Posted to gist.github.com/16173 on Oct. 2008
\r
5 # Copyright (c) 2003-2006 Open Source Applications Foundation
\r
7 # Licensed under the Apache License, Version 2.0 (the "License");
\r
8 # you may not use this file except in compliance with the License.
\r
9 # You may obtain a copy of the License at
\r
11 # http://www.apache.org/licenses/LICENSE-2.0
\r
13 # Unless required by applicable law or agreed to in writing, software
\r
14 # distributed under the License is distributed on an "AS IS" BASIS,
\r
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
16 # See the License for the specific language governing permissions and
\r
17 # limitations under the License.
\r
19 import re, sys, types
\r
22 Have all your function & method calls automatically logged, in indented outline
\r
23 form - unlike the stack snapshots in an interactive debugger, it tracks call
\r
24 structure & stack depths across time!
\r
26 It hooks into all function calls that you specify, and logs each time they're
\r
27 called. I find it especially useful when I don't know what's getting called
\r
28 when, or need to continuously test for state changes. (by hacking this file)
\r
30 Originally inspired from the python cookbook:
\r
31 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/198078
\r
34 - tag functions or individual methods to be autologged
\r
35 - tag an entire class's methods to be autologged
\r
36 - tag an entire module's classes and functions to be autologged
\r
39 - allow tagging of ALL modules in the program on startup?
\r
42 - certain classes barf when you logclass() them -- most notably,
\r
43 SWIG-generated wrappers, and perhaps others.
\r
45 USAGE: see examples on the bottom of this file.
\r
51 If your terminal can't keep up, try xterm or putty, they seem to be highest
\r
52 performance. xterm is available for all platforms through X11...
\r
54 Also try: (RunChandler > log &); tail -f log
\r
56 Also, you can "less -R log" afterward and get the colors correct.
\r
58 If you have long lines, less -RS kills wrapping, enhancing readability. Also
\r
59 can chop at formatAllArgs().
\r
61 If you want long lines to be chopped realtime, try piping through less::
\r
63 RunChandler | less -RS
\r
65 but then you have to hit 'space' lots to prevent chandler from freezing.
\r
66 less's 'F' command is supposed to do this correctly but doesn't work for me.
\r
70 #@@@ should use the standard python logging system?
\r
73 # Globally incremented across function calls, so tracks stack depth
\r
78 # ANSI escape codes for terminals.
\r
79 # X11 xterm: always works, all platforms
\r
80 # cygwin dosbox: run through |cat and then colors work
\r
81 # linux: works on console & gnome-terminal
\r
85 BLACK = "\033[0;30m"
\r
87 GREEN = "\033[0;32m"
\r
90 PURPLE = "\033[0;35m"
\r
91 BROWN = "\033[0;33m"
\r
93 BOLDGRAY = "\033[1;30m"
\r
94 BOLDBLUE = "\033[1;34m"
\r
95 BOLDGREEN = "\033[1;32m"
\r
96 BOLDCYAN = "\033[1;36m"
\r
97 BOLDRED = "\033[1;31m"
\r
98 BOLDPURPLE = "\033[1;35m"
\r
99 BOLDYELLOW = "\033[1;33m"
\r
100 WHITE = "\033[1;37m"
\r
105 def indentlog(message):
\r
106 global log, indStr, indent
\r
107 print >>log, "%s%s" %(indStr*indent, message)
\r
112 Where to put gritty heuristics to make an object appear in most useful
\r
113 form. defaults to __str__.
\r
115 if "wx." in str(obj.__class__) or obj.__class__.__name__.startswith("wx"):
\r
116 shortclassname = obj.__class__.__name__
\r
117 ##shortclassname = str(obj.__class__).split('.')[-1]
\r
118 if hasattr(obj, "blockItem") and hasattr(obj.blockItem, "blockName"):
\r
119 moreInfo = "block:'%s'" %obj.blockItem.blockName
\r
121 moreInfo = "at %d" %id(obj)
\r
122 return "<%s %s>" % (shortclassname, moreInfo)
\r
126 def formatAllArgs(args, kwds):
\r
128 makes a nice string representation of all the arguments
\r
132 allargs.append('%s' % shortstr(item))
\r
133 for key,item in kwds.items():
\r
134 allargs.append('%s=%s' % (key,shortstr(item)))
\r
135 formattedArgs = ', '.join(allargs)
\r
136 if len(formattedArgs) > 150:
\r
137 return formattedArgs[:146] + " ..."
\r
138 return formattedArgs
\r
141 def logmodules(listOfModules):
\r
142 for m in listOfModules:
\r
145 def logmodule(module, logMatch=".*", logNotMatch="nomatchasfdasdf"):
\r
147 WARNING: this seems to break if you import SWIG wrapper classes
\r
148 directly into the module namespace ... logclass() creates weirdness when
\r
149 used on them, for some reason.
\r
151 @param module: could be either an actual module object, or the string
\r
152 you can import (which seems to be the same thing as its
\r
153 __name__). So you can say logmodule(__name__) at the end
\r
154 of a module definition, to log all of it.
\r
157 allow = lambda s: re.match(logMatch, s) and not re.match(logNotMatch, s)
\r
159 if isinstance(module, str):
\r
161 exec "import %s" % module in d
\r
163 module = sys.modules[module]
\r
165 names = module.__dict__.keys()
\r
167 if not allow(name): continue
\r
169 value = getattr(module, name)
\r
170 if isinstance(value, type):
\r
171 setattr(module, name, logclass(value))
\r
172 print>>log,"autolog.logmodule(): bound %s" %name
\r
173 elif isinstance(value, types.FunctionType):
\r
174 setattr(module, name, logfunction(value))
\r
175 print>>log,"autolog.logmodule(): bound %s" %name
\r
177 def logfunction(theFunction, displayName=None):
\r
179 if not displayName: displayName = theFunction.__name__
\r
181 def _wrapper(*args, **kwds):
\r
183 argstr = formatAllArgs(args, kwds)
\r
185 # Log the entry into the function
\r
186 indentlog("%s%s%s (%s) " % (BOLDRED,displayName,NORMAL, argstr))
\r
190 returnval = theFunction(*args,**kwds)
\r
194 ##indentlog("return: %s"% shortstr(returnval)
\r
198 def logmethod(theMethod, displayName=None):
\r
199 """use this for class or instance methods, it formats with the object out front."""
\r
200 if not displayName: displayName = theMethod.__name__
\r
201 def _methodWrapper(self, *args, **kwds):
\r
202 "Use this one for instance or class methods"
\r
205 argstr = formatAllArgs(args, kwds)
\r
206 selfstr = shortstr(self)
\r
208 #print >> log,"%s%s. %s (%s) " % (indStr*indent,selfstr,methodname,argstr)
\r
209 indentlog("%s.%s%s%s (%s) " % (selfstr, BOLDRED,theMethod.__name__,NORMAL, argstr))
\r
214 if theMethod.__name__ == 'OnSize':
\r
215 indentlog("position, size = %s%s %s%s" %(BOLDBLUE, self.GetPosition(), self.GetSize(), NORMAL))
\r
217 returnval = theMethod(self, *args,**kwds)
\r
222 return _methodWrapper
\r
225 def logclass(cls, methodsAsFunctions=False,
\r
226 logMatch=".*", logNotMatch="asdfnomatch"):
\r
228 A class "decorator". But python doesn't support decorator syntax for
\r
229 classes, so do it manually::
\r
235 @param methodsAsFunctions: set to True if you always want methodname first
\r
236 in the display. Probably breaks if you're using class/staticmethods?
\r
239 allow = lambda s: re.match(logMatch, s) and not re.match(logNotMatch, s) and \
\r
240 s not in ('__str__','__repr__')
\r
242 namesToCheck = cls.__dict__.keys()
\r
244 for name in namesToCheck:
\r
245 if not allow(name): continue
\r
246 # unbound methods show up as mere functions in the values of
\r
247 # cls.__dict__,so we have to go through getattr
\r
248 value = getattr(cls, name)
\r
250 if methodsAsFunctions and callable(value):
\r
251 setattr(cls, name, logfunction(value))
\r
252 elif isinstance(value, types.MethodType):
\r
253 #a normal instance method
\r
254 if value.im_self == None:
\r
255 setattr(cls, name, logmethod(value))
\r
257 #class & static method are more complex.
\r
259 elif value.im_self == cls:
\r
260 w = logmethod(value.im_func,
\r
261 displayName="%s.%s" %(cls.__name__, value.__name__))
\r
262 setattr(cls, name, classmethod(w))
\r
266 elif isinstance(value, types.FunctionType):
\r
267 w = logfunction(value,
\r
268 displayName="%s.%s" %(cls.__name__, value.__name__))
\r
269 setattr(cls, name, staticmethod(w))
\r
272 class LogMetaClass(type):
\r
274 Alternative to logclass(), you set this as a class's __metaclass__.
\r
276 It will not work if the metaclass has already been overridden (e.g.
\r
277 schema.Item or zope.interface (used in Twisted)
\r
279 Also, it should fail for class/staticmethods, that hasnt been added here
\r
283 def __new__(cls,classname,bases,classdict):
\r
284 logmatch = re.compile(classdict.get('logMatch','.*'))
\r
285 lognotmatch = re.compile(classdict.get('logNotMatch', 'nevermatchthisstringasdfasdf'))
\r
287 for attr,item in classdict.items():
\r
288 if callable(item) and logmatch.match(attr) and not lognotmatch.match(attr):
\r
289 classdict['_H_%s'%attr] = item # rebind the method
\r
290 classdict[attr] = logmethod(item) # replace method by wrapper
\r
292 return type.__new__(cls,classname,bases,classdict)
\r
296 # ---------------------------- Tests and examples ----------------------------
\r
298 if __name__=='__main__':
\r
299 print; print "------------------- single function logging ---------------"
\r
306 print; print "------------------- single method logging -----------------"
\r
307 class Test1(object):
\r
308 def __init__(self):
\r
312 def add(self,a,b): return a+b
\r
319 return val * self.fac(val-1)
\r
322 def fac2(self, val):
\r
326 return val * self.fac2(val-1)
\r
331 print "--- tagged as @logfunction, doesn't understand 'self' is special:"
\r
335 print; print """-------------------- class "decorator" usage ------------------"""
\r
336 class Test2(object):
\r
338 def __init__(self):
\r
340 def ignoreThis(self): pass
\r
343 def add(self,a,b):return a+b
\r
348 return val * self.fac(val-1)
\r
350 Test2 = logclass(Test2, logMatch='fac|add')
\r
358 print; print "-------------------- metaclass usage ------------------"
\r
359 class Test3(object):
\r
360 __metaclass__ = LogMetaClass
\r
361 logNotMatch = 'ignoreThis'
\r
363 def __init__(self): pass
\r
369 return val * self.fac(val-1)
\r
370 def ignoreThis(self): pass
\r
375 print; print "-------------- testing static & classmethods --------------"
\r
376 class Test4(object):
\r
378 def cm(cls, a, b):
\r
382 def im(self, a, b):
\r
387 def sm(a,b): return a+b
\r
389 Test4 = logclass(Test4)
\r
399 #print; print "-------------- static & classmethods: where to put decorators? --------------"
\r
400 #class Test5(object):
\r
403 #def cm(cls, a, b):
\r
407 #def im(self, a, b):
\r
413 #def sm(a,b): return a+b
\r