changeset 991:05e02f8b7eb4

core: logging refactoring, first step: - added a core.log module - 3 backends can be used: basic, standard (python's logging module) or twisted - colors can be used - the module has been made to be used by frontends, it should work in exotic environments like pyjamas - logging basic configuration is now made in sat.tac - core.log configuration is inspired from python standard logging, and use it when possible - getLogger should be used the same way as for standard logging
author Goffi <goffi@goffi.org>
date Sat, 19 Apr 2014 00:02:38 +0200
parents f0e407709d8e
children f51a1895275c
files src/core/constants.py src/core/log.py src/core/sat_main.py src/sat.tac
diffstat 4 files changed, 188 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/src/core/constants.py	Fri Apr 18 23:36:52 2014 +0200
+++ b/src/core/constants.py	Sat Apr 19 00:02:38 2014 +0200
@@ -72,6 +72,12 @@
     # names of widely used plugins
     TEXT_CMDS = 'TEXT-COMMANDS'
 
+    ## Logging ##
+    LOG_BACKEND_STANDARD = 'standard'
+    LOG_BACKEND_TWISTED = 'twisted'
+    LOG_BACKEND_BASIC = 'basic'
+    LOG_BASE_LOGGER = 'root'
+
     ## Misc ##
     SAVEFILE_DATABASE = "sat.db"
     IQ_SET = '/iq[@type="set"]'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/core/log.py	Sat Apr 19 00:02:38 2014 +0200
@@ -0,0 +1,178 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# SàT: a XMPP client
+# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""High level logging functions"""
+# XXX: this module use standard logging module when possible, but as SàT can work in different cases where logging is not the best choice (twisted, pyjamas, etc), it is necessary to have a dedicated module. In addition additional feature like environment variable and color are also managed.
+
+from sat.core.constants import Const as C
+from sat.core import exceptions
+
+_backend = None
+_loggers = {}
+
+
+class Logger(object):
+    """ High level logging class """
+
+    def __init__(self, name):
+        self._name = name
+
+    def debug(self, msg):
+        print msg
+
+    def info(self, msg):
+        print msg
+
+    def warning(self, msg):
+        print msg
+
+    def error(self, msg):
+        print msg
+
+    def critical(self, msg):
+        print msg
+
+
+def _configureStdLogging(logging, level=None, colors=False, force_colors=False):
+    """Configure standard logging module
+    @param logging: standard logging module
+    @param colors: if True use ANSI colors to show log levels
+    @param force_colors: if True ANSI colors are used even if stdout is not a tty
+    """
+    FORMAT = '%(message)s'
+    if level is None:
+        level = logging.DEBUG
+    import sys
+    with_color = colors & (sys.stdout.isatty() or force_colors)
+    if not colors and force_colors:
+        raise ValueError("force_colors can't be used if colors is False")
+
+    class SatFormatter(logging.Formatter):
+        u"""Formatter which manage SàT specificities"""
+
+        def __init__(self, fmt=None, datefmt=None):
+            super(SatFormatter, self).__init__(fmt, datefmt)
+
+        def format(self, record):
+            s = super(SatFormatter, self).format(record)
+            if with_color:
+                if record.levelno == logging.DEBUG:
+                    fmt = (C.ANSI_FG_CYAN, s, C.ANSI_RESET)
+                elif record.levelno == logging.WARNING:
+                    fmt = (C.ANSI_FG_YELLOW, s, C.ANSI_RESET)
+                elif record.levelno == logging.ERROR:
+                    fmt = (C.ANSI_FG_RED,
+                           C.ANSI_BLINK,
+                           r'/!\ ',
+                           C.ANSI_BLINK_OFF,
+                           s,
+                           C.ANSI_RESET)
+                elif record.levelno == logging.CRITICAL:
+                    fmt = (C.ANSI_BOLD,
+                           C.ANSI_FG_RED,
+                           'Guru Meditation ',
+                           C.ANSI_NORMAL_WEIGHT,
+                           s,
+                           C.ANSI_RESET)
+                else:
+                    fmt = s
+                s = ''.join(fmt)
+
+            return s
+
+
+    root_logger = logging.getLogger()
+    if len(root_logger.handlers) == 0:
+        hdlr = logging.StreamHandler()
+        formatter = SatFormatter(FORMAT)
+        hdlr.setFormatter(formatter)
+        root_logger.addHandler(hdlr)
+        root_logger.setLevel(level)
+    else:
+        root_logger.warning(u"Handler already set on root logger")
+
+def configure(backend=C.LOG_BACKEND_STANDARD):
+    """Configure logging bejaviour
+    @param backend: can be:
+        C.LOG_BACKEND_STANDARD: use standard logging module
+        C.LOG_BACKEND_TWISTED: use twisted logging module (with standard logging observer)
+        C.LOG_BACKEND_BASIC: use a basic print based logging
+    """
+    global _backend
+    if _backend is not None:
+        raise exceptions.InternalError("Logging can only be configured once")
+    _backend = backend
+
+    if backend ==  C.LOG_BACKEND_TWISTED:
+        from twisted.python import log
+        import logging
+        _configureStdLogging(logging, colors=True)
+        def logMsg(self, msg, level):
+            log.msg(msg.encode('utf-8'), logLevel=level)
+        Logger.logMsg = logMsg
+        Logger.debug = lambda self, msg: self.logMsg(msg, logging.DEBUG)
+        Logger.info = lambda self, msg: self.logMsg(msg, logging.INFO)
+        Logger.warning = lambda self, msg: self.logMsg(msg, logging.WARNING)
+        Logger.error = lambda self, msg: self.logMsg(msg, logging.ERROR)
+        Logger.critical = lambda self, msg: self.logMsg(msg, logging.CRITICAL)
+        observer = log.PythonLoggingObserver()
+        observer.start()
+
+    elif backend == C.LOG_BACKEND_STANDARD:
+        global getLogger
+        global debug
+        global info
+        global warning
+        global error
+        global critical
+        import logging
+        _configureStdLogging(logging, colors=True)
+        getLogger = logging.getLogger
+        debug = logging.debug
+        info = logging.info
+        warning = logging.warning
+        error = logging.error
+        critical = logging.critical
+
+    elif backend == C.LOG_BACKEND_BASIC:
+        pass
+
+    else:
+        raise ValueError("unknown backend")
+
+
+def getLogger(name=C.LOG_BASE_LOGGER):
+    return _loggers.setdefault(name, Logger(name))
+
+_root_logger = getLogger()
+
+def debug(msg):
+    _root_logger.debug(msg)
+
+def info(msg):
+    _root_logger.info(msg)
+
+def warning(msg):
+    _root_logger.warning(msg)
+
+def error(msg):
+    _root_logger.error(msg)
+
+def critical(msg):
+    _root_logger.critical(msg)
--- a/src/core/sat_main.py	Fri Apr 18 23:36:52 2014 +0200
+++ b/src/core/sat_main.py	Sat Apr 19 00:02:38 2014 +0200
@@ -20,14 +20,10 @@
 from sat.core.i18n import _, languageSwitch
 from twisted.application import service
 from twisted.internet import defer
-
 from twisted.words.protocols.jabber import jid, xmlstream
 from twisted.words.xish import domish
-
 from twisted.internet import reactor
-
 from wokkel.xmppim import RosterItem
-
 from sat.bridge.DBus import DBusBridge
 import logging
 from logging import debug, info, warning, error
@@ -49,10 +45,6 @@
 except ImportError:
     from ordereddict import OrderedDict
 
-### logging configuration FIXME: put this elsewhere ###
-logging.basicConfig(level=logging.DEBUG,
-                    format='%(message)s')
-###
 
 sat_id = 0
 
--- a/src/sat.tac	Fri Apr 18 23:36:52 2014 +0200
+++ b/src/sat.tac	Sat Apr 19 00:02:38 2014 +0200
@@ -21,6 +21,10 @@
 from twisted.internet import glib2reactor
 glib2reactor.install()
 
+from sat.core.constants import Const as C
+from sat.core import log
+log.configure(C.LOG_BACKEND_TWISTED)
+
 from sat.core.sat_main import SAT
 
 application = service.Application('SàT')