Skip to content

Commit 0fa7895

Browse files
jkebingerclaude
andauthored
Fix InternalLogger to properly register with logging hierarchy (#127)
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 Prefab's 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 Prefab's internal messages. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7920d52 commit 0fa7895

1 file changed

Lines changed: 54 additions & 5 deletions

File tree

prefab_cloud_python/_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)