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