Re: problems with logging

Steven Armstrong <[email protected]>
Newsgroups gmane.comp.web.pyblosxom.devel
Message-ID <[email protected]>
ups ...
just learned that testing _all_ changes before sending patches would be
a good idea.

so, once again ...

cheers
Steven
logging.diff (text/plain, 14.5 KB)
Index: Pyblosxom/pyblosxom.py
===================================================================
RCS file: /cvsroot/pyblosxom/pyblosxom/Pyblosxom/pyblosxom.py,v
retrieving revision 1.55
diff -u -r1.55 pyblosxom.py
--- Pyblosxom/pyblosxom.py	1 Feb 2005 01:07:26 -0000	1.55
+++ Pyblosxom/pyblosxom.py	5 Feb 2005 00:41:31 -0000
@@ -56,6 +56,9 @@
         pyhttp = self._request.getHttp()
         config = self._request.getConfiguration()
 
+        # initialize the tools module
+        tools.initialize(config)
+
         data["pyblosxom_version"] = VERSION_DATE
         data['pi_bl'] = ''
 
@@ -83,6 +86,15 @@
                                         mappingfunc=lambda x,y:y,
                                         defaultfunc=lambda x:x)
 
+    def cleanup(self):
+        """
+        Cleanup everything.
+        This should be called when Pyblosxom has done all its work.
+        Right before exiting.
+        """
+        tools.cleanup()
+
+
     def getRequest(self):
         """
         Returns the L{Request} object.
@@ -129,6 +141,8 @@
 
         # do end callback
         tools.run_callback("end", {'request': self._request})
+        
+        self.cleanup()
 
 
     def runCallback(self, callback="help"):
@@ -298,6 +312,8 @@
             print "rendering '%s' ..." % url
             tools.render_url(config, url, q)
 
+        self.cleanup()
+
 
     def testInstallation(self):
         test_installation(self._request)
@@ -706,11 +722,20 @@
                      'request': request})
         else:
             renderer.addHeader('Status', '404 Not Found')
-            renderer.setContent(
-                {'title': 'The page you are looking for is not available',
-                 'body': 'Somehow I cannot find the page you want. ' + 
-                 'Go Back to <a href="%s">%s</a>?' 
-                 % (config["base_url"], config["blog_title"])})
+            # use a custom 404-story template.
+            # it is sort of silly to have a 404 message with permalink, trackback, comment etc.
+            from Pyblosxom import entries
+            entry = entries.base.EntryBase(request)
+            entry["title"] = 'The page you are looking for is not available'
+            entry["body"] = """Somehow I cannot find the page you want. 
+                 Go Back to <a href="%s">%s</a>?""" % (config["base_url"], config["blog_title"])
+            entry["template_name"] = "404-story"
+            renderer.setContent([entry])
+#            renderer.setContent(
+#                {'title': 'The page you are looking for is not available',
+#                 'body': 'Somehow I cannot find the page you want. ' + 
+#                 'Go Back to <a href="%s">%s</a>?' 
+#                 % (config["base_url"], config["blog_title"])})
             # Log it as failure
             tools.run_callback("logrequest", 
                     {'filename':config.get('logfile',''), 
Index: Pyblosxom/tools.py
===================================================================
RCS file: /cvsroot/pyblosxom/pyblosxom/Pyblosxom/tools.py,v
retrieving revision 1.39
diff -u -r1.39 tools.py
--- Pyblosxom/tools.py	1 Feb 2005 01:07:26 -0000	1.39
+++ Pyblosxom/tools.py	5 Feb 2005 00:41:33 -0000
@@ -8,9 +8,14 @@
 @var num2month: A dict of number month format to its literal format
 @var MONTHS: A list of valid literal and numeral months
 @var VAR_REGEXP: Regular expression for detection and substituion of variables
+@var _logger_registry: A dict of created loggers. Prevents multiple loggers/handlers using the same file.
+@var _logdir: The directory in which logfiles are created by default
+@var _config: A reference to the pyblosxom config dict.
 """
 import plugin_utils
