1""" 2basic logging functionality based on a producer/consumer scheme. 3 4XXX implement this API: (maybe put it into slogger.py?) 5 6 log = Logger( 7 info=py.log.STDOUT, 8 debug=py.log.STDOUT, 9 command=None) 10 log.info("hello", "world") 11 log.command("hello", "world") 12 13 log = Logger(info=Logger(something=...), 14 debug=py.log.STDOUT, 15 command=None) 16""" 17import py 18import sys 19 20 21class Message(object): 22 def __init__(self, keywords, args): 23 self.keywords = keywords 24 self.args = args 25 26 def content(self): 27 return " ".join(map(str, self.args)) 28 29 def prefix(self): 30 return "[%s] " % (":".join(self.keywords)) 31 32 def __str__(self): 33 return self.prefix() + self.content() 34 35 36class Producer(object): 37 """ (deprecated) Log producer API which sends messages to be logged 38 to a 'consumer' object, which then prints them to stdout, 39 stderr, files, etc. Used extensively by PyPy-1.1. 40 """ 41 42 Message = Message # to allow later customization 43 keywords2consumer = {} 44 45 def __init__(self, keywords, keywordmapper=None, **kw): 46 if hasattr(keywords, 'split'): 47 keywords = tuple(keywords.split()) 48 self._keywords = keywords 49 if keywordmapper is None: 50 keywordmapper = default_keywordmapper 51 self._keywordmapper = keywordmapper 52 53 def __repr__(self): 54 return "<py.log.Producer %s>" % ":".join(self._keywords) 55 56 def __getattr__(self, name): 57 if '_' in name: 58 raise AttributeError(name) 59 producer = self.__class__(self._keywords + (name,)) 60 setattr(self, name, producer) 61 return producer 62 63 def __call__(self, *args): 64 """ write a message to the appropriate consumer(s) """ 65 func = self._keywordmapper.getconsumer(self._keywords) 66 if func is not None: 67 func(self.Message(self._keywords, args)) 68 69class KeywordMapper: 70 def __init__(self): 71 self.keywords2consumer = {} 72 73 def getstate(self): 74 return self.keywords2consumer.copy() 75 76 def setstate(self, state): 77 self.keywords2consumer.clear() 78 self.keywords2consumer.update(state) 79 80 def getconsumer(self, keywords): 81 """ return a consumer matching the given keywords. 82 83 tries to find the most suitable consumer by walking, starting from 84 the back, the list of keywords, the first consumer matching a 85 keyword is returned (falling back to py.log.default) 86 """ 87 for i in range(len(keywords), 0, -1): 88 try: 89 return self.keywords2consumer[keywords[:i]] 90 except KeyError: 91 continue 92 return self.keywords2consumer.get('default', default_consumer) 93 94 def setconsumer(self, keywords, consumer): 95 """ set a consumer for a set of keywords. """ 96 # normalize to tuples 97 if isinstance(keywords, str): 98 keywords = tuple(filter(None, keywords.split())) 99 elif hasattr(keywords, '_keywords'): 100 keywords = keywords._keywords 101 elif not isinstance(keywords, tuple): 102 raise TypeError("key %r is not a string or tuple" % (keywords,)) 103 if consumer is not None and not py.builtin.callable(consumer): 104 if not hasattr(consumer, 'write'): 105 raise TypeError( 106 "%r should be None, callable or file-like" % (consumer,)) 107 consumer = File(consumer) 108 self.keywords2consumer[keywords] = consumer 109 110 111def default_consumer(msg): 112 """ the default consumer, prints the message to stdout (using 'print') """ 113 sys.stderr.write(str(msg)+"\n") 114 115default_keywordmapper = KeywordMapper() 116 117 118def setconsumer(keywords, consumer): 119 default_keywordmapper.setconsumer(keywords, consumer) 120 121 122def setstate(state): 123 default_keywordmapper.setstate(state) 124 125 126def getstate(): 127 return default_keywordmapper.getstate() 128 129# 130# Consumers 131# 132 133 134class File(object): 135 """ log consumer wrapping a file(-like) object """ 136 def __init__(self, f): 137 assert hasattr(f, 'write') 138 # assert isinstance(f, file) or not hasattr(f, 'open') 139 self._file = f 140 141 def __call__(self, msg): 142 """ write a message to the log """ 143 self._file.write(str(msg) + "\n") 144 if hasattr(self._file, 'flush'): 145 self._file.flush() 146 147 148class Path(object): 149 """ log consumer that opens and writes to a Path """ 150 def __init__(self, filename, append=False, 151 delayed_create=False, buffering=False): 152 self._append = append 153 self._filename = str(filename) 154 self._buffering = buffering 155 if not delayed_create: 156 self._openfile() 157 158 def _openfile(self): 159 mode = self._append and 'a' or 'w' 160 f = open(self._filename, mode) 161 self._file = f 162 163 def __call__(self, msg): 164 """ write a message to the log """ 165 if not hasattr(self, "_file"): 166 self._openfile() 167 self._file.write(str(msg) + "\n") 168 if not self._buffering: 169 self._file.flush() 170 171 172def STDOUT(msg): 173 """ consumer that writes to sys.stdout """ 174 sys.stdout.write(str(msg)+"\n") 175 176 177def STDERR(msg): 178 """ consumer that writes to sys.stderr """ 179 sys.stderr.write(str(msg)+"\n") 180 181 182class Syslog: 183 """ consumer that writes to the syslog daemon """ 184 185 def __init__(self, priority=None): 186 if priority is None: 187 priority = self.LOG_INFO 188 self.priority = priority 189 190 def __call__(self, msg): 191 """ write a message to the log """ 192 import syslog 193 syslog.syslog(self.priority, str(msg)) 194 195 196try: 197 import syslog 198except ImportError: 199 pass 200else: 201 for _prio in "EMERG ALERT CRIT ERR WARNING NOTICE INFO DEBUG".split(): 202 _prio = "LOG_" + _prio 203 try: 204 setattr(Syslog, _prio, getattr(syslog, _prio)) 205 except AttributeError: 206 pass 207