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 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, + )