# 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, 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)


_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()
        reserved = self.reserved; omitname = self.omitname; levels = self.levels

        for k in ( list([i for i in reserved if i in self]) + 
            list([i for i in self.iterkeys() if i not in reserved])
        ):
            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
    """

    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:
        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.
    '''
    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)


