# GeneNetwork logger
#
# The standard python logging module is very good. This logger adds a
# few facilities on top of that. Main one being that it picks up
# settings for log levels (global and by module) and (potentially)
# offers some fine grained log levels for the standard levels.
#
# All behaviour is defined here.  Global settings (defined in
# default_settings.py).
#
# To use logging and settings put this at the top of a module:
#
#   import utility.logger
#   logger = utility.logger.getLogger(__name__ )
#
# To override global behaviour set the LOG_LEVEL in default_settings.py
# or use an environment variable, e.g.
#
#    env LOG_LEVEL=INFO ./bin/genenetwork2
#
# To override log level for a module replace that with, for example,
#
#   import logging
#   import utility.logger
#   logger = utility.logger.getLogger(__name__,level=logging.DEBUG)
#
# We'll add more overrides soon.

import logging
import string
from inspect import isfunction
from pprint import pformat as pf
from inspect import stack
import datetime

from utility.tools import LOG_LEVEL, LOG_LEVEL_DEBUG, LOG_SQL

class GNLogger:
    """A logger class with some additional functionality, such as
    multiple parameter logging, SQL logging, timing, colors, and lazy
    functions.

    """

    def __init__(self, name):
        self.logger = logging.getLogger(name)

    def setLevel(self, value):
        """Set the undelying log level"""
        self.logger.setLevel(value)

    def debug(self,*args):
        """Call logging.debug for multiple args. Use (lazy) debugf and
level=num to filter on LOG_LEVEL_DEBUG.

        """
        self.collect(self.logger.debug, *args)

    def debug20(self,*args):
        """Call logging.debug for multiple args. Use level=num to filter on
LOG_LEVEL_DEBUG (NYI).

        """
        if level <= LOG_LEVEL_DEBUG:
            if self.logger.getEffectiveLevel() < 20:
                self.collect(self.logger.debug, *args)

    def info(self,*args):
        """Call logging.info for multiple args"""
        self.collect(self.logger.info, *args)

    def warning(self,*args):
        """Call logging.warning for multiple args"""
        self.collect(self.logger.warning, *args)
        # self.logger.warning(self.collect(*args))

    def error(self,*args):
        """Call logging.error for multiple args"""
        now = datetime.datetime.utcnow()
        time_str = now.strftime('%H:%M:%S UTC %Y%m%d')
        l = [time_str]+list(args)
        self.collect(self.logger.error, *l)

    def infof(self,*args):
        """Call logging.info for multiple args lazily"""
        # only evaluate function when logging
        if self.logger.getEffectiveLevel() < 30:
            self.collectf(self.logger.debug, *args)

    def debugf(self,level=0,*args):
        """Call logging.debug for multiple args lazily and handle
        LOG_LEVEL_DEBUG correctly

        """
        # only evaluate function when logging
        if level <= LOG_LEVEL_DEBUG:
            if self.logger.getEffectiveLevel() < 20:
                self.collectf(self.logger.debug, *args)

    def sql(self, sqlcommand, fun = None):
        """Log SQL command, optionally invoking a timed fun"""
        if LOG_SQL:
            caller = stack()[1][3]
            if caller in ['fetchone', 'fetch1', 'fetchall']:
                caller = stack()[2][3]
            self.info(caller, sqlcommand)
        if fun:
            result = fun(sqlcommand)
            if LOG_SQL:
                self.info(result)
            return result

    def collect(self,fun,*args):
        """Collect arguments and use fun to output"""
        out = "."+stack()[2][3]
        for a in args:
            if len(out)>1:
                out += ": "
            if isinstance(a, str):
                out = out + a
            else:
                out = out + pf(a, width=160)
        fun(out)

    def collectf(self,fun,*args):
        """Collect arguments and use fun to output one by one"""
        out = "."+stack()[2][3]
        for a in args:
            if len(out)>1:
                out += ": "
                if isfunction(a):
                    out += a()
                else:
                    if isinstance(a, str):
                        out = out + a
                    else:
                        out = out + pf(a, width=160)
        fun(out)

# Get the module logger. You can override log levels at the
# module level
def getLogger(name, level = None):
    gnlogger = GNLogger(name)
    logger = gnlogger.logger

    if level:
        logger.setLevel(level)
    else:
        logger.setLevel(LOG_LEVEL)

    logger.info("Log level of "+name+" set to "+logging.getLevelName(logger.getEffectiveLevel()))
    return gnlogger