From 3e14ac9ba942e958ec1e27ae7da86015aaff78b1 Mon Sep 17 00:00:00 2001 From: James Kebinger Date: Thu, 20 Nov 2025 10:33:40 -0600 Subject: [PATCH 1/2] Fix InternalLogger to properly register with logging hierarchy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit InternalLogger instances were not being registered with Python's logging manager, causing them to have no parent logger and preventing handler propagation. This meant that: - basicConfig() had no effect on InternalLogger instances - They couldn't inherit handlers from parent loggers - Users couldn't see SDK internal logs without manual configuration This commit fixes the issue by: 1. Registering InternalLogger instances with logging.Logger.manager.loggerDict during __init__, ensuring they participate in the logging hierarchy 2. Setting up parent loggers properly (adapted from Python's logging internals) so handlers propagate correctly 3. Fixing the prefab_internal extra attribute by overriding _log() instead of log(), since info(), debug(), etc. call _log() directly The fix is compatible with both: - Standard logging (logging.basicConfig, manual handler configuration) - Structlog (when configured to use stdlib logging) The prefab_internal=True extra attribute is still added to all log records from InternalLogger instances, allowing users to filter/identify SDK internal messages. Ported from: https://github.com/prefab-cloud/prefab-cloud-python/pull/127 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- sdk_reforge/_internal_logging.py | 59 +++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/sdk_reforge/_internal_logging.py b/sdk_reforge/_internal_logging.py index 4e4410d..d8a79fa 100644 --- a/sdk_reforge/_internal_logging.py +++ b/sdk_reforge/_internal_logging.py @@ -69,9 +69,58 @@ def __init__(self, name: str, level: int = logging.NOTSET) -> None: super().__init__(name, level) self.thread_local = threading.local() - def log(self, level: int, msg, *args, **kwargs) -> None: + # Register this logger with the logging manager so it can participate + # in the logger hierarchy and inherit handlers from parent loggers + logging.Logger.manager.loggerDict[name] = self + + # Set up the parent logger in the hierarchy + # This is adapted from logging.Logger.manager._fixupParents + i = name.rfind(".") + rv = None + while (i > 0) and not rv: + substr = name[:i] + if substr not in logging.Logger.manager.loggerDict: + logging.Logger.manager.loggerDict[substr] = logging.PlaceHolder(self) + else: + obj = logging.Logger.manager.loggerDict[substr] + if isinstance(obj, logging.Logger): + rv = obj + else: + # It's a PlaceHolder + obj.append(self) + i = name.rfind(".", 0, i - 1) + if not rv: + rv = logging.root + self.parent = rv + + def _log( + self, + level: int, + msg, + args, + exc_info=None, + extra=None, + stack_info=False, + stacklevel=1, + ) -> None: + """ + Override _log to add prefab_internal to extra. + This is called by info(), debug(), warning(), error(), etc. + """ if not ReentrancyCheck.is_set(): - extras = kwargs.pop("extra", {}) - extras["prefab_internal"] = True - # Pass the possibly-modified 'extra' dictionary to the underlying logger - super().log(level, msg, *args, extra=extras, **kwargs) + if extra is None: + extra = {} + else: + # Make a copy to avoid modifying the caller's dict + extra = extra.copy() + extra["prefab_internal"] = True + + super()._log( + level, + msg, + args, + exc_info=exc_info, + extra=extra, + stack_info=stack_info, + stacklevel=stacklevel, + ) From 3924982716ab0238a2057316f12df749281489ef Mon Sep 17 00:00:00 2001 From: James Kebinger Date: Thu, 20 Nov 2025 10:54:55 -0600 Subject: [PATCH 2/2] Bump version to 1.1.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CHANGELOG.md | 4 ++++ pyproject.toml | 2 +- sdk_reforge/VERSION | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c05f8d2..9bc3cac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [1.1.1] - 2025-11-20 + +- Fix InternalLogger to properly register with Python's logging hierarchy, enabling handler propagation from `logging.basicConfig()` and parent loggers [#18] + ## [0.12.0] - 2025-04-14 - Add special case handling for context `prefab.current-time` to return the current time (UTC) to round out new operator support [#124] diff --git a/pyproject.toml b/pyproject.toml index 1a70186..b7ed9b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "sdk-reforge" -version = "1.1.0" +version = "1.1.1" description = "Python sdk for Reforge Feature Flags and Config as a Service: https://www.reforge.com" license = "MIT" authors = ["Michael Berkowitz ", "James Kebinger "] diff --git a/sdk_reforge/VERSION b/sdk_reforge/VERSION index 9084fa2..524cb55 100644 --- a/sdk_reforge/VERSION +++ b/sdk_reforge/VERSION @@ -1 +1 @@ -1.1.0 +1.1.1