From f47d03a3c15a3d3371b42f12f0c5b209175137dc Mon Sep 17 00:00:00 2001 From: Christopher Toth Date: Thu, 29 Oct 2026 12:18:13 -0600 Subject: [PATCH] Stage logging handlers before installing configuration --- agent-notes/improve-atomic-initialization.md | 19 +++++ logger_setup/logger_setup.py | 76 ++++++++++++-------- 2 files changed, 65 insertions(+), 30 deletions(-) create mode 100644 agent-notes/improve-atomic-initialization.md diff --git a/agent-notes/improve-atomic-initialization.md b/agent-notes/improve-atomic-initialization.md new file mode 100644 index 0000000..a48c37c --- /dev/null +++ b/agent-notes/improve-atomic-initialization.md @@ -0,0 +1,19 @@ +# Goal + +Keep a failed logging initialization from installing partial handlers or replacing the exception hook. + +# State + +Construct and format handlers before attaching them or changing process logging state. Close constructed handlers when a later setup step fails. This PR is independently based on integrated master and contains no other open PR's commits. + +# Decisions and why + +The framework catches setup failures and continues. Leaving an error handler installed when opening the debug file fails previously made its basicConfig fallback a no-op. A failure now preserves pre-existing root handlers, root level and exception hook. Fatal handling is enabled only after handler construction succeeds. File opens may still create or truncate files; this change does not roll back their contents. + +# Surprises + +create_handler is an internal module helper, not exported by the package. Its callers now receive an unattached handler; setup_logging attaches only the completed set. No consumer call site changes are required. + +# Next step + +Read the existing package checks. The separate app_framework failure-reporting PR makes its fallback status accurate. No merge or release. diff --git a/logger_setup/logger_setup.py b/logger_setup/logger_setup.py index 92a91a9..9f35f8e 100644 --- a/logger_setup/logger_setup.py +++ b/logger_setup/logger_setup.py @@ -27,9 +27,12 @@ def create_handler( handler = handler_class() else: handler = handler_class(filename, mode=handler_mode, encoding="utf-8") - handler.setLevel(level) - handler.setFormatter(formatter) - logger.addHandler(handler) + try: + handler.setLevel(level) + handler.setFormatter(formatter) + except BaseException: + handler.close() + raise return handler @@ -52,35 +55,48 @@ def setup_logging( remote_address=None, remote_level=logging.ERROR, ): + formatter = logging.Formatter(message_format) + handlers = [] + try: + if error_log: + error_handler = create_handler(error_log, logging.WARNING, formatter=formatter) + handlers.append(error_handler) + if debug_log: + handlers.append(create_handler(debug_log, logging.DEBUG, formatter=formatter)) + if console_level: + handlers.append(create_handler( + level=console_level, + handler_class=logging.StreamHandler, + formatter=formatter, + )) + if remote_address: + remote_format = ( + "1 %(asctime)s - " + + application_name + + " - - version: %s" % application_version + + " thread: %(thread)d function: %(module)s.%(funcName)s:\n%(message)s" + ) + remote_date_format = "%Y-%m-%dT%H:%M:%SZ" + remote_formatter = logging.Formatter(remote_format, datefmt=remote_date_format) + remote_handler = create_handler( + filename=None, + level=remote_level, + handler_class=lambda: logging.handlers.SysLogHandler(address=remote_address), + formatter=remote_formatter, + ) + handlers.append(remote_handler) + if error_log and log_crashes: + crashlogger.enable_crashlogger(error_handler) + except BaseException: + for handler in handlers: + handler.close() + raise + + for handler in handlers: + logger.addHandler(handler) + logger.setLevel(logging.DEBUG) if log_unhandled_exceptions: sys.excepthook = custom_excepthook - logger.setLevel(logging.DEBUG) - formatter = logging.Formatter(message_format) - if error_log: - error_handler = create_handler(error_log, logging.WARNING, formatter=formatter) - if debug_log: - create_handler(debug_log, logging.DEBUG, formatter=formatter) - if console_level: - console_handler = create_handler( - level=console_level, - handler_class=logging.StreamHandler, - formatter=formatter, - ) - if error_log and log_crashes: - crashlogger.enable_crashlogger(error_handler) - if remote_address: - remote_format = ( - "1 %(asctime)s - " - + application_name - + " - - version: %s" % application_version - + " thread: %(thread)d function: %(module)s.%(funcName)s:\n%(message)s" - ) - remote_date_format = "%Y-%m-%dT%H:%M:%SZ" - remote_handler = logging.handlers.SysLogHandler(address=remote_address) - remote_handler.setLevel(remote_level) - remote_formatter = logging.Formatter(remote_format, datefmt=remote_date_format) - remote_handler.setFormatter(remote_formatter) - logger.addHandler(remote_handler) logger.info("Logging initialized. PID: %d", os.getpid()) return logger