Skip to content

Commit 3f2bc95

Browse files
jkebingerclaude
andcommitted
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 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 14b242a commit 3f2bc95

File tree

1 file changed

+54
-5
lines changed

1 file changed

+54
-5
lines changed

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)