From ecc4e25cc22667938aae18091f3793c6cf61c4ed Mon Sep 17 00:00:00 2001 From: Christopher Toth Date: Thu, 29 Oct 2026 12:41:40 -0600 Subject: [PATCH] Replace owned logging handlers when logging is configured again --- agent-notes/improve-repeated-setup.md | 19 +++++++++++++++++++ logger_setup/logger_setup.py | 24 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 agent-notes/improve-repeated-setup.md diff --git a/agent-notes/improve-repeated-setup.md b/agent-notes/improve-repeated-setup.md new file mode 100644 index 0000000..d3d3c29 --- /dev/null +++ b/agent-notes/improve-repeated-setup.md @@ -0,0 +1,19 @@ +# Goal + +Replace only this package's handlers on repeated logging setup. + +# State + +Rebuilt on master after #2, #3, #4, #6 and #7 merged. Keep the root logger and existing helper API. After the staged replacement succeeds, detach and close the handlers this package installed previously. + +# Decisions and why + +Builds on #4's staging: a failed setup closes its staged handlers and leaves installed handlers, root level and exception hook untouched, so this change needs no rollback code of its own. Fault handling uses #3's crashlogger state: a call that does not enable crash logging disables it before old streams close; a call that enables it has already retargeted it to the new stream. Track only owned handlers, preserve unrelated ones, and restore the previous exception hook when a repeat call or shutdown turns it off. Setup and shutdown run on the main thread. + +# Surprises + +The earlier version of this PR duplicated #3's fault-handler tracking and #4's rollback; both were removed when rebuilding on merged master. Opening a file may still truncate its contents before a later failure. + +# Next step + +Read the PR's package checks and merge. No release. diff --git a/logger_setup/logger_setup.py b/logger_setup/logger_setup.py index 7c3328a..c961924 100644 --- a/logger_setup/logger_setup.py +++ b/logger_setup/logger_setup.py @@ -16,6 +16,9 @@ except NameError: logger = logging.getLogger() +_handlers = [] +_previous_excepthook = None + def create_handler( filename=None, @@ -56,6 +59,7 @@ def setup_logging( remote_address=None, remote_level=logging.ERROR, ): + global _handlers, _previous_excepthook formatter = logging.Formatter(message_format) handlers = [] try: @@ -96,13 +100,33 @@ def setup_logging( for handler in handlers: logger.addHandler(handler) + # The fault handler must stop using an old stream before that stream closes. + if not (error_log and log_crashes): + crashlogger.disable_crashlogger() + for handler in _handlers: + logger.removeHandler(handler) + handler.close() + _handlers = handlers logger.setLevel(logging.DEBUG) if log_unhandled_exceptions: + if sys.excepthook is not custom_excepthook: + _previous_excepthook = sys.excepthook sys.excepthook = custom_excepthook + elif sys.excepthook is custom_excepthook: + sys.excepthook = _previous_excepthook + _previous_excepthook = None logger.info("Logging initialized. PID: %d", os.getpid()) return logger def shutdown(): + global _handlers, _previous_excepthook crashlogger.disable_crashlogger() + for handler in _handlers: + logger.removeHandler(handler) + handler.close() + _handlers = [] + if sys.excepthook is custom_excepthook: + sys.excepthook = _previous_excepthook + _previous_excepthook = None logging.shutdown()