changeset 1940:3fdacba9da68

core (logs): log color location can now be specified with %(color_start)s and %(color_end)s
author Goffi <goffi@goffi.org>
date Mon, 18 Apr 2016 18:33:59 +0200
parents e68483c5a999
children befdcfc55569
files src/core/log.py src/core/log_config.py
diffstat 2 files changed, 40 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/src/core/log.py	Mon Apr 18 18:33:55 2016 +0200
+++ b/src/core/log.py	Mon Apr 18 18:33:59 2016 +0200
@@ -19,6 +19,7 @@
 
 """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. Additional feature like environment variables and colors are also managed.
+# TODO: change formatting from "%s" style to "{}" when moved to Python 3
 
 from sat.core.constants import Const as C
 from sat.core import exceptions
@@ -26,6 +27,8 @@
 backend = None
 _loggers = {}
 handlers = {}
+COLOR_START = '%(color_start)s'
+COLOR_END = '%(color_end)s'
 
 
 class Filtered(Exception):
@@ -154,6 +157,7 @@
 
 class ConfigureBase(object):
     LOGGER_CLASS = Logger
+    _color_location = False # True if color location is specified in fmt (with COLOR_START)
 
     def __init__(self, level=None, fmt=None, output=None, logger=None, colors=False, force_colors=False, backend_data=None):
         """Configure a backend
@@ -201,6 +205,11 @@
         if fmt is not None:
             if fmt != '%(message)s': # %(message)s is the same as None
                 Logger.fmt = fmt
+            if COLOR_START in fmt:
+                ConfigureBase._color_location = True
+                if fmt.find(COLOR_END,fmt.rfind(COLOR_START))<0:
+                   # color_start not followed by an end, we add it
+                    Logger.fmt += COLOR_END
 
     def configureOutput(self, output):
         if output is not None:
@@ -249,7 +258,7 @@
                 handlers[output] = None
             elif output == C.LOG_OPT_OUTPUT_FILE:
                 if not options:
-                    ValueError("%(handler)s output need a path as option" % {'handler': output})
+                    ValueError("{handler} output need a path as option" .format(handle=output))
                 handlers.setdefault(output, []).append(options)
                 options = None # option are parsed, we can empty them
             elif output == C.LOG_OPT_OUTPUT_MEMORY:
@@ -262,7 +271,7 @@
                 handlers[output] = limit
 
             if options: # we should not have unparsed options
-                raise ValueError(u"options [%(options)s] are not supported for %(handler)s output" % {'options': options, 'handler': output})
+                raise ValueError(u"options [{options}] are not supported for {handler} output".format(options=options, handler=output))
 
     @staticmethod
     def memoryGet(size=None):
@@ -272,8 +281,8 @@
         """
         raise NotImplementedError
 
-    @staticmethod
-    def ansiColors(level, message):
+    @classmethod
+    def ansiColors(cls, level, message):
         """Colorise message depending on level for terminals
 
         @param level: one of C.LOG_LEVELS
@@ -281,26 +290,28 @@
         @return: message with ANSI escape codes for coloration
         """
         if level == C.LOG_LVL_DEBUG:
-            out = (C.ANSI_FG_CYAN, message, C.ANSI_RESET)
+            start = (C.ANSI_FG_CYAN,)
+        elif level == C.LOG_LVL_INFO:
+            start = ()
         elif level == C.LOG_LVL_WARNING:
-            out = (C.ANSI_FG_YELLOW, message, C.ANSI_RESET)
+            start = (C.ANSI_FG_YELLOW,)
         elif level == C.LOG_LVL_ERROR:
-            out = (C.ANSI_FG_RED,
+            start = (C.ANSI_FG_RED,
                    C.ANSI_BLINK,
                    r'/!\ ',
-                   C.ANSI_BLINK_OFF,
-                   message,
-                   C.ANSI_RESET)
+                   C.ANSI_BLINK_OFF)
         elif level == C.LOG_LVL_CRITICAL:
-            out = (C.ANSI_BOLD,
+            start = (C.ANSI_BOLD,
                    C.ANSI_FG_RED,
                    'Guru Meditation ',
-                   C.ANSI_NORMAL_WEIGHT,
-                   message,
-                   C.ANSI_RESET)
+                   C.ANSI_NORMAL_WEIGHT)
         else:
-            out = message
-        return ''.join(out)
+            start = ()
+        if cls._color_location:
+            return message % {'color_start': ''.join(start),
+                              'color_end': C.ANSI_RESET}
+        else:
+            return '%s%s%s' % (''.join(start), message, C.ANSI_RESET)
 
     @staticmethod
     def getProfile():
@@ -334,7 +345,7 @@
     try:
         configure_class = configure_cls[backend]
     except KeyError:
-        raise ValueError("unknown backend [%s]" % backend)
+        raise ValueError("unknown backend [{}]".format(backend))
     if backend == C.LOG_BACKEND_CUSTOM:
         logger_class = options.pop('logger_class')
         configure_class(logger_class, **options)
@@ -350,7 +361,7 @@
     try:
         logger_class = configure_cls[backend].LOGGER_CLASS
     except KeyError:
-        raise ValueError("This method should not be called with backend [%s]" % backend)
+        raise ValueError("This method should not be called with backend [{}]".format(backend))
     return _loggers.setdefault(name, logger_class(name))
 
 _root_logger = getLogger()
--- a/src/core/log_config.py	Mon Apr 18 18:33:55 2016 +0200
+++ b/src/core/log_config.py	Mon Apr 18 18:33:59 2016 +0200
@@ -258,6 +258,7 @@
         self.level = level
 
     def configureFormat(self, fmt):
+        super(ConfigureStandard, self).configureFormat(fmt)
         import logging
 
         class SatFormatter(logging.Formatter):
@@ -272,8 +273,17 @@
             def format(self, record):
                 if self._with_profile:
                     record.profile = ConfigureStandard.getProfile()
+                do_color = self.with_colors and (self.can_colors or self.force_colors)
+                if ConfigureStandard._color_location:
+                    # we copy raw formatting strings for color_*
+                    # as formatting is handled in ansiColors in this case
+                    if do_color:
+                        record.color_start = log.COLOR_START
+                        record.color_end = log.COLOR_END
+                    else:
+                        record.color_start = record.color_end = ''
                 s = super(SatFormatter, self).format(record)
-                if self.with_colors and (self.can_colors or self.force_colors):
+                if do_color:
                     s = ConfigureStandard.ansiColors(record.levelname, s)
                 return s