Statistics
| Branch: | Tag: | Revision:

mininet / mininet / log.py @ 7a506047

History | View | Annotate | Download (5.98 KB)

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