Skip to content

Commit 52744bd

Browse files
authored
fix: remove dependency on api-core for logging (#1748)
* fix: remove dep on api-core for logging * disable propagation to the root logger * update async helpers tests * fix lint issue
1 parent 8a81854 commit 52744bd

File tree

3 files changed

+219
-81
lines changed

3 files changed

+219
-81
lines changed

google/auth/_helpers.py

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,14 @@
2727

2828
from google.auth import exceptions
2929

30-
try:
31-
# TODO(https://github.com/googleapis/python-api-core/issues/813): Remove `# type: ignore` when
32-
# `google-api-core` type hints issue is resolved.
33-
from google.api_core import client_logging # type: ignore # noqa: F401
3430

35-
CLIENT_LOGGING_SUPPORTED = True # pragma: NO COVER
36-
# TODO(https://github.com/googleapis/google-auth-library-python/issues/1690): Remove `pragma: NO COVER` once
37-
# logging is supported in minimum version of google-api-core.
38-
except ImportError: # pragma: NO COVER
39-
CLIENT_LOGGING_SUPPORTED = False
31+
# _BASE_LOGGER_NAME is the base logger for all google-based loggers.
32+
_BASE_LOGGER_NAME = "google"
33+
34+
# _LOGGING_INITIALIZED ensures that base logger is only configured once
35+
# (unless already configured by the end-user).
36+
_LOGGING_INITIALIZED = False
37+
4038

4139
# The smallest MDS cache used by this library stores tokens until 4 minutes from
4240
# expiry.
@@ -344,6 +342,20 @@ def _hash_value(value, field_name: str) -> Optional[str]:
344342
return f"hashed_{field_name}-{hex_digest}"
345343

346344

345+
def _logger_configured(logger: logging.Logger) -> bool:
346+
"""Determines whether `logger` has non-default configuration
347+
348+
Args:
349+
logger: The logger to check.
350+
351+
Returns:
352+
bool: Whether the logger has any non-default configuration.
353+
"""
354+
return (
355+
logger.handlers != [] or logger.level != logging.NOTSET or not logger.propagate
356+
)
357+
358+
347359
def is_logging_enabled(logger: logging.Logger) -> bool:
348360
"""
349361
Checks if debug logging is enabled for the given logger.
@@ -354,7 +366,23 @@ def is_logging_enabled(logger: logging.Logger) -> bool:
354366
Returns:
355367
True if debug logging is enabled, False otherwise.
356368
"""
357-
return CLIENT_LOGGING_SUPPORTED and logger.isEnabledFor(logging.DEBUG)
369+
# NOTE: Log propagation to the root logger is disabled unless
370+
# the base logger i.e. logging.getLogger("google") is
371+
# explicitly configured by the end user. Ideally this
372+
# needs to happen in the client layer (already does for GAPICs).
373+
# However, this is implemented here to avoid logging
374+
# (if a root logger is configured) when a version of google-auth
375+
# which supports logging is used with:
376+
# - an older version of a GAPIC which does not support logging.
377+
# - Apiary client which does not support logging.
378+
global _LOGGING_INITIALIZED
379+
if not _LOGGING_INITIALIZED:
380+
base_logger = logging.getLogger(_BASE_LOGGER_NAME)
381+
if not _logger_configured(base_logger):
382+
base_logger.propagate = False
383+
_LOGGING_INITIALIZED = True
384+
385+
return logger.isEnabledFor(logging.DEBUG)
358386

359387

360388
def request_log(

tests/aio/test__helpers.py

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,48 +14,64 @@
1414

1515
import json
1616
import logging
17-
from unittest import mock
1817

1918
import pytest # type: ignore
2019

2120
from google.auth.aio import _helpers
2221

22+
# _MOCK_BASE_LOGGER_NAME is the base logger namespace used for testing.
23+
_MOCK_BASE_LOGGER_NAME = "foogle"
24+
25+
# _MOCK_CHILD_LOGGER_NAME is the child logger namespace used for testing.
26+
_MOCK_CHILD_LOGGER_NAME = "foogle.bar"
27+
2328

2429
@pytest.fixture
2530
def logger():
26-
"""Provides a basic logger instance for testing."""
27-
return logging.getLogger(__name__)
31+
"""Returns a child logger for testing."""
32+
logger = logging.getLogger(_MOCK_CHILD_LOGGER_NAME)
33+
logger.level = logging.NOTSET
34+
logger.handlers = []
35+
logger.propagate = True
36+
return logger
37+
38+
39+
@pytest.fixture
40+
def base_logger():
41+
"""Returns a child logger for testing."""
42+
logger = logging.getLogger(_MOCK_BASE_LOGGER_NAME)
43+
logger.level = logging.NOTSET
44+
logger.handlers = []
45+
logger.propagate = True
46+
return logger
2847

2948

3049
@pytest.mark.asyncio
31-
async def test_response_log_debug_enabled(logger, caplog):
32-
logger.setLevel(logging.DEBUG)
33-
with mock.patch("google.auth._helpers.CLIENT_LOGGING_SUPPORTED", True):
34-
await _helpers.response_log_async(logger, {"payload": None})
50+
async def test_response_log_debug_enabled(logger, caplog, base_logger):
51+
caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME)
52+
await _helpers.response_log_async(logger, {"payload": None})
3553
assert len(caplog.records) == 1
3654
record = caplog.records[0]
3755
assert record.message == "Response received..."
3856
assert record.httpResponse == "<class 'NoneType'>"
3957

4058

4159
@pytest.mark.asyncio
42-
async def test_response_log_debug_disabled(logger, caplog):
43-
logger.setLevel(logging.INFO)
44-
with mock.patch("google.auth._helpers.CLIENT_LOGGING_SUPPORTED", True):
45-
await _helpers.response_log_async(logger, "another_response")
60+
async def test_response_log_debug_disabled(logger, caplog, base_logger):
61+
caplog.set_level(logging.INFO, logger=_MOCK_CHILD_LOGGER_NAME)
62+
await _helpers.response_log_async(logger, "another_response")
4663
assert "Response received..." not in caplog.text
4764

4865

4966
@pytest.mark.asyncio
50-
async def test_response_log_debug_enabled_response_json(logger, caplog):
67+
async def test_response_log_debug_enabled_response_json(logger, caplog, base_logger):
5168
class MockResponse:
5269
async def json(self):
5370
return {"key1": "value1", "key2": "value2", "key3": "value3"}
5471

5572
response = MockResponse()
56-
logger.setLevel(logging.DEBUG)
57-
with mock.patch("google.auth._helpers.CLIENT_LOGGING_SUPPORTED", True):
58-
await _helpers.response_log_async(logger, response)
73+
caplog.set_level(logging.DEBUG, logger=_MOCK_CHILD_LOGGER_NAME)
74+
await _helpers.response_log_async(logger, response)
5975
assert len(caplog.records) == 1
6076
record = caplog.records[0]
6177
assert record.message == "Response received..."

0 commit comments

Comments
 (0)