# Copyright (c) 2003, The Regents of the University of California,
# through Lawrence Berkeley National Laboratory (subject to receipt of
# any required approvals from the U.S. Dept. of Energy).  All rights
# reserved.
#
"""Logging"""
ident = "$Id$"
import os
import sys

WARN = 1
DEBUG = 2


class ILogger:
    '''Logger interface, by default this class
    will be used and logging calls are no-ops.
    '''
    level = 0

    def __init__(self, msg):
        return

    def warning(self, *args, **kw):
        return

    def debug(self, *args, **kw):
        return

    def error(self, *args, **kw):
        return

    def setLevel(cls, level):
        cls.level = level
    setLevel = classmethod(setLevel)

    debugOn = lambda self: self.level >= DEBUG
    warnOn = lambda self: self.level >= WARN


class BasicLogger(ILogger):
    last = ''

    def __init__(self, msg, out=sys.stdout):
        self.msg, self.out = msg, out

    def warning(self, msg, *args, **kw):
        if self.warnOn() is False:
            return
        if BasicLogger.last != self.msg:
            BasicLogger.last = self.msg
            print >>self, "---- ", self.msg, " ----"
        print >>self, "    %s  " % self.WARN,
        print >>self, msg % args
    WARN = '[WARN]'

    def debug(self, msg, *args, **kw):
        if self.debugOn() is False:
            return
        if BasicLogger.last != self.msg:
            BasicLogger.last = self.msg
            print >>self, "---- ", self.msg, " ----"
        print >>self, "    %s  " % self.DEBUG,
        print >>self, msg % args
    DEBUG = '[DEBUG]'

    def error(self, msg, *args, **kw):
        if BasicLogger.last != self.msg:
            BasicLogger.last = self.msg
            print >>self, "---- ", self.msg, " ----"
        print >>self, "    %s  " % self.ERROR,
        print >>self, msg % args
    ERROR = '[ERROR]'

    def write(self, *args):
        '''Write convenience function; writes strings.
        '''
        for s in args:
            self.out.write(s)
        event = ''.join(*args)


_LoggerClass = BasicLogger


class GridLogger(ILogger):
    def debug(self, msg, *args, **kw):
        kw['component'] = self.msg
        gridLog(event=msg % args, level='DEBUG', **kw)

    def warning(self, msg, *args, **kw):
        kw['component'] = self.msg
        gridLog(event=msg % args, level='WARNING', **kw)

    def error(self, msg, *args, **kw):
        kw['component'] = self.msg
        gridLog(event=msg % args, level='ERROR', **kw)


#
# Registry of send functions for gridLog
#
GLRegistry = {}


