7575@author: Kenneth Hoste (Ghent University)
7676"""
7777
78+ from collections import namedtuple
7879import inspect
7980import logging
8081import logging .handlers
8586import weakref
8687from distutils .version import LooseVersion
8788
89+
90+ def _env_to_boolean (varname , default = False ):
91+ """
92+ Compute a boolean based on the truth value of environment variable `varname`.
93+ If no variable by that name is present in `os.environ`, then return `default`.
94+
95+ For the purpose of this function, the string values ``'1'``,
96+ ``'y'``, ``'yes'``, and ``'true'`` (case-insensitive) are all
97+ mapped to the truth value ``True``::
98+
99+ >>> os.environ['NO_FOOBAR'] = '1'
100+ >>> _env_to_boolean('NO_FOOBAR')
101+ True
102+ >>> os.environ['NO_FOOBAR'] = 'Y'
103+ >>> _env_to_boolean('NO_FOOBAR')
104+ True
105+ >>> os.environ['NO_FOOBAR'] = 'Yes'
106+ >>> _env_to_boolean('NO_FOOBAR')
107+ True
108+ >>> os.environ['NO_FOOBAR'] = 'yes'
109+ >>> _env_to_boolean('NO_FOOBAR')
110+ True
111+ >>> os.environ['NO_FOOBAR'] = 'True'
112+ >>> _env_to_boolean('NO_FOOBAR')
113+ True
114+ >>> os.environ['NO_FOOBAR'] = 'TRUE'
115+ >>> _env_to_boolean('NO_FOOBAR')
116+ True
117+ >>> os.environ['NO_FOOBAR'] = 'true'
118+ >>> _env_to_boolean('NO_FOOBAR')
119+ True
120+
121+ Any other value is mapped to Python ``False``::
122+
123+ >>> os.environ['NO_FOOBAR'] = '0'
124+ >>> _env_to_boolean('NO_FOOBAR')
125+ False
126+ >>> os.environ['NO_FOOBAR'] = 'no'
127+ >>> _env_to_boolean('NO_FOOBAR')
128+ False
129+ >>> os.environ['NO_FOOBAR'] = 'if you please'
130+ >>> _env_to_boolean('NO_FOOBAR')
131+ False
132+
133+ If no variable named `varname` is present in `os.environ`, then
134+ return `default`::
135+
136+ >>> del os.environ['NO_FOOBAR']
137+ >>> _env_to_boolean('NO_FOOBAR', 42)
138+ 42
139+
140+ By default, calling `_env_to_boolean` on an undefined
141+ variable returns Python ``False``::
142+
143+ >>> if 'NO_FOOBAR' in os.environ: del os.environ['NO_FOOBAR']
144+ >>> _env_to_boolean('NO_FOOBAR')
145+ False
146+ """
147+ if varname not in os .environ :
148+ return default
149+ else :
150+ return os .environ .get (varname ).lower () in ('1' , 'yes' , 'true' , 'y' )
151+
152+
153+ HAVE_COLOREDLOGS_MODULE = False
154+ if not _env_to_boolean ('FANCYLOGGER_NO_COLOREDLOGS' ):
155+ try :
156+ import coloredlogs
157+ import humanfriendly
158+ HAVE_COLOREDLOGS_MODULE = True
159+ except ImportError :
160+ pass
161+
88162# constants
89163TEST_LOGGING_FORMAT = '%(levelname)-10s %(name)-15s %(threadName)-10s %(message)s'
90164DEFAULT_LOGGING_FORMAT = '%(asctime)-15s ' + TEST_LOGGING_FORMAT
101175
102176DEFAULT_UDP_PORT = 5005
103177
178+ # poor man's enum
179+ Colorize = namedtuple ('Colorize' , 'AUTO ALWAYS NEVER' )('auto' , 'always' , 'never' )
180+
104181# register new loglevelname
105182logging .addLevelName (logging .CRITICAL * 2 + 1 , 'APOCALYPTIC' )
106183# register QUIET, EXCEPTION and FATAL alias
111188
112189# mpi rank support
113190_MPIRANK = MPIRANK_NO_MPI
114- if os . environ . get ( 'FANCYLOGGER_IGNORE_MPI4PY' , '0' ). lower () not in ( '1' , 'yes' , 'true' , 'y ' ):
191+ if not _env_to_boolean ( 'FANCYLOGGER_IGNORE_MPI4PY ' ):
115192 try :
116193 from mpi4py import MPI
117194 if MPI .Is_initialized ():
@@ -383,7 +460,7 @@ def getLogger(name=None, fname=False, clsname=False, fancyrecord=None):
383460
384461 l = logging .getLogger (fullname )
385462 l .fancyrecord = fancyrecord
386- if os . environ . get ('FANCYLOGGER_GETLOGGER_DEBUG' , '0' ). lower () in ( '1' , 'yes' , 'true' , 'y ' ):
463+ if _env_to_boolean ('FANCYLOGGER_GETLOGGER_DEBUG' ):
387464 print 'FANCYLOGGER_GETLOGGER_DEBUG' ,
388465 print 'name' , name , 'fname' , fname , 'fullname' , fullname ,
389466 print "getRootLoggerName: " , getRootLoggerName ()
@@ -435,7 +512,7 @@ def getRootLoggerName():
435512 return "not available in optimized mode"
436513
437514
438- def logToScreen (enable = True , handler = None , name = None , stdout = False ):
515+ def logToScreen (enable = True , handler = None , name = None , stdout = False , colorize = Colorize . NEVER ):
439516 """
440517 enable (or disable) logging to screen
441518 returns the screenhandler (this can be used to later disable logging to screen)
@@ -447,15 +524,22 @@ def logToScreen(enable=True, handler=None, name=None, stdout=False):
447524
448525 by default, logToScreen will log to stderr; logging to stdout instead can be done
449526 by setting the 'stdout' parameter to True
527+
528+ The `colorize` parameter enables or disables log colorization using
529+ ANSI terminal escape sequences, according to the values allowed
530+ in the `colorize` parameter to function `_screenLogFormatterFactory`
531+ (which see).
450532 """
451533 handleropts = {'stdout' : stdout }
534+ formatter = _screenLogFormatterFactory (colorize = colorize , stream = (sys .stdout if stdout else sys .stderr ))
452535
453536 return _logToSomething (FancyStreamHandler ,
454537 handleropts ,
455538 loggeroption = 'logtoscreen_stdout_%s' % str (stdout ),
456539 name = name ,
457540 enable = enable ,
458541 handler = handler ,
542+ formatterclass = formatter ,
459543 )
460544
461545
@@ -516,17 +600,22 @@ def logToUDP(hostname, port=5005, enable=True, datagramhandler=None, name=None):
516600 )
517601
518602
519- def _logToSomething (handlerclass , handleropts , loggeroption , enable = True , name = None , handler = None ):
603+ def _logToSomething (handlerclass , handleropts , loggeroption ,
604+ enable = True , name = None , handler = None , formatterclass = None ):
520605 """
521606 internal function to enable (or disable) logging to handler named handlername
522- handleropts is options dictionary passed to create the handler instance
607+ handleropts is options dictionary passed to create the handler instance;
608+ `formatterclass` is the class to use to instantiate a log formatter object.
523609
524610 returns the handler (this can be used to later disable logging to file)
525611
526612 if you want to disable logging to the handler, pass the earlier obtained handler
527613 """
528614 logger = getLogger (name , fname = False , clsname = False )
529615
616+ if formatterclass is None :
617+ formatterclass = logging .Formatter
618+
530619 if not hasattr (logger , loggeroption ):
531620 # not set.
532621 setattr (logger , loggeroption , False ) # set default to False
@@ -538,7 +627,7 @@ def _logToSomething(handlerclass, handleropts, loggeroption, enable=True, name=N
538627 f_format = DEFAULT_LOGGING_FORMAT
539628 else :
540629 f_format = FANCYLOG_LOGGING_FORMAT
541- formatter = logging . Formatter (f_format )
630+ formatter = formatterclass (f_format )
542631 handler = handlerclass (** handleropts )
543632 handler .setFormatter (formatter )
544633 logger .addHandler (handler )
@@ -566,6 +655,36 @@ def _logToSomething(handlerclass, handleropts, loggeroption, enable=True, name=N
566655 return handler
567656
568657
658+ def _screenLogFormatterFactory (colorize = Colorize .NEVER , stream = sys .stdout ):
659+ """
660+ Return a log formatter class, with optional colorization features.
661+
662+ Second argument `colorize` controls whether the formatter
663+ can use ANSI terminal escape sequences:
664+
665+ * ``Colorize.NEVER`` (default) forces use the plain `logging.Formatter` class;
666+ * ``Colorize.ALWAYS`` forces use of the colorizing formatter;
667+ * ``Colorize.AUTO`` selects the colorizing formatter depending on
668+ whether `stream` is connected to a terminal.
669+
670+ Second argument `stream` is the stream to check in case `colorize`
671+ is ``Colorize.AUTO``.
672+ """
673+ formatter = logging .Formatter # default
674+ if HAVE_COLOREDLOGS_MODULE :
675+ if colorize == Colorize .AUTO :
676+ # auto-detect
677+ if humanfriendly .terminal .terminal_supports_colors (stream ):
678+ formatter = coloredlogs .ColoredFormatter
679+ elif colorize == Colorize .ALWAYS :
680+ formatter = coloredlogs .ColoredFormatter
681+ elif colorize == Colorize .NEVER :
682+ pass
683+ else :
684+ raise ValueError ("Argument `colorize` must be one of 'auto', 'always', or 'never'." )
685+ return formatter
686+
687+
569688def _getSysLogFacility (name = None ):
570689 """Look for proper syslog facility
571690 typically the syslog/rsyslog config has an entry
@@ -605,7 +724,7 @@ def setLogLevel(level):
605724 level = getLevelInt (level )
606725 logger = getLogger (fname = False , clsname = False )
607726 logger .setLevel (level )
608- if os . environ . get ('FANCYLOGGER_LOGLEVEL_DEBUG' , '0' ). lower () in ( '1' , 'yes' , 'true' , 'y ' ):
727+ if _env_to_boolean ('FANCYLOGGER_LOGLEVEL_DEBUG' ):
609728 print "FANCYLOGGER_LOGLEVEL_DEBUG" , level , logging .getLevelName (level )
610729 print "\n " .join (logger .get_parent_info ("FANCYLOGGER_LOGLEVEL_DEBUG" ))
611730 sys .stdout .flush ()
0 commit comments