Source

pypy / py / _log / log.py

Full commit
"""
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, 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 """
        py.std.syslog.syslog(self.priority, str(msg))

for _prio in "EMERG ALERT CRIT ERR WARNING NOTICE INFO DEBUG".split():
    _prio = "LOG_" + _prio
    try:
        setattr(Syslog, _prio, getattr(py.std.syslog, _prio))
    except AttributeError:
        pass