Skip to content

fix: stop the remaining Google Ads log lines from writing the exception - #604

Merged
hyoshi merged 1 commit into
mainfrom
fix/google-ads-exc-info
Aug 13, 2026
Merged

fix: stop the remaining Google Ads log lines from writing the exception#604
hyoshi merged 1 commit into
mainfrom
fix/google-ads-exc-info

Conversation

@hyoshi

@hyoshi hyoshi commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

What

The 36 remaining exc_info=True logging calls under mureo/google_ads/ no longer write the exception itself.

exc_info=True writes traceback.format_exception(), which includes str(exc). GoogleAdsException does not curate its __str__: its __init__(self, error, call, failure, request_id) never passes a message to super().__init__(), so BaseException keeps the raw constructor args and formatting the exception prints the underlying grpc.Call repr — which carries debug_error_string(), and with it the request metadata: the developer token and the authorization header. Chaining means a RuntimeError caught one level up prints the same bytes, because a traceback follows __cause__.

Nothing in the package installed a logging handler until #581, so these lines were discarded at runtime. #581 fixed the three sites reachable from the configure server; this is the rest. They sit on MCP tool paths, so today the exposure depends on what the host process has switched on rather than on mureo — lower urgency than the three already fixed, not safe.

How each site was decided

  • 33 sites → type(exc).__name__. Every one is a generic except Exception around one of mureo's own high-level methods (self.get_performance_report, self.list_ads, self.diagnose_campaign_delivery, …). What arrives is a RuntimeError mureo raised itself, or anything else, so there is no failure to read — only the class can be named. This is the pattern mureo/cli/web_auth.py, mureo/amazon_ads/lwa.py and mureo/google_ads/accounts.py already use.
  • 3 sites → _extract_error_detail. Account-level sitelinks, conversion-action performance and impression share each wrap a self._search(...) GAQL call directly, so the failure is a GoogleAdsException whose curated server-side failure.errors[0].message ("Invalid field in query", a resource error) is the diagnostic worth keeping — safe and more useful than a class name. The surrounding except stays broad, so they fall back to the class name for anything without a .failure, following the shape already in _extensions_targeting.py.

Comment style follows the existing one: accounts.py keeps the full explanation, and each module carries one short pointer to it at its first site rather than repeating it 36 times.

One thing found on the way

_extract_error_detail — the function offered as the safe alternative — ended in return str(exc) when failure.errors was empty. That is the same leak, in the fallback, and it also reaches callers: the string is embedded in the RuntimeError messages _wrap_mutate_error raises. It now falls back to the class name, with a test that plants a revealing args tuple and asserts it does not survive.

Regression guard

tests/test_google_ads_log_exc_info.py parses every module under mureo/google_ads/ and fails on any logging call carrying an exc_info keyword, or any logger.exception() (which sets exc_info=True itself). Modelled on tests/test_google_ads_enum_reads.py, with the two properties that file states:

  • Derived, never enumerated — the call sites come from the tree, so a new module is covered the moment it is added.
  • Extraction is asserted, not assumedtest_the_sweep_still_matches runs the extractor over planted source, so a sweep that quietly stopped matching cannot turn the file into a green no-op. A third test pins the other direction: the AST sweep does not fire on the exc_info=True written in accounts.py's comment, which is why this is not a text grep.

Verification

  • pytest tests/test_google_ads_log_exc_info.py — red at 36 sites before the fix, green after.
  • pytest — 8968 passed. The 12 failures are pre-existing local plugin-discovery noise (test_live_clients.py, test_mcp_server.py, test_mcp_server_plugin_wiring.py, test_mcp_tool_provider.py) and are unchanged by this branch.
  • black --check mureo/ tests/, ruff check mureo/ tests/, mypy mureo/ — clean (mypy's 14 remaining errors are all missing third-party stubs, unchanged).

Closes #603

exc_info=True writes traceback.format_exception(), which includes str(exc).
GoogleAdsException does not curate its __str__: its __init__(self, error, call,
failure, request_id) never passes a message to super().__init__(), so
BaseException keeps the raw constructor args and formatting the exception
prints the underlying grpc.Call repr - which carries debug_error_string(), and
with it the request metadata: the developer token and the authorization header.
Chaining means a RuntimeError caught one level up prints the same bytes,
because a traceback follows __cause__.

Nothing in the package installed a handler until #581, so these lines were
discarded at runtime. #581 fixed the three sites reachable from the configure
server; this is the remaining 36, on MCP tool paths, where the exposure depends
on what the host process has switched on rather than on mureo.

33 sites now log type(exc).__name__, the pattern the rest of the codebase
already uses. The three that wrap a GAQL search directly - account-level
sitelinks, conversion-action performance, impression share - log the curated
server-side failure.errors[0].message via _extract_error_detail instead, which
is safe and more useful than a bare class name; the surrounding except is broad,
so they fall back to the class name for anything without a .failure.

_extract_error_detail itself returned str(exc) when the failure carried no
errors - the same leak, in the function offered as the safe alternative. It now
falls back to the class name.

tests/test_google_ads_log_exc_info.py sweeps the package's AST and fails on a
new exc_info= or logger.exception() anywhere under mureo/google_ads/. The sweep
is derived from the tree rather than a list, and asserted against planted source
so an extractor that stopped matching cannot turn it into a green no-op.

Closes #603
@hyoshi
hyoshi merged commit fe41687 into main Aug 13, 2026
13 checks passed
@hyoshi
hyoshi deleted the fix/google-ads-exc-info branch August 13, 2026 00:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

google_ads logs SDK exceptions with exc_info=True, which prints request metadata

1 participant