import functools
import logging
import os
-import pdb
import sys
-import traceback
# This module is commonly used by others in here and should avoid
# taking any unnecessary dependencies back on them.
default=False,
help='Break into pdb on top level unhandled exceptions.'
)
+args.add_argument(
+ '--show_random_seed',
+ action=ActionNoYes,
+ default=False,
+ help='Should we display (and log.debug) the global random seed?'
+)
+args.add_argument(
+ '--set_random_seed',
+ type=int,
+ nargs=1,
+ default=None,
+ metavar='SEED_INT',
+ help='Override the global random seed with a particular number.'
+)
+
+original_hook = sys.excepthook
-def handle_uncaught_exception(
- exc_type,
- exc_value,
- exc_traceback):
+def handle_uncaught_exception(exc_type, exc_value, exc_tb):
+ """
+ Top-level exception handler for exceptions that make it past any exception
+ handlers in the python code being run. Logs the error and stacktrace then
+ maybe attaches a debugger.
+ """
+ global original_hook
+ msg = f'Unhandled top level exception {exc_type}'
+ logger.exception(msg)
+ print(msg, file=sys.stderr)
if issubclass(exc_type, KeyboardInterrupt):
- sys.__excepthook__(exc_type, exc_value, exc_traceback)
+ sys.__excepthook__(exc_type, exc_value, exc_tb)
return
- logger.exception(f'Unhandled top level {exc_type}',
- exc_info=(exc_type, exc_value, exc_traceback))
- traceback.print_exception(exc_type, exc_value, exc_traceback)
- if config.config['debug_unhandled_exceptions']:
- logger.info("Invoking the debugger...")
- pdb.pm()
+ else:
+ if (
+ not sys.stderr.isatty() or
+ not sys.stdin.isatty()
+ ):
+ # stdin or stderr is redirected, just do the normal thing
+ original_hook(exc_type, exc_value, exc_tb)
+ else:
+ # a terminal is attached and stderr is not redirected, maybe debug.
+ import traceback
+ traceback.print_exception(exc_type, exc_value, exc_tb)
+ if config.config['debug_unhandled_exceptions']:
+ import pdb
+ logger.info("Invoking the debugger...")
+ pdb.pm()
+ else:
+ original_hook(exc_type, exc_value, exc_tb)
def initialize(entry_point):
+ """
+ Remember to initialize config, initialize logging, set/log a random
+ seed, etc... before running main.
- """Remember to initialize config and logging before running main."""
+ """
@functools.wraps(entry_point)
def initialize_wrapper(*args, **kwargs):
- sys.excepthook = handle_uncaught_exception
+
+ # Hook top level unhandled exceptions, maybe invoke debugger.
+ if sys.excepthook == sys.__excepthook__:
+ sys.excepthook = handle_uncaught_exception
+
+ # Try to figure out the name of the program entry point. Then
+ # parse configuration (based on cmdline flags, environment vars
+ # etc...)
if (
'__globals__' in entry_point.__dict__ and
'__file__' in entry_point.__globals__
else:
config.parse(None)
+ # Initialize logging... and log some remembered messages from
+ # config module.
logging_utils.initialize_logging(logging.getLogger())
-
config.late_logging()
- logger.debug(f'Starting {entry_point.__name__} (program entry point)')
+ # Allow programs that don't bother to override the random seed
+ # to be replayed via the commandline.
+ import random
+ random_seed = config.config['set_random_seed']
+ if random_seed is not None:
+ random_seed = random_seed[0]
+ else:
+ random_seed = int.from_bytes(os.urandom(4), 'little')
+
+ if config.config['show_random_seed']:
+ msg = f'Global random seed is: {random_seed}'
+ print(msg)
+ logger.debug(msg)
+ random.seed(random_seed)
+ # Do it, invoke the user's code. Pay attention to how long it takes.
+ logger.debug(f'Starting {entry_point.__name__} (program entry point)')
ret = None
import stopwatch
with stopwatch.Timer() as t:
f'child system: {cstime}s\n'
f'machine uptime: {elapsed_time}s\n'
f'walltime: {walltime}s')
- if ret != 0:
- logger.info(f'Exit {ret}')
+
+ # If it doesn't return cleanly, call attention to the return value.
+ if ret is not None and ret != 0:
+ logger.error(f'Exit {ret}')
else:
logger.debug(f'Exit {ret}')
sys.exit(ret)