1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """ Provides a Logger class which can be wrapped around another
17 python class to log method calls and attribute changes.
18 """
19
20 import types
21 import re
22
23 reType = type(re.compile('f*'))
24 stringTypes = [types.StringType,types.UnicodeType,reType]
25
27 """ checks to see if any of the regular expressions in a list match a string
28
29 **Arguments**
30
31 - what: the thing to match against
32
33 - checkList: the list of regexps to try the match with
34
35 **Returns**
36
37 1 or 0 depending upon whether or not _what_ was found in checkList
38
39 **Notes**
40
41 - the search is done using _match_, so we match *any* part of _what_
42
43 """
44 for entry in checkList:
45 if type(entry) == reType:
46 if entry.match(what) is not None:
47 return 1
48 else:
49 if what == entry:
50 return 1
51 return 0
52
54 """ this is just a simple class with a __call__ method we'll use to pass
55 any method invocations back to the caller.
56
57 """
59 """ Constructor
60
61 **Arguments:**
62
63 - log: a python list (or anything else supporting the append method)
64 which is used to log the actual invocation of a method
65
66 - obj: the object which is calling the method
67
68 - method: the *name* of the method to be invoked
69
70 """
71 self._obj = obj
72 self._log = log
73 self._method = method
75 """ logs the method name and arguments and makes the call
76
77 """
78 self._log.append((self._method,args,kwargs))
79 return getattr(self._obj,self._method)(*args,**kwargs)
80
82 """ This is the actual wrapper class.
83
84 The wrapper is fairly thin; it only has one methods of its own:
85
86 - _LoggerGetLog()
87
88 and then several instance variables:
89
90 - _loggerFlushCommand
91
92 - _loggerClass
93
94 - _loggerObj
95
96 - _loggerCommandLog
97
98 - _loggerIgnore
99
100 These names were chosen to minimize the likelihood of a collision
101 with the attributes of a wrapped class. Obviously... ;-)
102
103 The general idea of using this is that you wrap a class in the logger,
104 and then use the class as you normally would. Whenever you want to
105 get the contents of the log (for example after running your program for
106 a while), you can call _loggerCommandLog. The resulting list can be
107 played back in another (or the same) object using the replay() function
108 defined below.
109
110 The logger can, optionally, be set to flush its log whenever a method with
111 a particular name is invoked. For example, you may want to be wrapping
112 some kind of drawing canvas and want to reset the log whenever the canvas
113 is cleared because there's no point in storing commands which will have
114 no effect on the final drawing.
115
116 **Note**
117
118 because of the way I've worked this, the log will actually be flushed
119 whenever the client program accesses the flush method, it need not be invoked.
120 i.e. if the loggerFlushCommand is 'foo', then doing either wrappedObj.foo() or
121 wrappedObj.foo will reset the log. This is undesirable and will most likely
122 be fixed in a future version
123
124 """
125 - def __init__(self,klass,*args,**kwargs):
126 """ Constructor
127
128 **Arguments**
129
130 The one required argument here is _klass_, which is the class
131 to be wrapped.
132
133 **Optional Keyword Arguments**
134
135 - loggerFlushCommand: the name of the attribute which will flush the log
136
137 - loggerIgnore: a list of regexps defining methods which should not be
138 logged
139
140 **All other arguments are passed to the constructor for _klass_ **
141
142 """
143 if kwargs.has_key('loggerFlushCommand'):
144 self.__dict__['_loggerFlushCommand'] = kwargs['loggerFlushCommand']
145 del kwargs['loggerFlushCommand']
146 else:
147 self.__dict__['_loggerFlushCommand'] = None
148 if kwargs.has_key('loggerIgnore'):
149 tempL = kwargs['loggerIgnore']
150 for entry in tempL:
151 if type(entry) not in stringTypes:
152 raise ValueError,'All entries in loggerIgnore must be either strings or regexps'
153 self.__dict__['_loggerIgnore'] = tempL
154 del kwargs['loggerIgnore']
155 else:
156 self.__dict__['_loggerIgnore'] = []
157 self.__dict__['_loggerClass'] = klass
158 self.__dict__['_loggerObj'] = klass(*args,**kwargs)
159 self.__dict__['_loggerCommandLog'] = []
160
162 """ Returns the contents of the command log as a python list
163
164 """
165 return self._loggerCommandLog
166
168 """ here's where the logging of method invocations takes place
169
170 """
171 if hasattr(self._loggerObj,which):
172 tmpAttr = getattr(self._loggerObj,which)
173 if type(tmpAttr) == types.MethodType:
174 loggerFlushCommand = self._loggerFlushCommand
175 if which == loggerFlushCommand:
176 self._loggerCommandLog = []
177 return Callable([],self._loggerObj,which)
178 elif self._loggerIgnore != [] and isPresent(which,self._loggerIgnore):
179 return Callable([],self._loggerObj,which)
180 else:
181 return Callable(self._loggerCommandLog,self._loggerObj,which)
182 else:
183 return tmpAttr
184 else:
185 raise AttributeError, '%s instance has no attribute %s'%(repr(self._loggerClass.__name__),repr(which))
186
188 """ setattr calls (i.e. wrappedObject.foo = 1) are also logged
189
190 """
191 d = self.__dict__
192 if d.has_key(which):
193 d[which] = val
194 else:
195 self._loggerCommandLog.append((setattr,which,val))
196 setattr(self._loggerObj,which,val)
197 return val
198
200 """ loops through the items in a Logger log list and invokes
201 them in obj
202
203 **Arguments**
204
205 - logItems: a list of 3 tuples containing:
206
207 1) method name
208
209 2) tuple of positional arguments
210
211 3) dictionary of keyword arguments
212
213 - obj: the object in which the log should be replayed
214
215 **Returns**
216
217 a list with the the return values of all the method
218 invocations/attribute assignments
219
220 """
221 if isinstance(logItems,Logger):
222 logItems = logItems._LoggerGetLog()
223 resList = []
224 for method,a1,a2 in logItems:
225 if callable(method):
226 if method == setattr:
227 method(obj,a1,a2)
228 resList.append(a2)
229 else:
230 resList.append(method(obj,a1,a2))
231 else:
232 a = getattr(obj,method)
233 resList.append(apply(a,a1,a2))
234 return resList
235