Skip to content

Commit 245b9f2

Browse files
jkebingerclaude
andauthored
Fix InternalLogger logging hierarchy and bump to v1.1.1 (#18)
* Fix InternalLogger to properly register with logging hierarchy 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: prefab-cloud/prefab-cloud-python#127 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Bump version to 1.1.1 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 46de1e6 commit 245b9f2

4 files changed

Lines changed: 60 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [1.1.1] - 2025-11-20
4+
5+
- Fix InternalLogger to properly register with Python's logging hierarchy, enabling handler propagation from `logging.basicConfig()` and parent loggers [#18]
6+
37
## [0.12.0] - 2025-04-14
48

59
- Add special case handling for context `prefab.current-time` to return the current time (UTC) to round out new operator support [#124]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "sdk-reforge"
3-
version = "1.1.0"
3+
version = "1.1.1"
44
description = "Python sdk for Reforge Feature Flags and Config as a Service: https://www.reforge.com"
55
license = "MIT"
66
authors = ["Michael Berkowitz <michael.berkowitz@gmail.com>", "James Kebinger <james.kebinger@reforge.com>"]

sdk_reforge/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.1.0
1+
1.1.1

sdk_reforge/_internal_logging.py

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,58 @@ def __init__(self, name: str, level: int = logging.NOTSET) -> None:
6969
super().__init__(name, level)
7070
self.thread_local = threading.local()
7171

72-
def log(self, level: int, msg, *args, **kwargs) -> None:
72+
# Register this logger with the logging manager so it can participate
73+
# in the logger hierarchy and inherit handlers from parent loggers
74+
logging.Logger.manager.loggerDict[name] = self
75+
76+
# Set up the parent logger in the hierarchy
77+
# This is adapted from logging.Logger.manager._fixupParents
78+
i = name.rfind(".")
79+
rv = None
80+
while (i > 0) and not rv:
81+
substr = name[:i]
82+
if substr not in logging.Logger.manager.loggerDict:
83+
logging.Logger.manager.loggerDict[substr] = logging.PlaceHolder(self)
84+
else:
85+
obj = logging.Logger.manager.loggerDict[substr]
86+
if isinstance(obj, logging.Logger):
87+
rv = obj
88+
else:
89+
# It's a PlaceHolder
90+
obj.append(self)
91+
i = name.rfind(".", 0, i - 1)
92+
if not rv:
93+
rv = logging.root
94+
self.parent = rv
95+
96+
def _log(
97+
self,
98+
level: int,
99+
msg,
100+
args,
101+
exc_info=None,
102+
extra=None,
103+
stack_info=False,
104+
stacklevel=1,
105+
) -> None:
106+
"""
107+
Override _log to add prefab_internal to extra.
108+
This is called by info(), debug(), warning(), error(), etc.
109+
"""
73110
if not ReentrancyCheck.is_set():
74-
extras = kwargs.pop("extra", {})
75-
extras["prefab_internal"] = True
76-
# Pass the possibly-modified 'extra' dictionary to the underlying logger
77-
super().log(level, msg, *args, extra=extras, **kwargs)
111+
if extra is None:
112+
extra = {}
113+
else:
114+
# Make a copy to avoid modifying the caller's dict
115+
extra = extra.copy()
116+
extra["prefab_internal"] = True
117+
118+
super()._log(
119+
level,
120+
msg,
121+
args,
122+
exc_info=exc_info,
123+
extra=extra,
124+
stack_info=stack_info,
125+
stacklevel=stacklevel,
126+
)

0 commit comments

Comments
 (0)