fix: stop the remaining Google Ads log lines from writing the exception - #604
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The 36 remaining
exc_info=Truelogging calls undermureo/google_ads/no longer write the exception itself.exc_info=Truewritestraceback.format_exception(), which includesstr(exc).GoogleAdsExceptiondoes not curate its__str__: its__init__(self, error, call, failure, request_id)never passes a message tosuper().__init__(), soBaseExceptionkeeps the raw constructor args and formatting the exception prints the underlyinggrpc.Callrepr — which carriesdebug_error_string(), and with it the request metadata: the developer token and theauthorizationheader. Chaining means aRuntimeErrorcaught 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
type(exc).__name__. Every one is a genericexcept Exceptionaround one of mureo's own high-level methods (self.get_performance_report,self.list_ads,self.diagnose_campaign_delivery, …). What arrives is aRuntimeErrormureo raised itself, or anything else, so there is nofailureto read — only the class can be named. This is the patternmureo/cli/web_auth.py,mureo/amazon_ads/lwa.pyandmureo/google_ads/accounts.pyalready use._extract_error_detail. Account-level sitelinks, conversion-action performance and impression share each wrap aself._search(...)GAQL call directly, so the failure is aGoogleAdsExceptionwhose curated server-sidefailure.errors[0].message("Invalid field in query", a resource error) is the diagnostic worth keeping — safe and more useful than a class name. The surroundingexceptstays 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.pykeeps 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 inreturn str(exc)whenfailure.errorswas empty. That is the same leak, in the fallback, and it also reaches callers: the string is embedded in theRuntimeErrormessages_wrap_mutate_errorraises. It now falls back to the class name, with a test that plants a revealingargstuple and asserts it does not survive.Regression guard
tests/test_google_ads_log_exc_info.pyparses every module undermureo/google_ads/and fails on any logging call carrying anexc_infokeyword, or anylogger.exception()(which setsexc_info=Trueitself). Modelled ontests/test_google_ads_enum_reads.py, with the two properties that file states:test_the_sweep_still_matchesruns 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 theexc_info=Truewritten inaccounts.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