X-Git-Url: https://wannabe.guru.org/gitweb/?a=blobdiff_plain;f=logging_utils.py;h=b7fd11fd8168cfbc985fa1fa60e28704b86075bd;hb=1574e8a3a8982fab9278ad534f9427d464e4bffb;hp=328ea6fe9d3570b8e9fe6af3f09eadba774eca1c;hpb=0e451d3b3bf899b3d9ac0c38e3c3cd9d9be170ba;p=python_utils.git diff --git a/logging_utils.py b/logging_utils.py index 328ea6f..b7fd11f 100644 --- a/logging_utils.py +++ b/logging_utils.py @@ -4,6 +4,7 @@ import contextlib import datetime +import enum import logging from logging.handlers import RotatingFileHandler, SysLogHandler import os @@ -92,6 +93,16 @@ cfg.add_argument( help='logging.info also prints to stdout.' ) +# See also: OutputMultiplexer/OutputContext +cfg.add_argument( + '--logging_captures_prints', + action=argparse_utils.ActionNoYes, + default=False, + help='When calling print also log.info too' +) + +built_in_print = print + class OnlyInfoFilter(logging.Filter): def filter(self, record): @@ -136,12 +147,7 @@ def initialize_logging(logger=None) -> logging.Logger: fmt = f'%(process)d.%(thread)d|{fmt}' if config.config['logging_syslog']: - if sys.platform in ('win32', 'cygwin'): - print( - "WARNING: Current platform does not support syslog; IGNORING.", - file=sys.stderr - ) - else: + if sys.platform not in ('win32', 'cygwin'): handler = SysLogHandler() # for k, v in encoded_priorities.items(): # handler.encodePriority(k, v) @@ -154,7 +160,7 @@ def initialize_logging(logger=None) -> logging.Logger: handler.setLevel(numeric_level) handlers.append(handler) - if config.config['logging_filename'] is not None: + if config.config['logging_filename']: handler = RotatingFileHandler( config.config['logging_filename'], maxBytes = config.config['logging_filename_maxsize'], @@ -185,12 +191,28 @@ def initialize_logging(logger=None) -> logging.Logger: for handler in handlers: logger.addHandler(handler) + if config.config['logging_info_is_print']: handler = logging.StreamHandler(sys.stdout) handler.addFilter(OnlyInfoFilter()) logger.addHandler(handler) + logger.setLevel(numeric_level) logger.propagate = False + + if config.config['logging_captures_prints']: + import builtins + global built_in_print + + def print_and_also_log(*arg, **kwarg): + f = kwarg.get('file', None) + if f == sys.stderr: + logger.warning(*arg) + else: + logger.info(*arg) + built_in_print(*arg, **kwarg) + builtins.print = print_and_also_log + return logger @@ -212,30 +234,32 @@ def dprint(*args, **kwargs) -> None: print(*args, file=sys.stderr, **kwargs) -class OutputSink(object): - - # Bits in the destination_bitv bitvector. Used to indicate the - # output destination. - STDOUT = 0x1 - STDERR = 0x2 - LOG_DEBUG = 0x4 # -\ - LOG_INFO = 0x8 # | - LOG_WARNING = 0x10 # > Should provide logger to the c'tor. - LOG_ERROR = 0x20 # | - LOG_CRITICAL = 0x40 # _/ - FILENAME = 0x80 # Must provide a filename to the c'tor. - HLOG = 0x100 - - ALL_LOG_DESTINATIONS = ( - LOG_DEBUG | LOG_INFO | LOG_WARNING | LOG_ERROR | LOG_CRITICAL - ) - ALL_OUTPUT_DESTINATIONS = 0x1FF +class OutputMultiplexer(object): + + class Destination(enum.IntEnum): + """Bits in the destination_bitv bitvector. Used to indicate the + output destination.""" + STDOUT = 0x1 + STDERR = 0x2 + LOG_DEBUG = 0x4 # -\ + LOG_INFO = 0x8 # | + LOG_WARNING = 0x10 # > Should provide logger to the c'tor. + LOG_ERROR = 0x20 # | + LOG_CRITICAL = 0x40 # _/ + FILENAME = 0x80 # Must provide a filename to the c'tor. + FILEHANDLE = 0x100 # Must provide a handle to the c'tor. + HLOG = 0x200 + ALL_LOG_DESTINATIONS = ( + LOG_DEBUG | LOG_INFO | LOG_WARNING | LOG_ERROR | LOG_CRITICAL + ) + ALL_OUTPUT_DESTINATIONS = 0x2FF def __init__(self, destination_bitv: int, *, logger=None, - filename=None): + filename=None, + handle=None): if logger is None: logger = logging.getLogger(None) self.logger = logger @@ -243,21 +267,35 @@ class OutputSink(object): if filename is not None: self.f = open(filename, "wb", buffering=0) else: - if self.destination_bitv & OutputSink.FILENAME: + if self.destination_bitv & OutputMultiplexer.FILENAME: raise ValueError( "Filename argument is required if bitv & FILENAME" ) self.f = None + + if handle is not None: + self.h = handle + else: + if self.destination_bitv & OutputMultiplexer.FILEHANDLE: + raise ValueError( + "Handle argument is required if bitv & FILEHANDLE" + ) + self.h = None + self.set_destination_bitv(destination_bitv) def get_destination_bitv(self): return self.destination_bitv def set_destination_bitv(self, destination_bitv: int): - if destination_bitv & self.FILENAME and self.f is None: + if destination_bitv & self.Destination.FILENAME and self.f is None: raise ValueError( "Filename argument is required if bitv & FILENAME" ) + if destination_bitv & self.Destination.FILEHANDLE and self.h is None: + raise ValueError( + "Handle argument is required if bitv & FILEHANDLE" + ) self.destination_bitv = destination_bitv def print(self, *args, **kwargs): @@ -277,28 +315,39 @@ class OutputSink(object): sep = " " if end is None: end = "\n" - if self.destination_bitv & self.STDOUT: + if self.destination_bitv & self.Destination.STDOUT: print(buf, file=sys.stdout, sep=sep, end=end) - if self.destination_bitv & self.STDERR: + if self.destination_bitv & self.Destination.STDERR: print(buf, file=sys.stderr, sep=sep, end=end) if end == '\n': buf += '\n' - if self.destination_bitv & self.FILENAME and self.f is not None: + if ( + self.destination_bitv & self.Destination.FILENAME and + self.f is not None + ): self.f.write(buf.encode('utf-8')) self.f.flush() + + if ( + self.destination_bitv & self.Destination.FILEHANDLE and + self.h is not None + ): + self.h.write(buf) + self.h.flush() + buf = strip_escape_sequences(buf) if self.logger is not None: - if self.destination_bitv & self.LOG_DEBUG: + if self.destination_bitv & self.Destination.LOG_DEBUG: self.logger.debug(buf) - if self.destination_bitv & self.LOG_INFO: + if self.destination_bitv & self.Destination.LOG_INFO: self.logger.info(buf) - if self.destination_bitv & self.LOG_WARNING: + if self.destination_bitv & self.Destination.LOG_WARNING: self.logger.warning(buf) - if self.destination_bitv & self.LOG_ERROR: + if self.destination_bitv & self.Destination.LOG_ERROR: self.logger.error(buf) - if self.destination_bitv & self.LOG_CRITICAL: + if self.destination_bitv & self.Destination.LOG_CRITICAL: self.logger.critical(buf) - if self.destination_bitv & self.HLOG: + if self.destination_bitv & self.Destination.HLOG: hlog(buf) def close(self): @@ -306,18 +355,23 @@ class OutputSink(object): self.f.close() -class OutputContext(OutputSink, contextlib.ContextDecorator): +class OutputContext(OutputMultiplexer, contextlib.ContextDecorator): def __init__(self, - destination_bitv: int, + destination_bitv: OutputMultiplexer.Destination, *, logger=None, - filename=None): - super().__init__(destination_bitv, logger=logger, filename=filename) + filename=None, + handle=None): + super().__init__( + destination_bitv, + logger=logger, + filename=filename, + handle=handle) def __enter__(self): return self - def __exit__(self, etype, value, traceback): + def __exit__(self, etype, value, traceback) -> bool: super().close() if etype is not None: return False