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