carlosmscabral | f40ecd1 | 2013-02-01 18:15:58 -0200 | [diff] [blame] | 1 | "Logging functions for Mininet." |
| 2 | |
| 3 | import logging |
| 4 | from logging import Logger |
| 5 | import types |
| 6 | |
| 7 | # Create a new loglevel, 'CLI info', which enables a Mininet user to see only |
| 8 | # the output of the commands they execute, plus any errors or warnings. This |
| 9 | # level is in between info and warning. CLI info-level commands should not be |
| 10 | # printed during regression tests. |
| 11 | OUTPUT = 25 |
| 12 | |
| 13 | LEVELS = { 'debug': logging.DEBUG, |
| 14 | 'info': logging.INFO, |
| 15 | 'output': OUTPUT, |
| 16 | 'warning': logging.WARNING, |
| 17 | 'error': logging.ERROR, |
| 18 | 'critical': logging.CRITICAL } |
| 19 | |
| 20 | # change this to logging.INFO to get printouts when running unit tests |
| 21 | LOGLEVELDEFAULT = OUTPUT |
| 22 | |
| 23 | #default: '%(asctime)s - %(name)s - %(levelname)s - %(message)s' |
| 24 | LOGMSGFORMAT = '%(message)s' |
| 25 | |
| 26 | |
| 27 | # Modified from python2.5/__init__.py |
| 28 | class StreamHandlerNoNewline( logging.StreamHandler ): |
| 29 | """StreamHandler that doesn't print newlines by default. |
| 30 | Since StreamHandler automatically adds newlines, define a mod to more |
| 31 | easily support interactive mode when we want it, or errors-only logging |
| 32 | for running unit tests.""" |
| 33 | |
| 34 | def emit( self, record ): |
| 35 | """Emit a record. |
| 36 | If a formatter is specified, it is used to format the record. |
| 37 | The record is then written to the stream with a trailing newline |
| 38 | [ N.B. this may be removed depending on feedback ]. If exception |
| 39 | information is present, it is formatted using |
| 40 | traceback.printException and appended to the stream.""" |
| 41 | try: |
| 42 | msg = self.format( record ) |
| 43 | fs = '%s' # was '%s\n' |
| 44 | if not hasattr( types, 'UnicodeType' ): # if no unicode support... |
| 45 | self.stream.write( fs % msg ) |
| 46 | else: |
| 47 | try: |
| 48 | self.stream.write( fs % msg ) |
| 49 | except UnicodeError: |
| 50 | self.stream.write( fs % msg.encode( 'UTF-8' ) ) |
| 51 | self.flush() |
| 52 | except ( KeyboardInterrupt, SystemExit ): |
| 53 | raise |
| 54 | except: |
| 55 | self.handleError( record ) |
| 56 | |
| 57 | |
| 58 | class Singleton( type ): |
| 59 | """Singleton pattern from Wikipedia |
| 60 | See http://en.wikipedia.org/wiki/SingletonPattern#Python |
| 61 | |
| 62 | Intended to be used as a __metaclass_ param, as shown for the class |
| 63 | below. |
| 64 | |
| 65 | Changed cls first args to mcs to satisfy pylint.""" |
| 66 | |
| 67 | def __init__( mcs, name, bases, dict_ ): |
| 68 | super( Singleton, mcs ).__init__( name, bases, dict_ ) |
| 69 | mcs.instance = None |
| 70 | |
| 71 | def __call__( mcs, *args, **kw ): |
| 72 | if mcs.instance is None: |
| 73 | mcs.instance = super( Singleton, mcs ).__call__( *args, **kw ) |
| 74 | return mcs.instance |
| 75 | |
| 76 | |
| 77 | class MininetLogger( Logger, object ): |
| 78 | """Mininet-specific logger |
| 79 | Enable each mininet .py file to with one import: |
| 80 | |
| 81 | from mininet.log import [lg, info, error] |
| 82 | |
| 83 | ...get a default logger that doesn't require one newline per logging |
| 84 | call. |
| 85 | |
| 86 | Inherit from object to ensure that we have at least one new-style base |
| 87 | class, and can then use the __metaclass__ directive, to prevent this |
| 88 | error: |
| 89 | |
| 90 | TypeError: Error when calling the metaclass bases |
| 91 | a new-style class can't have only classic bases |
| 92 | |
| 93 | If Python2.5/logging/__init__.py defined Filterer as a new-style class, |
| 94 | via Filterer( object ): rather than Filterer, we wouldn't need this. |
| 95 | |
| 96 | Use singleton pattern to ensure only one logger is ever created.""" |
| 97 | |
| 98 | __metaclass__ = Singleton |
| 99 | |
| 100 | def __init__( self ): |
| 101 | |
| 102 | Logger.__init__( self, "mininet" ) |
| 103 | |
| 104 | # create console handler |
| 105 | ch = StreamHandlerNoNewline() |
| 106 | # create formatter |
| 107 | formatter = logging.Formatter( LOGMSGFORMAT ) |
| 108 | # add formatter to ch |
| 109 | ch.setFormatter( formatter ) |
| 110 | # add ch to lg |
| 111 | self.addHandler( ch ) |
| 112 | |
| 113 | self.setLogLevel() |
| 114 | |
| 115 | def setLogLevel( self, levelname=None ): |
| 116 | """Setup loglevel. |
| 117 | Convenience function to support lowercase names. |
| 118 | levelName: level name from LEVELS""" |
| 119 | level = LOGLEVELDEFAULT |
| 120 | if levelname is not None: |
| 121 | if levelname not in LEVELS: |
| 122 | raise Exception( 'unknown levelname seen in setLogLevel' ) |
| 123 | else: |
| 124 | level = LEVELS.get( levelname, level ) |
| 125 | |
| 126 | self.setLevel( level ) |
| 127 | self.handlers[ 0 ].setLevel( level ) |
| 128 | |
| 129 | # pylint: disable-msg=E0202 |
| 130 | # "An attribute inherited from mininet.log hide this method" |
| 131 | # Not sure why this is occurring - this function definitely gets called. |
| 132 | |
| 133 | # See /usr/lib/python2.5/logging/__init__.py; modified from warning() |
| 134 | def output( self, msg, *args, **kwargs ): |
| 135 | """Log 'msg % args' with severity 'OUTPUT'. |
| 136 | |
| 137 | To pass exception information, use the keyword argument exc_info |
| 138 | with a true value, e.g. |
| 139 | |
| 140 | logger.warning("Houston, we have a %s", "cli output", exc_info=1) |
| 141 | """ |
| 142 | if self.manager.disable >= OUTPUT: |
| 143 | return |
| 144 | if self.isEnabledFor( OUTPUT ): |
| 145 | self._log( OUTPUT, msg, args, kwargs ) |
| 146 | |
| 147 | # pylint: enable-msg=E0202 |
| 148 | |
| 149 | lg = MininetLogger() |
| 150 | |
| 151 | # Make things a bit more convenient by adding aliases |
| 152 | # (info, warn, error, debug) and allowing info( 'this', 'is', 'OK' ) |
| 153 | # In the future we may wish to make things more efficient by only |
| 154 | # doing the join (and calling the function) unless the logging level |
| 155 | # is high enough. |
| 156 | |
| 157 | def makeListCompatible( fn ): |
| 158 | """Return a new function allowing fn( 'a 1 b' ) to be called as |
| 159 | newfn( 'a', 1, 'b' )""" |
| 160 | |
| 161 | def newfn( *args ): |
| 162 | "Generated function. Closure-ish." |
| 163 | if len( args ) == 1: |
| 164 | return fn( *args ) |
| 165 | args = ' '.join( [ str( arg ) for arg in args ] ) |
| 166 | return fn( args ) |
| 167 | |
| 168 | # Fix newfn's name and docstring |
| 169 | setattr( newfn, '__name__', fn.__name__ ) |
| 170 | setattr( newfn, '__doc__', fn.__doc__ ) |
| 171 | return newfn |
| 172 | |
| 173 | info, output, warn, error, debug = ( |
| 174 | lg.info, lg.output, lg.warn, lg.error, lg.debug ) = [ |
| 175 | makeListCompatible( f ) for f in |
| 176 | lg.info, lg.output, lg.warn, lg.error, lg.debug ] |
| 177 | |
| 178 | setLogLevel = lg.setLogLevel |