Statistics
| Branch: | Tag: | Revision:

mininet / mininet / log.py @ b1ec912d

History | View | Annotate | Download (5.91 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/Singleton_Pattern
61

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

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

    
69
    def __call__( cls, *args, **kw ):
70
        if cls.instance is None:
71
            cls.instance = super( Singleton, cls ).__call__( *args, **kw )
72
        return cls.instance
73

    
74

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

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

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

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

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

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

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

    
96
    __metaclass__ = Singleton
97

    
98
    def __init__( self ):
99

    
100
        Logger.__init__( self, "mininet" )
101

    
102
        # create console handler
103
        ch = StreamHandlerNoNewline()
104
        # create formatter
105
        formatter = logging.Formatter( LOGMSGFORMAT )
106
        # add formatter to ch
107
        ch.setFormatter( formatter )
108
        # add ch to lg
109
        self.addHandler( ch )
110

    
111
        self.setLogLevel()
112

    
113
    def setLogLevel( self, levelname=None ):
114
        """Setup loglevel.
115
           Convenience function to support lowercase names.
116
           levelName: level name from LEVELS"""
117
        level = LOGLEVELDEFAULT
118
        if levelname is not None:
119
            if levelname not in LEVELS:
120
                raise Exception( 'unknown levelname seen in setLogLevel' )
121
            else:
122
                level = LEVELS.get( levelname, level )
123

    
124
        self.setLevel( level )
125
        self.handlers[ 0 ].setLevel( level )
126

    
127
    # pylint: disable=E0202
128
    # "An attribute inherited from mininet.log hide this method"
129
    # Not sure why this is occurring - this function definitely gets called.
130

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

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

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

    
145
    # pylint: enable=E0202
146

    
147
lg = MininetLogger()
148

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

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

    
159
    def newfn( *args ):
160
        "Generated function. Closure-ish."
161
        if len( args ) == 1:
162
            return fn( *args )
163
        args = ' '.join( [ str( arg ) for arg in args ] )
164
        return fn( args )
165

    
166
    # Fix newfn's name and docstring
167
    setattr( newfn, '__name__', fn.__name__ )
168
    setattr( newfn, '__doc__', fn.__doc__ )
169
    return newfn
170

    
171
info, output, warn, error, debug = (
172
    lg.info, lg.output, lg.warn, lg.error, lg.debug ) = [
173
        makeListCompatible( f ) for f in
174
            lg.info, lg.output, lg.warn, lg.error, lg.debug ]
175

    
176
setLogLevel = lg.setLogLevel