"Logging functions for Mininet."

import logging
from logging import Logger
import types

# Create a new loglevel, 'CLI info', which enables a Mininet user to see only
# the output of the commands they execute, plus any errors or warnings.  This
# level is in between info and warning.  CLI info-level commands should not be
# printed during regression tests.
OUTPUT = 25

LEVELS = { 'debug': logging.DEBUG,
           'info': logging.INFO,
           'output': OUTPUT,
           'warning': logging.WARNING,
           'error': logging.ERROR,
           'critical': logging.CRITICAL }

# change this to logging.INFO to get printouts when running unit tests
LOGLEVELDEFAULT = OUTPUT

#default: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
LOGMSGFORMAT = '%(message)s'


# Modified from python2.5/__init__.py
class StreamHandlerNoNewline( logging.StreamHandler ):
    """StreamHandler that doesn't print newlines by default.
       Since StreamHandler automatically adds newlines, define a mod to more
       easily support interactive mode when we want it, or errors-only logging
       for running unit tests."""

    def emit( self, record ):
        """Emit a record.
           If a formatter is specified, it is used to format the record.
           The record is then written to the stream with a trailing newline
           [ N.B. this may be removed depending on feedback ]. If exception
           information is present, it is formatted using
           traceback.printException and appended to the stream."""
        try:
            msg = self.format( record )
            fs = '%s'  # was '%s\n'
            if not hasattr( types, 'UnicodeType' ):  # if no unicode support...
                self.stream.write( fs % msg )
            else:
                try:
                    self.stream.write( fs % msg )
                except UnicodeError:
                    self.stream.write( fs % msg.encode( 'UTF-8' ) )
            self.flush()
        except ( KeyboardInterrupt, SystemExit ):
            raise
        except:
            self.handleError( record )


class Singleton( type ):
    """Singleton pattern from Wikipedia
       See http://en.wikipedia.org/wiki/Singleton_Pattern

       Intended to be used as a __metaclass_ param, as shown for the class
       below."""

    def __init__( cls, name, bases, dict_ ):
        super( Singleton, cls ).__init__( name, bases, dict_ )
        cls.instance = None

    def __call__( cls, *args, **kw ):
        if cls.instance is None:
            cls.instance = super( Singleton, cls ).__call__( *args, **kw )
        return cls.instance


class MininetLogger( Logger, object ):
    """Mininet-specific logger
       Enable each mininet .py file to with one import:

       from mininet.log import [lg, info, error]

       ...get a default logger that doesn't require one newline per logging
       call.

       Inherit from object to ensure that we have at least one new-style base
       class, and can then use the __metaclass__ directive, to prevent this
       error:

       TypeError: Error when calling the metaclass bases
       a new-style class can't have only classic bases

       If Python2.5/logging/__init__.py defined Filterer as a new-style class,
       via Filterer( object ): rather than Filterer, we wouldn't need this.

       Use singleton pattern to ensure only one logger is ever created."""

    __metaclass__ = Singleton

    def __init__( self ):

        Logger.__init__( self, "mininet" )

        # create console handler
        ch = StreamHandlerNoNewline()
        # create formatter
        formatter = logging.Formatter( LOGMSGFORMAT )
        # add formatter to ch
        ch.setFormatter( formatter )
        # add ch to lg
        self.addHandler( ch )

        self.setLogLevel()

    def setLogLevel( self, levelname=None ):
        """Setup loglevel.
           Convenience function to support lowercase names.
           levelName: level name from LEVELS"""
        level = LOGLEVELDEFAULT
        if levelname is not None:
            if levelname not in LEVELS:
                raise Exception( 'unknown levelname seen in setLogLevel' )
            else:
                level = LEVELS.get( levelname, level )

        self.setLevel( level )
        self.handlers[ 0 ].setLevel( level )

    # pylint: disable=method-hidden
    # "An attribute inherited from mininet.log hide this method" (sic)
    # Not sure why this is occurring - this function definitely gets called.

    # See /usr/lib/python2.5/logging/__init__.py; modified from warning()
    def output( self, msg, *args, **kwargs ):
        """Log 'msg % args' with severity 'OUTPUT'.

           To pass exception information, use the keyword argument exc_info
           with a true value, e.g.

           logger.warning("Houston, we have a %s", "cli output", exc_info=1)
        """
        if self.manager.disable >= OUTPUT:
            return
        if self.isEnabledFor( OUTPUT ):
            self._log( OUTPUT, msg, args, kwargs )

    # pylint: enable=method-hidden

lg = MininetLogger()

# Make things a bit more convenient by adding aliases
# (info, warn, error, debug) and allowing info( 'this', 'is', 'OK' )
# In the future we may wish to make things more efficient by only
# doing the join (and calling the function) unless the logging level
# is high enough.

def makeListCompatible( fn ):
    """Return a new function allowing fn( 'a 1 b' ) to be called as
       newfn( 'a', 1, 'b' )"""

    def newfn( *args ):
        "Generated function. Closure-ish."
        if len( args ) == 1:
            return fn( *args )
        args = ' '.join( str( arg ) for arg in args )
        return fn( args )

    # Fix newfn's name and docstring
    setattr( newfn, '__name__', fn.__name__ )
    setattr( newfn, '__doc__', fn.__doc__ )
    return newfn

_loggers = lg.info, lg.output, lg.warn, lg.error, lg.debug
_loggers = tuple( makeListCompatible( logger )
                  for logger in _loggers )
lg.info, lg.output, lg.warn, lg.error, lg.debug = _loggers
info, output, warn, error, debug = _loggers

setLogLevel = lg.setLogLevel