class GLRecord(dict):
    """Grid Logging Best Practices Record, Distributed Logging Utilities

    The following names are reserved:

    event -- log event name
        Below is EBNF for the event name part of a log message.
            name        = <nodot> ( "." <name> )?
            nodot       = {RFC3896-chars except "."}

        Suffixes:
            start: Immediately before the first action in a task.
            end: Immediately after the last action in a task (that succeeded).
            error: an error condition that does not correspond to an end event.

    ts -- timestamp
    level -- logging level (see levels below)
    status -- integer status code
    gid -- global grid identifier
    gid, cgid -- parent/child identifiers
    prog -- program name


    More info: http://www.cedps.net/wiki/index.php/LoggingBestPractices#Python

    reserved -- list of reserved names,
    omitname -- list of reserved names, output only values ('ts', 'event',)
    levels -- dict of levels and description
    """
    reserved = ('ts', 'event', 'level', 'status', 'gid', 'prog')
    omitname = ()
    levels = dict(FATAL='Component cannot continue, or system is unusable.',
        ALERT='Action must be taken immediately.',
        CRITICAL='Critical conditions (on the system).',
        ERROR='Errors in the component; not errors from elsewhere.',
        WARNING='Problems that are recovered from, usually.',
        NOTICE='Normal but significant condition.',
        INFO='Informational messages that would be useful to a deployer or administrator.',
        DEBUG='Lower level information concerning program logic decisions, internal state, etc.',
        TRACE='Finest granularity, similar to "stepping through" the component or system.',)

    def __init__(self, date=None, **kw):
        kw['ts'] = date or self.GLDate()
        kw['gid'] = kw.get('gid') or os.getpid()
        dict.__init__(self, kw)

    def __str__(self):
        """
        """
        from cStringIO import StringIO
        s = StringIO()
        n = " "
        reserved = self.reserved
        omitname = self.omitname
        levels = self.levels

        for k in (list(filter(lambda i: i in self, reserved)) +
            list(filter(lambda i: i not in reserved, self.keys()))
        ):
            v = self[k]
            if k in omitname:
                s.write("%s " % self.format[type(v)](v))
                continue

            if k == reserved[2] and v not in levels:
                pass

            s.write("%s=%s " % (k, self.format[type(v)](v)))

        s.write("\n")
        return s.getvalue()

    class GLDate(str):
        """Grid logging Date Format
        all timestamps should all be in the same time zone (UTC).
        Grid timestamp value format that is a highly readable variant of the ISO8601 time standard [1]:

        YYYY-MM-DDTHH:MM:SS.SSSSSSZ

        """
        def __new__(self, args=None):
            """args -- datetime (year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]])
            """
            import datetime
            args = args or datetime.datetime.utcnow()
            l = (args.year, args.month, args.day, args.hour, args.minute, args.second,
                 args.microsecond, args.tzinfo or 'Z')

            return str.__new__(self, "%04d-%02d-%02dT%02d:%02d:%02d.%06d%s" % l)

    format = {int: str, float: lambda x: "%lf" % x, long: str, str: lambda x: x,
        unicode: str, GLDate: str, }


def gridLog(**kw):
    """Send GLRecord, Distributed Logging Utilities
    If the scheme is passed as a keyword parameter
    the value is expected to be a callable function
    that takes 2 parameters: url, outputStr

    GRIDLOG_ON   -- turn grid logging on
    GRIDLOG_DEST -- provide URL destination
    """
    import os

    if not bool(int(os.environ.get('GRIDLOG_ON', 0))):
        return

    url = os.environ.get('GRIDLOG_DEST')
    if url is None:
        return

    ## NOTE: urlparse problem w/customized schemes
    try:
        scheme = url[:url.find('://')]
        send = GLRegistry[scheme]
        send(url, str(GLRecord(**kw)), )
    except Exception, ex:
        print >>sys.stderr, "*** gridLog failed -- %s" % (str(kw))


def sendUDP(url, outputStr):
    from socket import socket, AF_INET, SOCK_DGRAM
    idx1 = url.find('://') + 3
    idx2 = url.find('/', idx1)
    if idx2 < idx1:
        idx2 = len(url)
    netloc = url[idx1:idx2]
    host, port = (netloc.split(':') + [80])[0:2]
    socket(AF_INET, SOCK_DGRAM).sendto(outputStr, (host, int(port)), )


def writeToFile(url, outputStr):
    print >> open(url.split('://')[1], 'a+'), outputStr

GLRegistry["gridlog-udp"] = sendUDP
GLRegistry["file"] = writeToFile


def setBasicLogger():
    '''Use Basic Logger.
    '''
    setLoggerClass(BasicLogger)
    BasicLogger.setLevel(0)


def setGridLogger():
    '''Use GridLogger for all logging events.
    '''
    setLoggerClass(GridLogger)


def setBasicLoggerWARN():
    '''Use Basic Logger.
    '''
    setLoggerClass(BasicLogger)
    BasicLogger.setLevel(WARN)


def setBasicLoggerDEBUG():
    '''Use Basic Logger.
    '''
    setLoggerClass(BasicLogger)
    BasicLogger.setLevel(DEBUG)


def setLoggerClass(loggingClass):
    '''Set Logging Class.
    '''


def setLoggerClass(loggingClass):
    '''Set Logging Class.
    '''
    assert issubclass(loggingClass, ILogger), 'loggingClass must subclass ILogger'
    global _LoggerClass
    _LoggerClass = loggingClass


def setLevel(level=0):
    '''Set Global Logging Level.
    '''
    ILogger.level = level


def getLevel():
    return ILogger.level


def getLogger(msg):
    '''Return instance of Logging class.
    '''
    return _LoggerClass(msg)
