Package joy :: Module loggit
[hide private]
[frames] | no frames]

Source Code for Module joy.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  # Time at which this module was imported. Used as base time for all `progress` 
 35  #   messages printed. 
 36  T0 = time() 
 37   
 38  PROGRESS_LOG = set() 
 39   
40 -def dbgId(obj):
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
46 -def debugMsg(obj,msg):
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
56 -def progress(msg,sameLine=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
88 -class LogWriter( object ):
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 # print "~~> ",entry 161 self.s.write(entry) 162 if self.sync: 163 self.flush()
164
165 - def close( self ):
166 """ 167 Close a logger output stream 168 """ 169 self.s.close() 170 self.s = None
171
172 - def flush( self ):
173 """ 174 Flush the logger output stream 175 """ 176 self.s.flush()
177
178 - def getterWrapperFor( self, fun, fmt=lambda x : x, attr={} ):
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
216 - def setterWrapperFor( self, fun, ifmt = lambda x : x, ofmt = None, attr={} ):
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
240 -def iterlog( filename ):
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