from argparse_utils import ActionNoYes
import config
-
+import logging_utils
logger = logging.getLogger(__name__)
@functools.wraps(entry_point)
def initialize_wrapper(*args, **kwargs):
sys.excepthook = handle_uncaught_exception
- config.parse(entry_point.__globals__['__file__'])
+ if (
+ '__globals__' in entry_point.__dict__ and
+ '__file__' in entry_point.__globals__
+ ):
+ config.parse(entry_point.__globals__['__file__'])
+ else:
+ config.parse(None)
- import logging_utils
logging_utils.initialize_logging(logging.getLogger())
config.late_logging()
logger.debug(f'Starting {entry_point.__name__} (program entry point)')
ret = None
- import timer
- with timer.Timer() as t:
+ import stopwatch
+ with stopwatch.Timer() as t:
ret = entry_point(*args, **kwargs)
logger.debug(
f'{entry_point.__name__} (program entry point) returned {ret}.'
walltime = t()
(utime, stime, cutime, cstime, elapsed_time) = os.times()
- logger.debug(f'\n'
+ logger.debug('\n'
f'user: {utime}s\n'
f'system: {stime}s\n'
f'child user: {cutime}s\n'
f'child system: {cstime}s\n'
- f'elapsed: {elapsed_time}s\n'
- f'walltime: {walltime}s\n')
+ f'machine uptime: {elapsed_time}s\n'
+ f'walltime: {walltime}s')
if ret != 0:
logger.info(f'Exit {ret}')
else:
import pprint
import re
import sys
-from typing import Any, Dict, List
+from typing import Any, Dict, List, Optional
# This module is commonly used by others in here and should avoid
# taking any unnecessary dependencies back on them.
return False
-def parse(entry_module: str) -> Dict[str, Any]:
+def parse(entry_module: Optional[str]) -> Dict[str, Any]:
"""Main program should call this early in main()"""
global config_parse_called
if config_parse_called:
global saved_messages
# If we're about to do the usage message dump, put the main module's
- # argument group first in the list, please.
+ # argument group first in the list (if possible), please.
reordered_action_groups = []
prog = sys.argv[0]
+
for arg in sys.argv:
if arg == '--help' or arg == '-h':
- print(entry_module)
for group in args._action_groups:
- if entry_module in group.title or prog in group.title:
- reordered_action_groups.insert(0, group)
+ if entry_module is not None and entry_module in group.title:
+ reordered_action_groups.insert(0, group) # prepend
+ elif prog in group.title:
+ reordered_action_groups.insert(0, group) # prepend
else:
- reordered_action_groups.append(group)
+ reordered_action_groups.append(group) # append
args._action_groups = reordered_action_groups
# Examine the environment variables to settings that match
import contextlib
import datetime
+import enum
import logging
from logging.handlers import RotatingFileHandler, SysLogHandler
import os
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):
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)
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'],
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
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
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):
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):
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