source: trunk/moap/util/log.py @ 40

Revision 40, 11.7 KB checked in by thomas, 7 years ago (diff)

remove soap traces

Line 
1# -*- Mode: Python -*-
2# vi:si:et:sw=4:sts=4:ts=4
3#
4# moap - Maintenance Of A Project
5# Copyright (C) 2006 Thomas Vander Stichele <thomas at apestaart dot org>
6
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
20
21# this file adapted from Flumotion, (C) Copyright Fluendo
22
23"""
24Logging
25"""
26import sys
27import os
28import fnmatch
29import time
30import traceback
31
32# environment variables controlling levels for each category
33_MOAP_DEBUG = "*:1"
34
35# dynamic dictionary of categories already seen and their level
36_categories = {}
37
38# log handlers registered
39_log_handlers = []
40_log_handlers_limited = []
41
42_initialized = False
43
44# public log levels
45ERROR = 1
46WARN = 2
47INFO = 3
48DEBUG = 4
49LOG = 5
50
51def getLevelName(level):
52    """
53    Return the name of a log level.
54    """
55    assert isinstance(level, int) and level > 0 and level < 6, \
56           "Bad debug level"
57    return ('ERROR', 'WARN', 'INFO', 'DEBUG', 'LOG')[level - 1]
58
59def registerCategory(category):
60    """
61    Register a given category in the debug system.
62    A level will be assigned to it based on the setting of MOAP_DEBUG.
63    """
64    # parse what level it is set to based on MOAP_DEBUG
65    # example: *:2,admin:4
66    global _MOAP_DEBUG
67    global _levels
68    global _categories
69
70    level = 0
71    chunks = _MOAP_DEBUG.split(',')
72    for chunk in chunks:
73        if not chunk:
74            continue
75        if ':' in chunk: 
76            spec, value = chunk.split(':')
77        else:
78            spec = '*'
79            value = chunk
80           
81        # our glob is unix filename style globbing, so cheat with fnmatch
82        # fnmatch.fnmatch didn't work for this, so don't use it
83        if category in fnmatch.filter((category, ), spec):
84            # we have a match, so set level based on string or int
85            if not value:
86                continue
87            try:
88                level = int(value)
89            except ValueError: # e.g. *; we default to most
90                level = 5
91    # store it
92    _categories[category] = level
93
94def getCategoryLevel(category):
95    """
96    @param category: string
97
98    Get the debug level at which this category is being logged, adding it
99    if it wasn't registered yet.
100    """
101    global _categories
102    if not _categories.has_key(category):
103        registerCategory(category)
104    return _categories[category]
105
106def _canShortcutLogging(category, level):
107    if _log_handlers:
108        # we have some loggers operating without filters, have to do
109        # everything
110        return False
111    else:
112        return level > getCategoryLevel(category)
113
114def scrubFilename(filename):
115    '''
116    Scrub the filename of everything before 'moap'
117    '''
118    i = filename.rfind('moap')
119    if i != -1:
120        filename = filename[i:]
121   
122    return filename
123
124def _handle(level, object, category, format, args):
125    def getFileLine():
126        # Return a tuple of (file, line) for the first stack entry
127        # outside of log.py (which would be the caller of log)
128        frame = sys._getframe()
129        while frame:
130            co = frame.f_code
131            if not co.co_filename.endswith('log.py'):
132                return scrubFilename(co.co_filename), frame.f_lineno
133            frame = frame.f_back
134           
135        return "Not found", 0
136
137    if args:
138        message = format % args
139    else:
140        message = format
141
142    # first all the unlimited ones
143    if _log_handlers:
144        (fileName, line) = getFileLine()
145        for handler in _log_handlers:
146            try:
147                handler(level, object, category, fileName, line, message)
148            except TypeError:
149                raise SystemError, "handler %r raised a TypeError" % handler
150
151    if level > getCategoryLevel(category):
152        return
153
154    # set this a second time, just in case there weren't unlimited
155    # loggers there before
156    (fileName, line) = getFileLine()
157
158    for handler in _log_handlers_limited:
159        try:
160            handler(level, object, category, fileName, line, message)
161        except TypeError:
162            raise SystemError, "handler %r raised a TypeError" % handler
163   
164def errorObject(object, cat, format, *args):
165    """
166    Log a fatal error message in the given category. \
167    This will also raise a L{SystemError}.
168    """
169    _handle(ERROR, object, cat, format, args)
170
171    # we do the import here because having it globally causes weird import
172    # errors if our gstreactor also imports .log, which brings in errors
173    # and pb stuff
174    if args:
175        raise SystemError(format % args)
176    else:
177        raise SystemError(format)
178
179def warningObject(object, cat, format, *args):
180    """
181    Log a warning message in the given category.
182    This is used for non-fatal problems.
183    """
184    _handle(WARN, object, cat, format, args)
185
186def infoObject(object, cat, format, *args):
187    """
188    Log an informational message in the given category.
189    """
190    _handle(INFO, object, cat, format, args)
191
192def debugObject(object, cat, format, *args):
193    """
194    Log a debug message in the given category.
195    """
196    _handle(DEBUG, object, cat, format, args)
197
198def logObject(object, cat, format, *args):
199    """
200    Log a log message.  Used for debugging recurring events.
201    """
202    _handle(LOG, object, cat, format, args)
203
204def error(cat, format, *args):
205    errorObject(None, cat, format, *args)
206
207def warning(cat, format, *args):
208    warningObject(None, cat, format, *args)
209
210def info(cat, format, *args):
211    infoObject(None, cat, format, *args)
212
213def debug(cat, format, *args):
214    debugObject(None, cat, format, *args)
215
216def log(cat, format, *args):
217    logObject(None, cat, format, *args)
218
219class Loggable:
220    """
221    Base class for objects that want to be able to log messages with
222    different level of severity.  The levels are, in order from least
223    to most: log, debug, info, warning, error.
224
225    @cvar logCategory: Implementors can provide a category to log their
226       messages under.
227    """
228
229    logCategory = 'default'
230   
231    def error(self, *args):
232        """Log an error.  By default this will also raise an exception."""
233        if _canShortcutLogging(self.logCategory, ERROR):
234            return
235        errorObject(self.logObjectName(), self.logCategory,
236            *self.logFunction(*args))
237       
238    def warning(self, *args):
239        """Log a warning.  Used for non-fatal problems."""
240        if _canShortcutLogging(self.logCategory, WARN):
241            return
242        warningObject(self.logObjectName(), self.logCategory,
243            *self.logFunction(*args))
244       
245    def info(self, *args):
246        """Log an informational message.  Used for normal operation."""
247        if _canShortcutLogging(self.logCategory, INFO):
248            return
249        infoObject(self.logObjectName(), self.logCategory,
250            *self.logFunction(*args))
251
252    def debug(self, *args):
253        """Log a debug message.  Used for debugging."""
254        if _canShortcutLogging(self.logCategory, DEBUG):
255            return
256        debugObject(self.logObjectName(), self.logCategory,
257            *self.logFunction(*args))
258
259    def log(self, *args):
260        """Log a log message.  Used for debugging recurring events."""
261        if _canShortcutLogging(self.logCategory, LOG):
262            return
263        logObject(self.logObjectName(), self.logCategory,
264            *self.logFunction(*args))
265
266    def logFunction(self, *args):
267        """Overridable log function.  Default just returns passed message."""
268        return args
269
270    def logObjectName(self):
271        """Overridable object name function."""
272        # cheat pychecker
273        for name in ['logName', 'name']:
274            if hasattr(self, name):
275                return getattr(self, name)
276
277        return None
278
279def stderrHandler(level, object, category, file, line, message):
280    """
281    A log handler that writes to stdout.
282
283    @type level:    string
284    @type object:   string (or None)
285    @type category: string
286    @type message:  string
287    """
288
289    o = ""
290    if object:
291        o = '"' + object + '"'
292
293    where = "(%s:%d)" % (file, line)
294
295    try:
296        # level   pid     object   cat      time
297        # 5 + 1 + 7 + 1 + 32 + 1 + 17 + 1 + 15 == 80
298        sys.stderr.write('%-5s [%5d] %-32s %-17s %-15s ' % (
299            getLevelName(level), os.getpid(), o, category,
300            time.strftime("%b %d %H:%M:%S")))
301        sys.stderr.write('%-4s %s %s\n' % ("", message, where))
302
303        # old: 5 + 1 + 20 + 1 + 12 + 1 + 32 + 1 + 7 == 80
304        #sys.stderr.write('%-5s %-20s %-12s %-32s [%5d] %-4s %-15s %s\n' % (
305        #    level, o, category, where, os.getpid(),
306        #    "", time.strftime("%b %d %H:%M:%S"), message))
307        sys.stderr.flush()
308    except IOError:
309        # happens in SIGCHLDHandler for example
310        pass
311
312def addLogHandler(func, limited=True):
313    """
314    Add a custom log handler.
315
316    @param func:    a function object
317                    with prototype (level, object, category, message)
318                    where level is either ERROR, WARN, INFO, DEBUG, or
319                    LOG, and the rest of the arguments are strings or
320                    None. Use getLevelName(level) to get a printable
321                    name for the log level.
322    @type func:     a callable function
323    @type limited:  boolean
324    @param limited: whether to automatically filter based on FLU_DEBUG
325    """
326
327    if not callable(func):
328        raise TypeError, "func must be callable"
329
330    if limited:
331        _log_handlers_limited.append(func)
332    else:
333        _log_handlers.append(func)
334
335def init():
336    """
337    Initialize the logging system and parse the MOAP_DEBUG environment
338    variable.  Needs to be called before starting the actual application.
339    """
340    global _initialized
341
342    if _initialized:
343        return
344
345    if os.environ.has_key('MOAP_DEBUG'):
346        # install a log handler that uses the value of MOAP_DEBUG
347        setSavonDebug(os.environ['MOAP_DEBUG'])
348    addLogHandler(stderrHandler, limited=True)
349
350    _initialized = True
351
352def reset():
353    """
354    Resets the logging system, removing all log handlers.
355    """
356    global _log_handlers, _log_handlers_limited, _initialized
357   
358    _log_handlers = []
359    _log_handlers_limited = []
360    _initialized = False
361   
362def setSavonDebug(string):
363    """Set the MOAP_DEBUG string.  This controls the log output."""
364    global _MOAP_DEBUG
365    global _categories
366   
367    _MOAP_DEBUG = string
368    debug('log', "MOAP_DEBUG set to %s" % _MOAP_DEBUG)
369
370    # reparse all already registered category levels
371    for category in _categories:
372        registerCategory(category)
373
374def getExceptionMessage(exception, frame=-1, filename=None):
375    """
376    Return a short message based on an exception, useful for debugging.
377    Tries to find where the exception was triggered.
378    """
379    stack = traceback.extract_tb(sys.exc_info()[2])
380    if filename:
381        stack = [f for f in stack if f[0].find(filename) > -1]
382    #import code; code.interact(local=locals())
383    (filename, line, func, text) = stack[frame]
384    filename = scrubFilename(filename)
385    exc = exception.__class__.__name__
386    msg = ""
387    # a shortcut to extract a useful message out of most flumotion errors
388    # for now
389    if len(exception.args) == 1 and isinstance(exception.args[0], str):
390        msg = ": %s" % exception.args[0]
391    return "exception %(exc)s at %(filename)s:%(line)s: %(func)s()%(msg)s" % locals()
Note: See TracBrowser for help on using the repository browser.