blob: cd008217e253955bb578ec2cbce14351018c60b5 [file] [log] [blame]
carlosmscabralf40ecd12013-02-01 18:15:58 -02001"Logging functions for Mininet."
2
3import logging
4from logging import Logger
5import 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.
11OUTPUT = 25
12
13LEVELS = { '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
21LOGLEVELDEFAULT = OUTPUT
22
23#default: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
24LOGMSGFORMAT = '%(message)s'
25
26
27# Modified from python2.5/__init__.py
28class 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
58class 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
77class 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
149lg = 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
157def 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
173info, 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
178setLogLevel = lg.setLogLevel