view src/core/log.py @ 994:652c01ca69b1

core (log): configuration and environment variables are now checked for log level and colors: - variable change logs behaviour, so far only level and colors are implemented - configuration use log_[name], for example you can put log_level=debug in sat.conf (section [DEFAULT]) to see all levels - environment variables use SAT_LOG_[NAME]: e.g. SAT_LOG_LEVEL=debug - colors can be true, false or force to force colors even if stdout is not a tty
author Goffi <goffi@goffi.org>
date Sat, 19 Apr 2014 20:11:23 +0200
parents f51a1895275c
children b4af31a8a4f2
line wrap: on
line source

#!/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, **options):
    """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 in (C.LOG_BACKEND_TWISTED, C.LOG_BACKEND_STANDARD):
        if backend ==  C.LOG_BACKEND_TWISTED:
            from twisted.python import log
            observer = log.PythonLoggingObserver()
            observer.start()
        global getLogger
        global debug
        global info
        global warning
        global error
        global critical
        import logging
        _configureStdLogging(logging, **options)
        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 _parseOptions(options):
    """Parse string options as given in conf or environment variable, and return expected python value

    @param options (dict): options with (key: name, value: string value)
    """
    if 'colors' in options:
        if options['colors'].lower() in ('1', 'true'):
            options['colors'] = True
        elif options['colors'] == 'force':
            options['colors'] = True
            options['force_colors'] = True
        else:
            options['colors'] = False
    if 'level' in options:
        level = options['level'].upper()
        if level not in ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'):
            level = 'INFO'
        options['level'] = level

def satConfigure(backend=C.LOG_BACKEND_TWISTED):
    """Configure logging system for SàT, can be used by frontends

    logs conf is read in SàT conf, then in environment variables. It must be done before Memory init
    """
    import ConfigParser
    import os
    to_get = (C.LOG_OPT_COLORS, C.LOG_OPT_LEVEL)
    log_conf = {}
    config = ConfigParser.SafeConfigParser()
    config.read(C.CONFIG_FILES)
    for opt_name, opt_default in to_get:
        try:
            log_conf[opt_name] = os.environ[''.join((C.ENV_PREFIX, C.LOG_OPT_PREFIX.upper(), opt_name.upper()))]
        except KeyError:
            try:
                log_conf[opt_name] = config.get('DEFAULT', C.LOG_OPT_PREFIX + opt_name)
            except ConfigParser.NoOptionError:
                log_conf[opt_name] = opt_default

    _parseOptions(log_conf)
    configure(backend, **log_conf)

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)