Package joy ::
Module loggit
|
|
1 """
2 loggit.py implements a general purpose logging interface and some convenience
3 functions for emitting both debug and progress messages.
4
5 The interface consists of the LogWriter class and several utility functions,
6 most notable of which is progress().
7
8 Functions:
9 dbgId, debugMsg -- used for generating useful debug messages
10
11 progress -- emit a text message "immediately" as a progress indication for
12 the user. Progress messages can also be emitted using speech synthesis
13 and may be automatically logged to one or more LogWriter-s.
14
15 iterlog -- iterator for log entries
16
17 Data Format:
18 loggit logs are GNU-zipped (using the gzip module) streams of 'safe' YAML
19 documents. YAML is a human and machine readable text format, much more
20 friendly than XML; see http://www.yaml.org . Each document contains a single
21 YAML mapping object, with at least a TIME integer and a TOPIC string. By
22 virtue of the YAML standard, mapping keys are sorted in ASCII lexicographic
23 order, so that if all other log entry mapping keys start with lowercase
24 letters the first two keys will be TIME and TOPIC, e.g.
25
26 --- {TIME: 286447, TOPIC: getter, func_name: get, value: 7}
27 """
28 import gzip, yaml
29 import types
30 from pygix import now as time
31 from sys import stdout
32 from speak import say
33
34
35
36 T0 = time()
37
38 PROGRESS_LOG = set()
39
41 """Generate a unique, human readable name for an object"""
42 if type(obj) in [tuple,list,set]:
43 return '[%s]' % ", ".join( (dbgId(x) for x in obj) )
44 return '%s@%04x' % (obj.__class__.__name__,id(obj) % (0x10000-1))
45
47 """
48 Print a "debug" message. Debug messages are indicated by a leading 'DBG'
49 and the dbgId() of the object sending the message. This is used to make
50 it easier to identify the actual object that generated a given message.
51 """
52 nm = dbgId(obj)
53 progress(("DBG %s " % nm)+msg.replace("\n","\n : "))
54
55 __NEED_NL = False
57 """
58 Print a progress message to standard output. The message will have a
59 pre-pended timestamp showing seconds elapsed from the moment the joy
60 module was imported.
61
62 Messages that start with the string "(say) " will be read out loud on
63 systems that support it. See the speak module for details.
64
65 Progress messages flush standard output, so they display immediately.
66
67 A copy of progress messages is write()n to every logger in PROGRESS_LOG
68
69 if sameLine is True, message is prefixed by a "\r" instead of ending with
70 a "\n" -- showing it on the same line in the terminal
71 """
72 global __NEED_NL
73 t = time()-T0
74 if sameLine:
75 stdout.write("\r%6.2f: %s" % (t,msg))
76 __NEED_NL = True
77 else:
78 if __NEED_NL:
79 stdout.write("\n")
80 stdout.write("%6.2f: %s\n" % (t,msg))
81 __NEED_NL = False
82 stdout.flush()
83 if msg[:6]=="(say) ":
84 say(msg[6:])
85 for logger in PROGRESS_LOG:
86 logger.write('logmsg',progress=msg)
87
89 """
90 Concrete class LogWriter provides an interface for logging data.
91
92 Logs may be written to a stream or a file. The default file format is
93 a gzipped YAML file. If output is sent to a stream, the output is an
94 uncompressed YAML.
95
96 Each .write() operation maps into a single YAML document, allowing the
97 yaml module's safe_load_all method to parse them one at a time.
98
99 Typical usage:
100 >>> L = LogWriter('mylog.yml.gz')
101 >>> L.write( foo = 'fu', bar = 'bar' )
102 >>> L.close()
103 >>> for data in iterlog('mylog.yml.gz'):
104 >>> print data
105
106 """
107 - def __init__(self,stream=None,sync=False):
108 """
109 INPUTS:
110 stream -- stream -- output stream to use, must support write, flush, close
111 -- str -- filename to use (.gz added automatically if not present)
112 -- None -- logger started in uninitialized state; use open()
113 sync -- boolean -- set to force a flush after every log entry.
114
115 ATTRIBUTES:
116 .s -- the open stream
117 .sync -- auto-flush flag
118 .timeUnit -- time unit for log timestamps; default is millisecond
119 """
120 if stream is not None:
121 self.open(stream)
122 else:
123 self.s = None
124 self.sync = sync
125 self.timeUnit = 1e-3
126
127 - def open( self, stream ):
128 """
129 Open a logging stream.
130 INPUT:
131 stream -- str -- a file name for the log. A .gz will be appended if not
132 present. Log will be stored as a gzip YAML file.
133 -- file-like -- an object with .write() .flush() and .close()
134 methods. Log will be sent as YAML text, with a single .write()
135 for each entry of the log.
136 """
137 if type(stream)==str:
138 if stream[-3:]!='.gz':
139 stream = stream + ".gz"
140 stream = gzip.open(stream,"w")
141 self.s = stream
142
143 - def write( self, topic, **kw ):
144 """
145 Write a log entry. Each entry consists of a topic string and an
146 automatically generated timestamp, and may include a dictionary of
147 additional attributes supplied as keyword arguments.
148
149 The log entry will be emitted as a YAML document (entry starting with ---)
150 that contains a mapping with keys TIME for the timestamp (in units of
151 .timeUnit) and TOPIC for the topic.
152
153 If .sync is set, the stream will be flushed after each entry.
154 """
155 if self.s is None:
156 raise IOError("LogWriter stream is not open")
157 t = long((time()-T0)/self.timeUnit)
158 kw.update(TIME=t,TOPIC=topic)
159 entry = "--- " + yaml.safe_dump(kw)
160
161 self.s.write(entry)
162 if self.sync:
163 self.flush()
164
166 """
167 Close a logger output stream
168 """
169 self.s.close()
170 self.s = None
171
173 """
174 Flush the logger output stream
175 """
176 self.s.flush()
177
179 """
180 Wrap the specified callable and log results of calls.
181 INPUTS:
182 fun -- callable -- "getter" function with no params
183 fmt -- callable -- formatting function for the parameter.
184 Must return something yaml can safe_dump()
185 attr -- dict -- dictionary of extra attributes for log entry
186 OUTPUTS:
187 callable function that calls fun() and logs the results
188
189 Typical usage is to take an existing getter function and replace it in
190 in place with the wrapped getter:
191 >>> def dummyGetter():
192 >>> return int(raw_input())
193 >>> L = joy.loggit.LogWriter("foo")
194 >>> g = L.getterWrapperFor(dummyGetter
195 ,fmt=lambda x : "0x%X" % x
196 , attr=dict(sparrow = 'african'))
197 >>> g()
198 258
199 258
200 >>> L.close()
201 >>> !gunzip -c foo.gz
202 --- {TIME: ????, TOPIC: getter, func_name: dummyGetter, sparrow: african, value: '0x102'}
203 """
204 if type(fun) is types.MethodType:
205 fn = fun.__func__.__name__
206 elif type(fun) is types.FunctionType:
207 fn = fun.func_name
208 else:
209 fn = "<%s instance>" % fun.__class__.__name__
210 def _getWrapper():
211 val = fun()
212 self.write("getter",func_name=fn, value=fmt(val), **attr)
213 return val
214 return _getWrapper
215
217 """
218 Wrap the specified callable and log parameters of calls.
219 INPUTS:
220 fun -- callable function with one parameter
221 name -- function name to use in
222 ifmt -- formatting function for arguments
223 ofmt -- formatting function for results / None to omit result
224 OUTPUTS:
225 callable function that logs the parameter and then
226 calls fun() with it and logs the results
227
228 Usage -- see example for getterWrapperFor
229 """
230 fn = fun.__func__.__name__
231 def _setWrapper(arg):
232 res = fun(arg)
233 if ofmt is None:
234 self.write("setter",func_name=fn, argument=ifmt(arg),**attr )
235 else:
236 self.write("setter",func_name=fn, argument=ifmt(arg), result=ofmt(res), **attr)
237 return res
238 return _setWrapper
239
241 """
242 Iterate over a logfile returning entry values
243
244 Entries have keys TIME and TOPIC for timestamp and topic
245
246 OUTPUT: t,topic,val
247 t -- timestamp
248 val -- dictionary with log entry values
249 """
250 f = gzip.open( filename,"r")
251 try:
252 for entry in yaml.safe_load_all(f):
253 yield entry
254 finally:
255 f.close()
256