75
75
@author: Kenneth Hoste (Ghent University)
76
76
"""
77
77
78
+ from collections import namedtuple
78
79
import inspect
79
80
import logging
80
81
import logging .handlers
85
86
import weakref
86
87
from distutils .version import LooseVersion
87
88
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
+
88
162
# constants
89
163
TEST_LOGGING_FORMAT = '%(levelname)-10s %(name)-15s %(threadName)-10s %(message)s'
90
164
DEFAULT_LOGGING_FORMAT = '%(asctime)-15s ' + TEST_LOGGING_FORMAT
101
175
102
176
DEFAULT_UDP_PORT = 5005
103
177
178
+ # poor man's enum
179
+ Colorize = namedtuple ('Colorize' , 'AUTO ALWAYS NEVER' )('auto' , 'always' , 'never' )
180
+
104
181
# register new loglevelname
105
182
logging .addLevelName (logging .CRITICAL * 2 + 1 , 'APOCALYPTIC' )
106
183
# register QUIET, EXCEPTION and FATAL alias
111
188
112
189
# mpi rank support
113
190
_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 ' ):
115
192
try :
116
193
from mpi4py import MPI
117
194
if MPI .Is_initialized ():
@@ -383,7 +460,7 @@ def getLogger(name=None, fname=False, clsname=False, fancyrecord=None):
383
460
384
461
l = logging .getLogger (fullname )
385
462
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' ):
387
464
print 'FANCYLOGGER_GETLOGGER_DEBUG' ,
388
465
print 'name' , name , 'fname' , fname , 'fullname' , fullname ,
389
466
print "getRootLoggerName: " , getRootLoggerName ()
@@ -435,7 +512,7 @@ def getRootLoggerName():
435
512
return "not available in optimized mode"
436
513
437
514
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 ):
439
516
"""
440
517
enable (or disable) logging to screen
441
518
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):
447
524
448
525
by default, logToScreen will log to stderr; logging to stdout instead can be done
449
526
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).
450
532
"""
451
533
handleropts = {'stdout' : stdout }
534
+ formatter = _screenLogFormatterFactory (colorize = colorize , stream = (sys .stdout if stdout else sys .stderr ))
452
535
453
536
return _logToSomething (FancyStreamHandler ,
454
537
handleropts ,
455
538
loggeroption = 'logtoscreen_stdout_%s' % str (stdout ),
456
539
name = name ,
457
540
enable = enable ,
458
541
handler = handler ,
542
+ formatterclass = formatter ,
459
543
)
460
544
461
545
@@ -516,17 +600,22 @@ def logToUDP(hostname, port=5005, enable=True, datagramhandler=None, name=None):
516
600
)
517
601
518
602
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 ):
520
605
"""
521
606
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.
523
609
524
610
returns the handler (this can be used to later disable logging to file)
525
611
526
612
if you want to disable logging to the handler, pass the earlier obtained handler
527
613
"""
528
614
logger = getLogger (name , fname = False , clsname = False )
529
615
616
+ if formatterclass is None :
617
+ formatterclass = logging .Formatter
618
+
530
619
if not hasattr (logger , loggeroption ):
531
620
# not set.
532
621
setattr (logger , loggeroption , False ) # set default to False
@@ -538,7 +627,7 @@ def _logToSomething(handlerclass, handleropts, loggeroption, enable=True, name=N
538
627
f_format = DEFAULT_LOGGING_FORMAT
539
628
else :
540
629
f_format = FANCYLOG_LOGGING_FORMAT
541
- formatter = logging . Formatter (f_format )
630
+ formatter = formatterclass (f_format )
542
631
handler = handlerclass (** handleropts )
543
632
handler .setFormatter (formatter )
544
633
logger .addHandler (handler )
@@ -566,6 +655,36 @@ def _logToSomething(handlerclass, handleropts, loggeroption, enable=True, name=N
566
655
return handler
567
656
568
657
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
+
569
688
def _getSysLogFacility (name = None ):
570
689
"""Look for proper syslog facility
571
690
typically the syslog/rsyslog config has an entry
@@ -605,7 +724,7 @@ def setLogLevel(level):
605
724
level = getLevelInt (level )
606
725
logger = getLogger (fname = False , clsname = False )
607
726
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' ):
609
728
print "FANCYLOGGER_LOGLEVEL_DEBUG" , level , logging .getLevelName (level )
610
729
print "\n " .join (logger .get_parent_info ("FANCYLOGGER_LOGLEVEL_DEBUG" ))
611
730
sys .stdout .flush ()
0 commit comments