| """ |
| basic logging functionality based on a producer/consumer scheme. |
| |
| XXX implement this API: (maybe put it into slogger.py?) |
| |
| log = Logger( |
| info=py.log.STDOUT, |
| debug=py.log.STDOUT, |
| command=None) |
| log.info("hello", "world") |
| log.command("hello", "world") |
| |
| log = Logger(info=Logger(something=...), |
| debug=py.log.STDOUT, |
| command=None) |
| """ |
| import py |
| import sys |
| |
| |
| class Message(object): |
| def __init__(self, keywords, args): |
| self.keywords = keywords |
| self.args = args |
| |
| def content(self): |
| return " ".join(map(str, self.args)) |
| |
| def prefix(self): |
| return "[%s] " % (":".join(self.keywords)) |
| |
| def __str__(self): |
| return self.prefix() + self.content() |
| |
| |
| class Producer(object): |
| """ (deprecated) Log producer API which sends messages to be logged |
| to a 'consumer' object, which then prints them to stdout, |
| stderr, files, etc. Used extensively by PyPy-1.1. |
| """ |
| |
| Message = Message # to allow later customization |
| keywords2consumer = {} |
| |
| def __init__(self, keywords, keywordmapper=None, **kw): |
| if hasattr(keywords, 'split'): |
| keywords = tuple(keywords.split()) |
| self._keywords = keywords |
| if keywordmapper is None: |
| keywordmapper = default_keywordmapper |
| self._keywordmapper = keywordmapper |
| |
| def __repr__(self): |
| return "<py.log.Producer %s>" % ":".join(self._keywords) |
| |
| def __getattr__(self, name): |
| if '_' in name: |
| raise AttributeError(name) |
| producer = self.__class__(self._keywords + (name,)) |
| setattr(self, name, producer) |
| return producer |
| |
| def __call__(self, *args): |
| """ write a message to the appropriate consumer(s) """ |
| func = self._keywordmapper.getconsumer(self._keywords) |
| if func is not None: |
| func(self.Message(self._keywords, args)) |
| |
| class KeywordMapper: |
| def __init__(self): |
| self.keywords2consumer = {} |
| |
| def getstate(self): |
| return self.keywords2consumer.copy() |
| |
| def setstate(self, state): |
| self.keywords2consumer.clear() |
| self.keywords2consumer.update(state) |
| |
| def getconsumer(self, keywords): |
| """ return a consumer matching the given keywords. |
| |
| tries to find the most suitable consumer by walking, starting from |
| the back, the list of keywords, the first consumer matching a |
| keyword is returned (falling back to py.log.default) |
| """ |
| for i in range(len(keywords), 0, -1): |
| try: |
| return self.keywords2consumer[keywords[:i]] |
| except KeyError: |
| continue |
| return self.keywords2consumer.get('default', default_consumer) |
| |
| def setconsumer(self, keywords, consumer): |
| """ set a consumer for a set of keywords. """ |
| # normalize to tuples |
| if isinstance(keywords, str): |
| keywords = tuple(filter(None, keywords.split())) |
| elif hasattr(keywords, '_keywords'): |
| keywords = keywords._keywords |
| elif not isinstance(keywords, tuple): |
| raise TypeError("key %r is not a string or tuple" % (keywords,)) |
| if consumer is not None and not py.builtin.callable(consumer): |
| if not hasattr(consumer, 'write'): |
| raise TypeError( |
| "%r should be None, callable or file-like" % (consumer,)) |
| consumer = File(consumer) |
| self.keywords2consumer[keywords] = consumer |
| |
| |
| def default_consumer(msg): |
| """ the default consumer, prints the message to stdout (using 'print') """ |
| sys.stderr.write(str(msg)+"\n") |
| |
| default_keywordmapper = KeywordMapper() |
| |
| |
| def setconsumer(keywords, consumer): |
| default_keywordmapper.setconsumer(keywords, consumer) |
| |
| |
| def setstate(state): |
| default_keywordmapper.setstate(state) |
| |
| |
| def getstate(): |
| return default_keywordmapper.getstate() |
| |
| # |
| # Consumers |
| # |
| |
| |
| class File(object): |
| """ log consumer wrapping a file(-like) object """ |
| def __init__(self, f): |
| assert hasattr(f, 'write') |
| # assert isinstance(f, file) or not hasattr(f, 'open') |
| self._file = f |
| |
| def __call__(self, msg): |
| """ write a message to the log """ |
| self._file.write(str(msg) + "\n") |
| if hasattr(self._file, 'flush'): |
| self._file.flush() |
| |
| |
| class Path(object): |
| """ log consumer that opens and writes to a Path """ |
| def __init__(self, filename, append=False, |
| delayed_create=False, buffering=False): |
| self._append = append |
| self._filename = str(filename) |
| self._buffering = buffering |
| if not delayed_create: |
| self._openfile() |
| |
| def _openfile(self): |
| mode = self._append and 'a' or 'w' |
| f = open(self._filename, mode) |
| self._file = f |
| |
| def __call__(self, msg): |
| """ write a message to the log """ |
| if not hasattr(self, "_file"): |
| self._openfile() |
| self._file.write(str(msg) + "\n") |
| if not self._buffering: |
| self._file.flush() |
| |
| |
| def STDOUT(msg): |
| """ consumer that writes to sys.stdout """ |
| sys.stdout.write(str(msg)+"\n") |
| |
| |
| def STDERR(msg): |
| """ consumer that writes to sys.stderr """ |
| sys.stderr.write(str(msg)+"\n") |
| |
| |
| class Syslog: |
| """ consumer that writes to the syslog daemon """ |
| |
| def __init__(self, priority=None): |
| if priority is None: |
| priority = self.LOG_INFO |
| self.priority = priority |
| |
| def __call__(self, msg): |
| """ write a message to the log """ |
| import syslog |
| syslog.syslog(self.priority, str(msg)) |
| |
| |
| try: |
| import syslog |
| except ImportError: |
| pass |
| else: |
| for _prio in "EMERG ALERT CRIT ERR WARNING NOTICE INFO DEBUG".split(): |
| _prio = "LOG_" + _prio |
| try: |
| setattr(Syslog, _prio, getattr(syslog, _prio)) |
| except AttributeError: |
| pass |