-import sgmllib, re, os, string, types, time, os.path, StringIO, sys
+import sgmllib, re, os, string, types, time, os.path, sys
+try: from cStringIO import StringIO
+except ImportError: from StringIO import StringIO
 
 month2num = { 'nil' : '00',
               'Jan' : '01',
@@ -39,6 +44,33 @@
 # regular expression for detection and substituion of variables.
 VAR_REGEXP = re.compile(ur'(?<!\\)\$((?:\w|\-|::\w)+(?:\(.*?(?<!\\)\))?)')
 
+# see module docstring for infos
+_logger_registry = {}
+_logdir = "/tmp"
+_config = None
+
+def initialize(config):
+    """
+    Initialize the tools module.
+    This gives the module a chance to use the pyblosxom config.py file
+    without having to change sys.path and importing it manually.
+    This should be called from Pyblosxom.pyblosxom.PyBlosxom.initialize.
+    """
+    global _logdir, _config
+    _logdir = config.get('logdir', _logdir)
+    _config = config
+
+def cleanup():
+    """
+    Cleanup the tools module.
+    This should be called from Pyblosxom.pyblosxom.PyBlosxom.cleanup.
+    """
+    # close log files
+    for l in _logger_registry:
+        if hasattr(_logger_registry[l], 'cleanup'):
+            _logger_registry[l].cleanup()
+
+
 class Stripper(sgmllib.SGMLParser):
     """
     Strips HTML
@@ -488,51 +520,6 @@
 
     return mycache
 
-_logger_registry = {}
-def make_logger(filename):
-    """
-    Create a logging function called log, which logs to the supplied filename
-    usage is:
-
-    -->>> tools.make_logger('/tmp/pybloxom.log')
-    -->>> tools.log('log message')
-
-    @param filename: the name of a file to log to
-    @type filename: string
-    """
-    global log
-    try:
-        import logging
-    except ImportError:    
-        def log(*args):
-            f = open(filename, "a")
-            for i in args:
-                f.write("%s INFO %s" % (time.asctime(), repr(i)))
-            f.write("\n")
-            f.close()
-    else:
-        global _logger_registry
-        # if all loggers have the same name,
-        # everything is logged to all files.
-        logger_name = os.path.splitext(os.path.basename(filename))[0]
-        # only add one handler per logger
-        if not logger_name in _logger_registry:
-            _logger = logging.getLogger(logger_name)
-            hdlr = logging.FileHandler(filename)
-            formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
-            hdlr.setFormatter(formatter)
-            _logger.addHandler(hdlr)
-            _logger.setLevel(logging.INFO)
-            _logger_registry[logger_name] = _logger
-
-        logger = _logger_registry[logger_name]
-
-        def log(*args):
-            # adjusted to match the 'manual' log func
-            for i in args:
-                logger.info(repr(i))
-
-
 def update_static_entry(cdict, entry_filename):
     """
     This is a utility function that allows plugins to easily update
@@ -620,6 +607,261 @@
     f.write(response.read())
     f.close()
 
+
+#******************************
+# Logging
+#******************************
+
+class _Logger:
+    """
+    Emulates some functionality of the logging module for older Python installations.
+    """
+    CRITICAL = 50
+    FATAL = CRITICAL
+    ERROR = 40
+    WARNING = 30
+    WARN = WARNING
+    INFO = 20
+    DEBUG = 10
+    NOTSET = 0
+    
+    # order matters!
+    _names = ['FATAL', 'CRITICAL', 'ERROR', 'WARN', 'WARNING', 'INFO', 'DEBUG', 'NOTSET']
+
+    # populated in __init__
+    _levels = {}
+    _level_names = {}
+
+    def __init__(self, filename, level):
+        self._populate_levels()
+        self.filename = filename
+        self.setLevel(level)
+        self.format = "%Y-%m-%d %H:%M:%S"
+        self._file = None
+
+    def _populate_levels(self):
+        for n in self._names:
+            self._levels[n] = getattr(self, n)
+            self._level_names[getattr(self, n)] = n
+
+    def _getLevel(self, level):
+        """
+        Return the numeric representation of logging level 'level'.
+        Defaults to 0 (self.NOTSET).
+        """
+        return self._levels.get(level.upper(), self.NOTSET)
+
+    def _getMessage(self, msg, args):
+        """
+        Return the message for this log record after merging any user-supplied
+        arguments with the message.
+        """
+        import types
+        if not hasattr(types, "UnicodeType"): #if no unicode support...
+            msg = str(msg)
+        else:
+            try:
+                msg = str(msg)
+            except UnicodeError:
+                pass
+        if args:
+            msg = msg % args
+        return msg
+
+    def _getExcInfo(self):
+        import traceback
+        (exc_type, exc_value, tb) = sys.exc_info()
+        exc_file = StringIO()
+        traceback.print_exception(exc_type, exc_value, tb, file=exc_file)
+        exc_string = exc_file.getvalue()
+        return exc_string
+
+    def _log(self, level, msg, args, exc_info=None):
+        if self.level <= level:
+            ct = time.time()
+            msecs = (ct - long(ct)) * 1000
+            time_string = "%s,%03d" % (time.strftime(self.format, time.localtime(ct)), msecs)
+            level = self.getLevelName(level)
+            msg = self._getMessage(msg, args)
+
+            if self._file == None or self._file.closed:
+                self._file = open(self.filename, "a")
+            self._file.write("%s %s %s\n" % (time_string, level, msg))
+            if exc_info: self._file.write(self._getExcInfo())
+
+    def cleanup(self):
+        """
+        Closes open log files.
+        """
+        if self._file != None:
+            self._file.close()
+
+    # public methods
+
+    def getLevelName(self, level):
+        """
+        Return the textual representation of logging level 'level'.
+    
+        If the level is one of the predefined levels (CRITICAL, ERROR, WARNING,
+        INFO, DEBUG) then you get the corresponding string.
+        Otherwise, the string "Level %s" % level is returned.
+        """
+        return self._level_names.get(level, ("Level %s" % level))
+
+    def setLevel(self, level):
+        if isinstance(level, int):
+            self.level = level
+        else:
+            self.level = self._getLevel(level.upper())
+
+    def critical(self, msg, *args, **kwargs):
+        apply(self._log, (self.CRITICAL, msg, args), kwargs)
+    fatal = critical
+
+    def error(self, msg, *args, **kwargs):
+        apply(self._log, (self.ERROR, msg, args), kwargs)
+
+    def warning(self, msg, *args, **kwargs):
+        apply(self._log, (self.WARNING, msg, args), kwargs)
+    warn = warning
+
+    def info(self, msg, *args, **kwargs):
+        apply(self._log, (self.INFO, msg, args), kwargs)
+
+    def debug(self, msg, *args, **kwargs):
+        apply(self._log, (self.DEBUG, msg, args), kwargs)
+
+def getLogger(logname="pyblosxom", level="info", filename=None):
+    """
+    log = tools.getLogger("logname", "info")
+    # this creates a logger that logs to 'logdir'/logname.log
+    log.info("We %s a %s", "have", "good time")
+    log.debug("We %s a %s", "are testing", "new function")
+    log.setLevel(log.ERROR)
+    log.error("We %s a %s", "have", "mysterious problem", exc_info=True)
+
+    or:
+    log = tools.getLogger(filename="/path/to/logfile", level="info")
+
+    @param logname: the name of this logger. used to figure out the logfile
+    @type  logname: string
+
+    @param level: the default loglevel
+    @type  level: string
+
+    @param filename: the file to log to
+    @type  filename: string
+    """
+    if filename:
+        logname = os.path.splitext(os.path.basename(filename))[0]
+    else:
+        filename = "%s%s%s.log" % (_logdir, os.sep, logname)
+        filename = os.path.normpath(filename)
+
+    global _logger_registry
+    try:
+        import logging
+        #raise ImportError, "whatever"
+        if not filename in _logger_registry:
+            int_level = getattr(logging, level.upper())
+            _logger = logging.getLogger(logname)
+            hdlr = logging.FileHandler(filename)
+            hdlr.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s'))
+            _logger.addHandler(hdlr)
+            _logger.setLevel(int_level)
+            for l in ['CRITICAL', 'FATAL', 'ERROR', 'WARNING', 'WARN', 'INFO', 'DEBUG', 'getLevelName']:
+                setattr(_logger, l, getattr(logging, l))
+            _logger_registry[filename] = _logger
+    except ImportError:
+        if not filename in _logger_registry:
+            _logger_registry[filename] = _Logger(filename, level)
+
+    # update loglevel if necessary
+    logger = _logger_registry[filename]
+    new_level = getattr(logger, level.upper())
+    if new_level < logger.level:
+        logger.setLevel(new_level)
+    return logger
+
+def log_exception(logname="error", level="error", filename=None):
+    """
+    Logs a exception. By default to a file named error.log in 
+    your 'logdir' directory. Uses /tmp if 'logdir' is not set.
+    You can also pass your own logfile. See below.
+
+    Usage:
+        try:
+            whatever
+        except:
+            import tools
+            tools.log_exception()
+            # or:
+            #tools.log_exception(filename="/path/to/logfile")
+
+    @param logname: the name of this logger. used to figure out the logfile
+    @type  logname: string
+
+    @param level: the default loglevel
+    @type  level: string
+
+    @param filename: the file to log to
+    @type  filename: string
+    """
+    log = getLogger(logname, level, filename)
+    log.error("Exception occured:", exc_info=True)
+
+def log_frame(_log, num):
+    """
+    Logs some info about the calling function/method.
+    Usefull for debugging.
+    Usage:
+        import tools
+        log = tools.getLogger(filename="/path/to/logfile")
+        tool.log_frame(log, 3)
+        tool.log_frame(log, 2)
+        tool.log_frame(log, 1)
+
+    @param _log: instance of a logger
+    @type  _log: L{_Logger} or logging module logger
+
+    @param num: index of the frame
+    @type  num: int
+    """
+    f = sys._getframe(num)
+    module = f.f_globals["__name__"]
+    filename = f.f_code.co_filename
+    line = f.f_lineno
+    subr = f.f_code.co_name
+    _log.info("module: %s\nfilename: %s\nline: %s\nsubroutine: %s", 
+        module, filename, line, subr)
+
+
+def make_logger(filename):
+    """
+    DEPRECATED
+    Use this instead:
+    -->>> log = tools.getLogger(filename='/tmp/pybloxom.log')
+    -->>> log.info('log message')
+    -->>> log.error('the programm made %s', 'a bubu')
+    ----------
+    
+    Create a logging function called log, which logs to the supplied filename
+    usage is:
+
+    -->>> tools.make_logger('/tmp/pybloxom.log')
+    -->>> tools.log('log message')
+
+    @param filename: the name of a file to log to
+    @type filename: string
+    """
+    global log
+
+    _logger = getLogger(filename=filename)
+
+    def log(*args):
+        for i in args:
+            _logger.info(repr(i))
+
  
 # %<-------------------------
 # BEGIN portalocking block from Python Cookbook.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.