From 7b1605cbc20ebbb5d4975d76c746a125ab5c790d Mon Sep 17 00:00:00 2001 From: ohmayr Date: Mon, 4 Nov 2024 20:28:20 +0000 Subject: [PATCH 01/16] feat: add base logger to enable debug logging --- google/api_core/client_logging.py | 73 +++++++++++++++++++++++++++++++ tests/unit/test_client_logging.py | 17 +++++++ 2 files changed, 90 insertions(+) create mode 100644 google/api_core/client_logging.py create mode 100644 tests/unit/test_client_logging.py diff --git a/google/api_core/client_logging.py b/google/api_core/client_logging.py new file mode 100644 index 00000000..705c84b0 --- /dev/null +++ b/google/api_core/client_logging.py @@ -0,0 +1,73 @@ +import logging +import json +import re +import os + +LOGGING_INITIALIZED = False + +# TODO(): Update Request / Response messages. +REQUEST_MESSAGE = "Sending request ..." +RESPONSE_MESSAGE = "Receiving response ..." + +# TODO(): Update this list to support additional logging fields +_recognized_logging_fields = ["httpRequest", "rpcName", "serviceName"] # Additional fields to be Logged. + +def logger_configured(logger): + return logger.hasHandlers() or logger.level != logging.NOTSET + +def initialize_logging(): + global LOGGING_INITIALIZED + if LOGGING_INITIALIZED: + return + scopes = os.getenv("GOOGLE_SDK_PYTHON_LOGGING_SCOPE") + setup_logging(scopes) + LOGGING_INITIALIZED = True + +def parse_logging_scopes(scopes): + if not scopes: + return [] + # TODO(): check if the namespace is a valid namespace. + # TODO(): parse a list of namespaces. Current flow expects a single string for now. + namespaces = [scopes] + return namespaces + +def default_settings(logger): + if not logger_configured(logger): + console_handler = logging.StreamHandler() + logger.setLevel("DEBUG") + logger.propagate = False + formatter = StructuredLogFormatter() + console_handler.setFormatter(formatter) + logger.addHandler(console_handler) + +def setup_logging(scopes): + # disable log propagation at base logger level to the root logger only if a base logger is not already configured via code changes. + base_logger = logging.getLogger("google") + if not logger_configured(base_logger): + base_logger.propagate = False + + # only returns valid logger scopes (namespaces) + # this list has at most one element. + loggers = parse_logging_scopes(scopes) + + for namespace in loggers: + # This will either create a module level logger or get the reference of the base logger instantiated above. + logger = logging.getLogger(namespace) + + # Set default settings. + default_settings(logger) + +class StructuredLogFormatter(logging.Formatter): + def format(self, record): + log_obj = { + 'timestamp': self.formatTime(record), + 'severity': record.levelname, + 'name': record.name, + 'message': record.getMessage(), + } + + for field_name in _recognized_logging_fields: + value = getattr(record, field_name, None) + if value is not None: + log_obj[field_name] = value + return json.dumps(log_obj) diff --git a/tests/unit/test_client_logging.py b/tests/unit/test_client_logging.py new file mode 100644 index 00000000..5e03b64b --- /dev/null +++ b/tests/unit/test_client_logging.py @@ -0,0 +1,17 @@ +import logging +import pytest + +from google.api_core.client_logging import BaseLogger + + +def test_base_logger(caplog): + + logger = BaseLogger().get_logger() + + with caplog.at_level(logging.INFO, logger="google"): + logger.info("This is a test message.") + + assert "This is a test message." in caplog.text + assert caplog.records[0].name == "google" + assert caplog.records[0].levelname == "INFO" + assert caplog.records[0].message == "This is a test message." From 48b9ed92bd13337808c1bbf52dda2457e0fcab46 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Mon, 25 Nov 2024 19:21:43 +0000 Subject: [PATCH 02/16] address PR feedback --- google/api_core/client_logging.py | 16 ++++++++-------- tests/unit/test_client_logging.py | 23 +++++++++++++---------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/google/api_core/client_logging.py b/google/api_core/client_logging.py index 705c84b0..ba445916 100644 --- a/google/api_core/client_logging.py +++ b/google/api_core/client_logging.py @@ -3,7 +3,7 @@ import re import os -LOGGING_INITIALIZED = False +_LOGGING_INITIALIZED = False # TODO(): Update Request / Response messages. REQUEST_MESSAGE = "Sending request ..." @@ -13,15 +13,15 @@ _recognized_logging_fields = ["httpRequest", "rpcName", "serviceName"] # Additional fields to be Logged. def logger_configured(logger): - return logger.hasHandlers() or logger.level != logging.NOTSET + return logger.hasHandlers() or logger.level != logging.NOTSET or logger.propagate == False def initialize_logging(): - global LOGGING_INITIALIZED - if LOGGING_INITIALIZED: + global _LOGGING_INITIALIZED + if _LOGGING_INITIALIZED: return scopes = os.getenv("GOOGLE_SDK_PYTHON_LOGGING_SCOPE") setup_logging(scopes) - LOGGING_INITIALIZED = True + _LOGGING_INITIALIZED = True def parse_logging_scopes(scopes): if not scopes: @@ -31,7 +31,7 @@ def parse_logging_scopes(scopes): namespaces = [scopes] return namespaces -def default_settings(logger): +def configure_defaults(logger): if not logger_configured(logger): console_handler = logging.StreamHandler() logger.setLevel("DEBUG") @@ -54,8 +54,8 @@ def setup_logging(scopes): # This will either create a module level logger or get the reference of the base logger instantiated above. logger = logging.getLogger(namespace) - # Set default settings. - default_settings(logger) + # Configure default settings. + configure_defaults(logger) class StructuredLogFormatter(logging.Formatter): def format(self, record): diff --git a/tests/unit/test_client_logging.py b/tests/unit/test_client_logging.py index 5e03b64b..216dae7f 100644 --- a/tests/unit/test_client_logging.py +++ b/tests/unit/test_client_logging.py @@ -1,17 +1,20 @@ import logging import pytest -from google.api_core.client_logging import BaseLogger +# Test expected behaviour for warnings, propagation, handler + formatter. +def test_setup_logging_w_no_scopes(): + # TODO(in-progress): + pass -def test_base_logger(caplog): +def test_setup_logging_w_base_scope(): + # TODO(in-progress): + pass - logger = BaseLogger().get_logger() +def test_setup_logging_w_module_scope(): + # TODO(in-progress): + pass - with caplog.at_level(logging.INFO, logger="google"): - logger.info("This is a test message.") - - assert "This is a test message." in caplog.text - assert caplog.records[0].name == "google" - assert caplog.records[0].levelname == "INFO" - assert caplog.records[0].message == "This is a test message." +def test_setup_logging_w_incorrect_scope(): + # TODO(in-progress): + pass From ef66ac574e8b41ef16ba74e769ca81c840dc6b8c Mon Sep 17 00:00:00 2001 From: ohmayr Date: Mon, 25 Nov 2024 21:54:41 +0000 Subject: [PATCH 03/16] address PR feedback --- google/api_core/client_logging.py | 29 +++++++++------- tests/unit/test_client_logging.py | 58 ++++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 21 deletions(-) diff --git a/google/api_core/client_logging.py b/google/api_core/client_logging.py index ba445916..a2485e76 100644 --- a/google/api_core/client_logging.py +++ b/google/api_core/client_logging.py @@ -4,16 +4,16 @@ import os _LOGGING_INITIALIZED = False - -# TODO(): Update Request / Response messages. +_BASE_LOGGER_NAME = "google" +# TODO(https://github.com/googleapis/python-api-core/issues/760): Update Request / Response messages. REQUEST_MESSAGE = "Sending request ..." RESPONSE_MESSAGE = "Receiving response ..." -# TODO(): Update this list to support additional logging fields +# TODO(https://github.com/googleapis/python-api-core/issues/761): Update this list to support additional logging fields _recognized_logging_fields = ["httpRequest", "rpcName", "serviceName"] # Additional fields to be Logged. def logger_configured(logger): - return logger.hasHandlers() or logger.level != logging.NOTSET or logger.propagate == False + return logger.handlers != [] or logger.level != logging.NOTSET or logger.propagate == False def initialize_logging(): global _LOGGING_INITIALIZED @@ -26,8 +26,9 @@ def initialize_logging(): def parse_logging_scopes(scopes): if not scopes: return [] - # TODO(): check if the namespace is a valid namespace. - # TODO(): parse a list of namespaces. Current flow expects a single string for now. + # TODO(https://github.com/googleapis/python-api-core/issues/759): check if the namespace is a valid namespace. + # TODO(b/380481951): Support logging multiple scopes. + # TODO(b/380483756): Raise or log a warning for an invalid scope. namespaces = [scopes] return namespaces @@ -40,23 +41,25 @@ def configure_defaults(logger): console_handler.setFormatter(formatter) logger.addHandler(console_handler) -def setup_logging(scopes): - # disable log propagation at base logger level to the root logger only if a base logger is not already configured via code changes. - base_logger = logging.getLogger("google") - if not logger_configured(base_logger): - base_logger.propagate = False +def setup_logging(scopes=[]): # only returns valid logger scopes (namespaces) # this list has at most one element. - loggers = parse_logging_scopes(scopes) + logger_names = parse_logging_scopes(scopes) - for namespace in loggers: + for namespace in logger_names: # This will either create a module level logger or get the reference of the base logger instantiated above. logger = logging.getLogger(namespace) # Configure default settings. configure_defaults(logger) + # disable log propagation at base logger level to the root logger only if a base logger is not already configured via code changes. + # Maybe we do this at the end? + base_logger = logging.getLogger(_BASE_LOGGER_NAME) + if not logger_configured(base_logger): + base_logger.propagate = False + class StructuredLogFormatter(logging.Formatter): def format(self, record): log_obj = { diff --git a/tests/unit/test_client_logging.py b/tests/unit/test_client_logging.py index 216dae7f..1654b122 100644 --- a/tests/unit/test_client_logging.py +++ b/tests/unit/test_client_logging.py @@ -1,20 +1,62 @@ import logging import pytest +from google.api_core.client_logging import setup_logging # Test expected behaviour for warnings, propagation, handler + formatter. +def reset_logger(scope): + logger = logging.getLogger(scope) + logger.handlers = [] + logger.setLevel(logging.NOTSET) + logger.propagate = True + def test_setup_logging_w_no_scopes(): - # TODO(in-progress): - pass + setup_logging() + base_logger = logging.getLogger("google") + assert base_logger.handlers == [] + assert base_logger.propagate == False + assert base_logger.level == logging.NOTSET + + reset_logger("google") + def test_setup_logging_w_base_scope(): - # TODO(in-progress): - pass + setup_logging("google") + base_logger = logging.getLogger("google") + assert isinstance(base_logger.handlers[0], logging.StreamHandler) + assert base_logger.propagate == False + assert base_logger.level == logging.DEBUG + + reset_logger("google") def test_setup_logging_w_module_scope(): - # TODO(in-progress): - pass + setup_logging("google.foo") + + base_logger = logging.getLogger("google") + assert base_logger.handlers == [] + assert base_logger.propagate == False + assert base_logger.level == logging.NOTSET + + module_logger = logging.getLogger("google.foo") + assert isinstance(module_logger.handlers[0], logging.StreamHandler) + assert module_logger.propagate == False + assert module_logger.level == logging.DEBUG + + + reset_logger("google") def test_setup_logging_w_incorrect_scope(): - # TODO(in-progress): - pass + setup_logging("foo") + + base_logger = logging.getLogger("google") + assert base_logger.handlers == [] + assert base_logger.propagate == False + assert base_logger.level == logging.NOTSET + + # TODO: update test once we add logic to ignore an incorrect scope. + logger = logging.getLogger("foo") + assert isinstance(logger.handlers[0], logging.StreamHandler) + assert logger.propagate == False + assert logger.level == logging.DEBUG + + reset_logger("google") From a42c5ac55ac248525e00dce93abc10abf6db4f86 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Tue, 26 Nov 2024 17:47:08 +0000 Subject: [PATCH 04/16] add tests --- tests/unit/test_client_logging.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_client_logging.py b/tests/unit/test_client_logging.py index 1654b122..8dc4d01d 100644 --- a/tests/unit/test_client_logging.py +++ b/tests/unit/test_client_logging.py @@ -2,7 +2,9 @@ import pytest from google.api_core.client_logging import setup_logging -# Test expected behaviour for warnings, propagation, handler + formatter. + +# TODO: We should not be testing against the "google" logger +# and should mock `base_logger` instead. def reset_logger(scope): logger = logging.getLogger(scope) From ff7792bed99929dfbf087de0456dcb66bd3d5d05 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Tue, 26 Nov 2024 17:49:45 +0000 Subject: [PATCH 05/16] address PR feedback --- google/api_core/client_logging.py | 5 ++--- tests/unit/test_client_logging.py | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/google/api_core/client_logging.py b/google/api_core/client_logging.py index a2485e76..8497ca42 100644 --- a/google/api_core/client_logging.py +++ b/google/api_core/client_logging.py @@ -6,8 +6,8 @@ _LOGGING_INITIALIZED = False _BASE_LOGGER_NAME = "google" # TODO(https://github.com/googleapis/python-api-core/issues/760): Update Request / Response messages. -REQUEST_MESSAGE = "Sending request ..." -RESPONSE_MESSAGE = "Receiving response ..." +REQUEST_MESSAGE = "Sending request " +RESPONSE_MESSAGE = "Receiving response " # TODO(https://github.com/googleapis/python-api-core/issues/761): Update this list to support additional logging fields _recognized_logging_fields = ["httpRequest", "rpcName", "serviceName"] # Additional fields to be Logged. @@ -55,7 +55,6 @@ def setup_logging(scopes=[]): configure_defaults(logger) # disable log propagation at base logger level to the root logger only if a base logger is not already configured via code changes. - # Maybe we do this at the end? base_logger = logging.getLogger(_BASE_LOGGER_NAME) if not logger_configured(base_logger): base_logger.propagate = False diff --git a/tests/unit/test_client_logging.py b/tests/unit/test_client_logging.py index 8dc4d01d..5847f971 100644 --- a/tests/unit/test_client_logging.py +++ b/tests/unit/test_client_logging.py @@ -55,10 +55,11 @@ def test_setup_logging_w_incorrect_scope(): assert base_logger.propagate == False assert base_logger.level == logging.NOTSET - # TODO: update test once we add logic to ignore an incorrect scope. + # TODO(https://github.com/googleapis/python-api-core/issues/759): update test once we add logic to ignore an incorrect scope. logger = logging.getLogger("foo") assert isinstance(logger.handlers[0], logging.StreamHandler) assert logger.propagate == False assert logger.level == logging.DEBUG reset_logger("google") + reset_logger("foo") From 65708ff00129fc57b8a6676f7f1303ded0211d43 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Tue, 26 Nov 2024 17:51:24 +0000 Subject: [PATCH 06/16] address PR feedback --- tests/unit/test_client_logging.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/test_client_logging.py b/tests/unit/test_client_logging.py index 5847f971..a61bc9f9 100644 --- a/tests/unit/test_client_logging.py +++ b/tests/unit/test_client_logging.py @@ -46,6 +46,7 @@ def test_setup_logging_w_module_scope(): reset_logger("google") + reset_logger("google.foo") def test_setup_logging_w_incorrect_scope(): setup_logging("foo") From 0f1a4bd7c592860db23e8b72c1bc0221efcb2804 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Tue, 26 Nov 2024 18:48:10 +0000 Subject: [PATCH 07/16] address PR feedback --- google/api_core/client_logging.py | 66 +++++++++++++++++------------ tests/unit/test_client_logging.py | 69 ++++++++++++++++++++++--------- 2 files changed, 89 insertions(+), 46 deletions(-) diff --git a/google/api_core/client_logging.py b/google/api_core/client_logging.py index 8497ca42..2d3488d6 100644 --- a/google/api_core/client_logging.py +++ b/google/api_core/client_logging.py @@ -10,30 +10,42 @@ RESPONSE_MESSAGE = "Receiving response " # TODO(https://github.com/googleapis/python-api-core/issues/761): Update this list to support additional logging fields -_recognized_logging_fields = ["httpRequest", "rpcName", "serviceName"] # Additional fields to be Logged. +_recognized_logging_fields = [ + "httpRequest", + "rpcName", + "serviceName", +] # Additional fields to be Logged. + def logger_configured(logger): - return logger.handlers != [] or logger.level != logging.NOTSET or logger.propagate == False + return ( + logger.handlers != [] + or logger.level != logging.NOTSET + or logger.propagate == False + ) + def initialize_logging(): - global _LOGGING_INITIALIZED - if _LOGGING_INITIALIZED: - return - scopes = os.getenv("GOOGLE_SDK_PYTHON_LOGGING_SCOPE") - setup_logging(scopes) - _LOGGING_INITIALIZED = True + global _LOGGING_INITIALIZED + if _LOGGING_INITIALIZED: + return + scopes = os.getenv("GOOGLE_SDK_PYTHON_LOGGING_SCOPE", "") + setup_logging(scopes) + _LOGGING_INITIALIZED = True + def parse_logging_scopes(scopes): - if not scopes: - return [] - # TODO(https://github.com/googleapis/python-api-core/issues/759): check if the namespace is a valid namespace. - # TODO(b/380481951): Support logging multiple scopes. - # TODO(b/380483756): Raise or log a warning for an invalid scope. - namespaces = [scopes] - return namespaces + if not scopes: + return [] + # TODO(https://github.com/googleapis/python-api-core/issues/759): check if the namespace is a valid namespace. + # TODO(b/380481951): Support logging multiple scopes. + # TODO(b/380483756): Raise or log a warning for an invalid scope. + namespaces = [scopes] + return namespaces + def configure_defaults(logger): - if not logger_configured(logger): + if not logger_configured(logger): console_handler = logging.StreamHandler() logger.setLevel("DEBUG") logger.propagate = False @@ -41,31 +53,33 @@ def configure_defaults(logger): console_handler.setFormatter(formatter) logger.addHandler(console_handler) + def setup_logging(scopes=[]): - + # only returns valid logger scopes (namespaces) # this list has at most one element. - logger_names = parse_logging_scopes(scopes) + logger_names = parse_logging_scopes(scopes) for namespace in logger_names: - # This will either create a module level logger or get the reference of the base logger instantiated above. - logger = logging.getLogger(namespace) + # This will either create a module level logger or get the reference of the base logger instantiated above. + logger = logging.getLogger(namespace) - # Configure default settings. - configure_defaults(logger) + # Configure default settings. + configure_defaults(logger) # disable log propagation at base logger level to the root logger only if a base logger is not already configured via code changes. base_logger = logging.getLogger(_BASE_LOGGER_NAME) if not logger_configured(base_logger): base_logger.propagate = False + class StructuredLogFormatter(logging.Formatter): def format(self, record): log_obj = { - 'timestamp': self.formatTime(record), - 'severity': record.levelname, - 'name': record.name, - 'message': record.getMessage(), + "timestamp": self.formatTime(record), + "severity": record.levelname, + "name": record.name, + "message": record.getMessage(), } for field_name in _recognized_logging_fields: diff --git a/tests/unit/test_client_logging.py b/tests/unit/test_client_logging.py index a61bc9f9..fcbd19cb 100644 --- a/tests/unit/test_client_logging.py +++ b/tests/unit/test_client_logging.py @@ -1,66 +1,95 @@ import logging import pytest +import mock -from google.api_core.client_logging import setup_logging +from google.api_core.client_logging import ( + setup_logging, + parse_logging_scopes, + initialize_logging, +) -# TODO: We should not be testing against the "google" logger -# and should mock `base_logger` instead. def reset_logger(scope): logger = logging.getLogger(scope) logger.handlers = [] logger.setLevel(logging.NOTSET) logger.propagate = True - + + def test_setup_logging_w_no_scopes(): - setup_logging() - base_logger = logging.getLogger("google") - assert base_logger.handlers == [] - assert base_logger.propagate == False - assert base_logger.level == logging.NOTSET + with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): + setup_logging() + base_logger = logging.getLogger("foo") + assert base_logger.handlers == [] + assert base_logger.propagate == False + assert base_logger.level == logging.NOTSET reset_logger("google") def test_setup_logging_w_base_scope(): - setup_logging("google") - base_logger = logging.getLogger("google") + with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): + setup_logging("foo") + base_logger = logging.getLogger("foo") assert isinstance(base_logger.handlers[0], logging.StreamHandler) assert base_logger.propagate == False assert base_logger.level == logging.DEBUG reset_logger("google") + def test_setup_logging_w_module_scope(): - setup_logging("google.foo") - - base_logger = logging.getLogger("google") + with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): + setup_logging("foo.bar") + + base_logger = logging.getLogger("foo") assert base_logger.handlers == [] assert base_logger.propagate == False assert base_logger.level == logging.NOTSET - module_logger = logging.getLogger("google.foo") + module_logger = logging.getLogger("foo.bar") assert isinstance(module_logger.handlers[0], logging.StreamHandler) assert module_logger.propagate == False assert module_logger.level == logging.DEBUG - reset_logger("google") reset_logger("google.foo") + def test_setup_logging_w_incorrect_scope(): - setup_logging("foo") - - base_logger = logging.getLogger("google") + with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): + setup_logging("abc") + + base_logger = logging.getLogger("foo") assert base_logger.handlers == [] assert base_logger.propagate == False assert base_logger.level == logging.NOTSET # TODO(https://github.com/googleapis/python-api-core/issues/759): update test once we add logic to ignore an incorrect scope. - logger = logging.getLogger("foo") + logger = logging.getLogger("abc") assert isinstance(logger.handlers[0], logging.StreamHandler) assert logger.propagate == False assert logger.level == logging.DEBUG reset_logger("google") reset_logger("foo") + + +def test_initialize_logging(): + + with mock.patch("os.getenv", return_value="foo.bar"): + with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): + initialize_logging() + + base_logger = logging.getLogger("foo") + assert base_logger.handlers == [] + assert base_logger.propagate == False + assert base_logger.level == logging.NOTSET + + module_logger = logging.getLogger("foo.bar") + assert isinstance(module_logger.handlers[0], logging.StreamHandler) + assert module_logger.propagate == False + assert module_logger.level == logging.DEBUG + + reset_logger("google") + reset_logger("google.foo") From b5088afb12bc85f066ce6c2423147ad9e93c32de Mon Sep 17 00:00:00 2001 From: ohmayr Date: Tue, 26 Nov 2024 18:52:48 +0000 Subject: [PATCH 08/16] address PR feedback --- tests/unit/test_client_logging.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/tests/unit/test_client_logging.py b/tests/unit/test_client_logging.py index fcbd19cb..6a991d2a 100644 --- a/tests/unit/test_client_logging.py +++ b/tests/unit/test_client_logging.py @@ -4,7 +4,6 @@ from google.api_core.client_logging import ( setup_logging, - parse_logging_scopes, initialize_logging, ) @@ -24,7 +23,7 @@ def test_setup_logging_w_no_scopes(): assert base_logger.propagate == False assert base_logger.level == logging.NOTSET - reset_logger("google") + reset_logger("foo") def test_setup_logging_w_base_scope(): @@ -35,7 +34,7 @@ def test_setup_logging_w_base_scope(): assert base_logger.propagate == False assert base_logger.level == logging.DEBUG - reset_logger("google") + reset_logger("foo") def test_setup_logging_w_module_scope(): @@ -52,8 +51,8 @@ def test_setup_logging_w_module_scope(): assert module_logger.propagate == False assert module_logger.level == logging.DEBUG - reset_logger("google") - reset_logger("google.foo") + reset_logger("foo") + reset_logger("foo.bar") def test_setup_logging_w_incorrect_scope(): @@ -71,8 +70,8 @@ def test_setup_logging_w_incorrect_scope(): assert logger.propagate == False assert logger.level == logging.DEBUG - reset_logger("google") reset_logger("foo") + reset_logger("abc") def test_initialize_logging(): @@ -91,5 +90,16 @@ def test_initialize_logging(): assert module_logger.propagate == False assert module_logger.level == logging.DEBUG - reset_logger("google") - reset_logger("google.foo") + base_logger.propagate = True + module_logger.propagate = True + + with mock.patch("os.getenv", return_value="foo.bar"): + with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): + initialize_logging() + + assert base_logger.propagate == True + assert module_logger.propagate == True + + + reset_logger("foo") + reset_logger("foo.bar") From 09283ec05b4a1f078f1c56be473562a5b637c5d6 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Tue, 26 Nov 2024 18:54:33 +0000 Subject: [PATCH 09/16] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- tests/unit/test_client_logging.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/test_client_logging.py b/tests/unit/test_client_logging.py index 6a991d2a..07525cb2 100644 --- a/tests/unit/test_client_logging.py +++ b/tests/unit/test_client_logging.py @@ -96,10 +96,9 @@ def test_initialize_logging(): with mock.patch("os.getenv", return_value="foo.bar"): with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): initialize_logging() - + assert base_logger.propagate == True assert module_logger.propagate == True - reset_logger("foo") reset_logger("foo.bar") From 89c15b57cfb6e3cffde1762b7ecc2b4061d5e7fd Mon Sep 17 00:00:00 2001 From: ohmayr Date: Tue, 26 Nov 2024 21:50:54 +0000 Subject: [PATCH 10/16] address PR feedback --- google/api_core/client_logging.py | 9 ++++----- tests/unit/test_client_logging.py | 24 +++++++++++------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/google/api_core/client_logging.py b/google/api_core/client_logging.py index 2d3488d6..248f1953 100644 --- a/google/api_core/client_logging.py +++ b/google/api_core/client_logging.py @@ -1,8 +1,9 @@ import logging import json -import re import os +from typing import List, Optional + _LOGGING_INITIALIZED = False _BASE_LOGGER_NAME = "google" # TODO(https://github.com/googleapis/python-api-core/issues/760): Update Request / Response messages. @@ -19,9 +20,7 @@ def logger_configured(logger): return ( - logger.handlers != [] - or logger.level != logging.NOTSET - or logger.propagate == False + logger.handlers != [] or logger.level != logging.NOTSET or not logger.propagate ) @@ -34,7 +33,7 @@ def initialize_logging(): _LOGGING_INITIALIZED = True -def parse_logging_scopes(scopes): +def parse_logging_scopes(scopes: Optional[str] = None) -> List[str]: if not scopes: return [] # TODO(https://github.com/googleapis/python-api-core/issues/759): check if the namespace is a valid namespace. diff --git a/tests/unit/test_client_logging.py b/tests/unit/test_client_logging.py index 6a991d2a..66610366 100644 --- a/tests/unit/test_client_logging.py +++ b/tests/unit/test_client_logging.py @@ -1,6 +1,5 @@ import logging -import pytest -import mock +from unittest import mock from google.api_core.client_logging import ( setup_logging, @@ -20,7 +19,7 @@ def test_setup_logging_w_no_scopes(): setup_logging() base_logger = logging.getLogger("foo") assert base_logger.handlers == [] - assert base_logger.propagate == False + assert not base_logger.propagate assert base_logger.level == logging.NOTSET reset_logger("foo") @@ -31,7 +30,7 @@ def test_setup_logging_w_base_scope(): setup_logging("foo") base_logger = logging.getLogger("foo") assert isinstance(base_logger.handlers[0], logging.StreamHandler) - assert base_logger.propagate == False + assert not base_logger.propagate assert base_logger.level == logging.DEBUG reset_logger("foo") @@ -43,12 +42,12 @@ def test_setup_logging_w_module_scope(): base_logger = logging.getLogger("foo") assert base_logger.handlers == [] - assert base_logger.propagate == False + assert not base_logger.propagate assert base_logger.level == logging.NOTSET module_logger = logging.getLogger("foo.bar") assert isinstance(module_logger.handlers[0], logging.StreamHandler) - assert module_logger.propagate == False + assert not module_logger.propagate assert module_logger.level == logging.DEBUG reset_logger("foo") @@ -61,13 +60,13 @@ def test_setup_logging_w_incorrect_scope(): base_logger = logging.getLogger("foo") assert base_logger.handlers == [] - assert base_logger.propagate == False + assert not base_logger.propagate assert base_logger.level == logging.NOTSET # TODO(https://github.com/googleapis/python-api-core/issues/759): update test once we add logic to ignore an incorrect scope. logger = logging.getLogger("abc") assert isinstance(logger.handlers[0], logging.StreamHandler) - assert logger.propagate == False + assert not logger.propagate assert logger.level == logging.DEBUG reset_logger("foo") @@ -82,12 +81,12 @@ def test_initialize_logging(): base_logger = logging.getLogger("foo") assert base_logger.handlers == [] - assert base_logger.propagate == False + assert not base_logger.propagate assert base_logger.level == logging.NOTSET module_logger = logging.getLogger("foo.bar") assert isinstance(module_logger.handlers[0], logging.StreamHandler) - assert module_logger.propagate == False + assert not module_logger.propagate assert module_logger.level == logging.DEBUG base_logger.propagate = True @@ -96,10 +95,9 @@ def test_initialize_logging(): with mock.patch("os.getenv", return_value="foo.bar"): with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): initialize_logging() - - assert base_logger.propagate == True - assert module_logger.propagate == True + assert base_logger.propagate + assert module_logger.propagate reset_logger("foo") reset_logger("foo.bar") From f782b638a347f3b136f588e34511f07234c7be09 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Tue, 26 Nov 2024 21:57:57 +0000 Subject: [PATCH 11/16] address PR feedback --- google/api_core/client_logging.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/google/api_core/client_logging.py b/google/api_core/client_logging.py index 248f1953..fbdde370 100644 --- a/google/api_core/client_logging.py +++ b/google/api_core/client_logging.py @@ -6,9 +6,6 @@ _LOGGING_INITIALIZED = False _BASE_LOGGER_NAME = "google" -# TODO(https://github.com/googleapis/python-api-core/issues/760): Update Request / Response messages. -REQUEST_MESSAGE = "Sending request " -RESPONSE_MESSAGE = "Receiving response " # TODO(https://github.com/googleapis/python-api-core/issues/761): Update this list to support additional logging fields _recognized_logging_fields = [ From 601b653d36545712f6bc444c678e6c88b2786919 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Wed, 27 Nov 2024 15:19:45 +0000 Subject: [PATCH 12/16] address PR feedback --- google/api_core/client_logging.py | 6 +- tests/unit/test_client_logging.py | 95 +++++++++++++++++++------------ 2 files changed, 64 insertions(+), 37 deletions(-) diff --git a/google/api_core/client_logging.py b/google/api_core/client_logging.py index fbdde370..bc80e52b 100644 --- a/google/api_core/client_logging.py +++ b/google/api_core/client_logging.py @@ -7,7 +7,7 @@ _LOGGING_INITIALIZED = False _BASE_LOGGER_NAME = "google" -# TODO(https://github.com/googleapis/python-api-core/issues/761): Update this list to support additional logging fields +# TODO(https://github.com/googleapis/python-api-core/issues/761): Update this list to support additional logging fields. _recognized_logging_fields = [ "httpRequest", "rpcName", @@ -50,7 +50,7 @@ def configure_defaults(logger): logger.addHandler(console_handler) -def setup_logging(scopes=[]): +def setup_logging(scopes=""): # only returns valid logger scopes (namespaces) # this list has at most one element. @@ -70,6 +70,8 @@ def setup_logging(scopes=[]): class StructuredLogFormatter(logging.Formatter): + # TODO(https://github.com/googleapis/python-api-core/issues/761): ensure that additional fields such as + # function name, file name, and line no. appear in a log output. def format(self, record): log_obj = { "timestamp": self.formatTime(record), diff --git a/tests/unit/test_client_logging.py b/tests/unit/test_client_logging.py index 66610366..b2467037 100644 --- a/tests/unit/test_client_logging.py +++ b/tests/unit/test_client_logging.py @@ -1,9 +1,11 @@ +import json import logging from unittest import mock from google.api_core.client_logging import ( setup_logging, initialize_logging, + StructuredLogFormatter, ) @@ -28,10 +30,10 @@ def test_setup_logging_w_no_scopes(): def test_setup_logging_w_base_scope(): with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): setup_logging("foo") - base_logger = logging.getLogger("foo") - assert isinstance(base_logger.handlers[0], logging.StreamHandler) - assert not base_logger.propagate - assert base_logger.level == logging.DEBUG + base_logger = logging.getLogger("foo") + assert isinstance(base_logger.handlers[0], logging.StreamHandler) + assert not base_logger.propagate + assert base_logger.level == logging.DEBUG reset_logger("foo") @@ -40,15 +42,15 @@ def test_setup_logging_w_module_scope(): with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): setup_logging("foo.bar") - base_logger = logging.getLogger("foo") - assert base_logger.handlers == [] - assert not base_logger.propagate - assert base_logger.level == logging.NOTSET + base_logger = logging.getLogger("foo") + assert base_logger.handlers == [] + assert not base_logger.propagate + assert base_logger.level == logging.NOTSET - module_logger = logging.getLogger("foo.bar") - assert isinstance(module_logger.handlers[0], logging.StreamHandler) - assert not module_logger.propagate - assert module_logger.level == logging.DEBUG + module_logger = logging.getLogger("foo.bar") + assert isinstance(module_logger.handlers[0], logging.StreamHandler) + assert not module_logger.propagate + assert module_logger.level == logging.DEBUG reset_logger("foo") reset_logger("foo.bar") @@ -58,16 +60,16 @@ def test_setup_logging_w_incorrect_scope(): with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): setup_logging("abc") - base_logger = logging.getLogger("foo") - assert base_logger.handlers == [] - assert not base_logger.propagate - assert base_logger.level == logging.NOTSET + base_logger = logging.getLogger("foo") + assert base_logger.handlers == [] + assert not base_logger.propagate + assert base_logger.level == logging.NOTSET - # TODO(https://github.com/googleapis/python-api-core/issues/759): update test once we add logic to ignore an incorrect scope. - logger = logging.getLogger("abc") - assert isinstance(logger.handlers[0], logging.StreamHandler) - assert not logger.propagate - assert logger.level == logging.DEBUG + # TODO(https://github.com/googleapis/python-api-core/issues/759): update test once we add logic to ignore an incorrect scope. + logger = logging.getLogger("abc") + assert isinstance(logger.handlers[0], logging.StreamHandler) + assert not logger.propagate + assert logger.level == logging.DEBUG reset_logger("foo") reset_logger("abc") @@ -79,25 +81,48 @@ def test_initialize_logging(): with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): initialize_logging() - base_logger = logging.getLogger("foo") - assert base_logger.handlers == [] - assert not base_logger.propagate - assert base_logger.level == logging.NOTSET + base_logger = logging.getLogger("foo") + assert base_logger.handlers == [] + assert not base_logger.propagate + assert base_logger.level == logging.NOTSET - module_logger = logging.getLogger("foo.bar") - assert isinstance(module_logger.handlers[0], logging.StreamHandler) - assert not module_logger.propagate - assert module_logger.level == logging.DEBUG + module_logger = logging.getLogger("foo.bar") + assert isinstance(module_logger.handlers[0], logging.StreamHandler) + assert not module_logger.propagate + assert module_logger.level == logging.DEBUG - base_logger.propagate = True - module_logger.propagate = True + # Check that `initialize_logging()` is a no-op after the first time by verifying that user-set configs are not modified: + base_logger.propagate = True + module_logger.propagate = True - with mock.patch("os.getenv", return_value="foo.bar"): - with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): initialize_logging() - assert base_logger.propagate - assert module_logger.propagate + assert base_logger.propagate + assert module_logger.propagate reset_logger("foo") reset_logger("foo.bar") + + +def test_structured_log_formatter(): + # TODO(https://github.com/googleapis/python-api-core/issues/761): Test additional fields when implemented. + record = logging.LogRecord( + name="foo", + level=logging.DEBUG, + msg="This is a test message.", + pathname="foo/bar", + lineno=25, + args=None, + exc_info=None, + ) + + # Extra fields: + record.rpcName = "bar" + + formatted_msg = StructuredLogFormatter().format(record) + parsed_msg = json.loads(formatted_msg) + + assert parsed_msg["name"] == "foo" + assert parsed_msg["severity"] == "DEBUG" + assert parsed_msg["message"] == "This is a test message." + assert parsed_msg["rpcName"] == "bar" From 69bbd059f4f7fd0730daa9d45fabeaf37696dd15 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Wed, 27 Nov 2024 15:35:22 +0000 Subject: [PATCH 13/16] add coverage for already configured scope --- google/api_core/client_logging.py | 6 ++++++ tests/unit/test_client_logging.py | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/google/api_core/client_logging.py b/google/api_core/client_logging.py index bc80e52b..a9693531 100644 --- a/google/api_core/client_logging.py +++ b/google/api_core/client_logging.py @@ -15,12 +15,14 @@ ] # Additional fields to be Logged. +# TODO(https://github.com/googleapis/python-api-core/issues/763): Add documentation. def logger_configured(logger): return ( logger.handlers != [] or logger.level != logging.NOTSET or not logger.propagate ) +# TODO(https://github.com/googleapis/python-api-core/issues/763): Add documentation. def initialize_logging(): global _LOGGING_INITIALIZED if _LOGGING_INITIALIZED: @@ -30,6 +32,7 @@ def initialize_logging(): _LOGGING_INITIALIZED = True +# TODO(https://github.com/googleapis/python-api-core/issues/763): Add documentation. def parse_logging_scopes(scopes: Optional[str] = None) -> List[str]: if not scopes: return [] @@ -40,6 +43,7 @@ def parse_logging_scopes(scopes: Optional[str] = None) -> List[str]: return namespaces +# TODO(https://github.com/googleapis/python-api-core/issues/763): Add documentation. def configure_defaults(logger): if not logger_configured(logger): console_handler = logging.StreamHandler() @@ -50,6 +54,7 @@ def configure_defaults(logger): logger.addHandler(console_handler) +# TODO(https://github.com/googleapis/python-api-core/issues/763): Add documentation. def setup_logging(scopes=""): # only returns valid logger scopes (namespaces) @@ -69,6 +74,7 @@ def setup_logging(scopes=""): base_logger.propagate = False +# TODO(https://github.com/googleapis/python-api-core/issues/763): Add documentation. class StructuredLogFormatter(logging.Formatter): # TODO(https://github.com/googleapis/python-api-core/issues/761): ensure that additional fields such as # function name, file name, and line no. appear in a log output. diff --git a/tests/unit/test_client_logging.py b/tests/unit/test_client_logging.py index b2467037..48808b48 100644 --- a/tests/unit/test_client_logging.py +++ b/tests/unit/test_client_logging.py @@ -38,6 +38,18 @@ def test_setup_logging_w_base_scope(): reset_logger("foo") +def test_setup_logging_w_configured_scope(): + with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): + base_logger = logging.getLogger("foo") + base_logger.propagate = False + setup_logging("foo") + assert base_logger.handlers == [] + assert not base_logger.propagate + assert base_logger.level == logging.NOTSET + + reset_logger("foo") + + def test_setup_logging_w_module_scope(): with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): setup_logging("foo.bar") From b6b354fb490768d198d283d6d045e7fdad2a3440 Mon Sep 17 00:00:00 2001 From: Victor Chudnovsky Date: Wed, 27 Nov 2024 12:27:51 -0800 Subject: [PATCH 14/16] Add partial documentation; clarify test base logger --- google/api_core/client_logging.py | 58 ++++++++++++++++++++++++++++--- tests/unit/test_client_logging.py | 58 +++++++++++++++---------------- 2 files changed, 82 insertions(+), 34 deletions(-) diff --git a/google/api_core/client_logging.py b/google/api_core/client_logging.py index a9693531..3d02695b 100644 --- a/google/api_core/client_logging.py +++ b/google/api_core/client_logging.py @@ -7,6 +7,8 @@ _LOGGING_INITIALIZED = False _BASE_LOGGER_NAME = "google" +# Fields to be included in the StructuredLogFormatter. +# # TODO(https://github.com/googleapis/python-api-core/issues/761): Update this list to support additional logging fields. _recognized_logging_fields = [ "httpRequest", @@ -15,15 +17,36 @@ ] # Additional fields to be Logged. -# TODO(https://github.com/googleapis/python-api-core/issues/763): Add documentation. +# TODO(https://github.com/googleapis/python-api-core/issues/763): Expand documentation. def logger_configured(logger): + """Determines whether `logger` has non-default configuration + + Args: + logger: The logger to check. + + Returns: + bool: Whether the logger has any non-default configuration. + """ return ( logger.handlers != [] or logger.level != logging.NOTSET or not logger.propagate ) -# TODO(https://github.com/googleapis/python-api-core/issues/763): Add documentation. +# TODO(https://github.com/googleapis/python-api-core/issues/763): Expand documentation. def initialize_logging(): + """Initializes "google" loggers, partly based on the environment variable + + Initializes the "google" logger and any loggers (at the "google" + level or lower) specified by the environment variable + GOOGLE_SDK_PYTHON_LOGGING_SCOPE, as long as none of these loggers + were previously configured. If any such loggers (including the + "google" logger) are initialized, they are set to NOT propagate + log events up to their parent loggers. + + This initialization is executed only once, and hence the + environment variable is only processed the first time this + function is called. + """ global _LOGGING_INITIALIZED if _LOGGING_INITIALIZED: return @@ -32,8 +55,18 @@ def initialize_logging(): _LOGGING_INITIALIZED = True -# TODO(https://github.com/googleapis/python-api-core/issues/763): Add documentation. +# TODO(https://github.com/googleapis/python-api-core/issues/763): Expand documentation. def parse_logging_scopes(scopes: Optional[str] = None) -> List[str]: + """Returns a list of logger names. + + Splits the single string of comma-separated logger names into a list of individual logger name strings. + + Args: + scopes: The name of a single logger. (In the future, this will be a comma-separated list of multiple loggers.) + + Returns: + A list of all the logger names in scopes. + """ if not scopes: return [] # TODO(https://github.com/googleapis/python-api-core/issues/759): check if the namespace is a valid namespace. @@ -43,8 +76,9 @@ def parse_logging_scopes(scopes: Optional[str] = None) -> List[str]: return namespaces -# TODO(https://github.com/googleapis/python-api-core/issues/763): Add documentation. +# TODO(https://github.com/googleapis/python-api-core/issues/763): Expand documentation. def configure_defaults(logger): + """Configures `logger` to emit structured info to stdout.""" if not logger_configured(logger): console_handler = logging.StreamHandler() logger.setLevel("DEBUG") @@ -54,8 +88,22 @@ def configure_defaults(logger): logger.addHandler(console_handler) -# TODO(https://github.com/googleapis/python-api-core/issues/763): Add documentation. +# TODO(https://github.com/googleapis/python-api-core/issues/763): Expand documentation. def setup_logging(scopes=""): + """Sets up logging for the specified `scopes`. + + If the loggers specified in `scopes` have not been previously + configured, this will configure them to emit structured log + entries to stdout, and to not propagate their log events to their + parent loggers. Additionally, if the "google" logger (whether it + was specified in `scopes` or not) was not previously configured, + it will also configure it to not propagate log events to the root + logger. + + Args: + scopes: The name of a single logger. (In the future, this will be a comma-separated list of multiple loggers.) + + """ # only returns valid logger scopes (namespaces) # this list has at most one element. diff --git a/tests/unit/test_client_logging.py b/tests/unit/test_client_logging.py index 48808b48..b3b0b5c8 100644 --- a/tests/unit/test_client_logging.py +++ b/tests/unit/test_client_logging.py @@ -17,62 +17,62 @@ def reset_logger(scope): def test_setup_logging_w_no_scopes(): - with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): + with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foogle"): setup_logging() - base_logger = logging.getLogger("foo") + base_logger = logging.getLogger("foogle") assert base_logger.handlers == [] assert not base_logger.propagate assert base_logger.level == logging.NOTSET - reset_logger("foo") + reset_logger("foogle") def test_setup_logging_w_base_scope(): - with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): - setup_logging("foo") - base_logger = logging.getLogger("foo") + with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foogle"): + setup_logging("foogle") + base_logger = logging.getLogger("foogle") assert isinstance(base_logger.handlers[0], logging.StreamHandler) assert not base_logger.propagate assert base_logger.level == logging.DEBUG - reset_logger("foo") + reset_logger("foogle") def test_setup_logging_w_configured_scope(): - with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): - base_logger = logging.getLogger("foo") + with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foogle"): + base_logger = logging.getLogger("foogle") base_logger.propagate = False - setup_logging("foo") + setup_logging("foogle") assert base_logger.handlers == [] assert not base_logger.propagate assert base_logger.level == logging.NOTSET - reset_logger("foo") + reset_logger("foogle") def test_setup_logging_w_module_scope(): - with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): - setup_logging("foo.bar") + with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foogle"): + setup_logging("foogle.bar") - base_logger = logging.getLogger("foo") + base_logger = logging.getLogger("foogle") assert base_logger.handlers == [] assert not base_logger.propagate assert base_logger.level == logging.NOTSET - module_logger = logging.getLogger("foo.bar") + module_logger = logging.getLogger("foogle.bar") assert isinstance(module_logger.handlers[0], logging.StreamHandler) assert not module_logger.propagate assert module_logger.level == logging.DEBUG - reset_logger("foo") - reset_logger("foo.bar") + reset_logger("foogle") + reset_logger("foogle.bar") def test_setup_logging_w_incorrect_scope(): - with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): + with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foogle"): setup_logging("abc") - base_logger = logging.getLogger("foo") + base_logger = logging.getLogger("foogle") assert base_logger.handlers == [] assert not base_logger.propagate assert base_logger.level == logging.NOTSET @@ -83,22 +83,22 @@ def test_setup_logging_w_incorrect_scope(): assert not logger.propagate assert logger.level == logging.DEBUG - reset_logger("foo") + reset_logger("foogle") reset_logger("abc") def test_initialize_logging(): - with mock.patch("os.getenv", return_value="foo.bar"): - with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foo"): + with mock.patch("os.getenv", return_value="foogle.bar"): + with mock.patch("google.api_core.client_logging._BASE_LOGGER_NAME", "foogle"): initialize_logging() - base_logger = logging.getLogger("foo") + base_logger = logging.getLogger("foogle") assert base_logger.handlers == [] assert not base_logger.propagate assert base_logger.level == logging.NOTSET - module_logger = logging.getLogger("foo.bar") + module_logger = logging.getLogger("foogle.bar") assert isinstance(module_logger.handlers[0], logging.StreamHandler) assert not module_logger.propagate assert module_logger.level == logging.DEBUG @@ -112,17 +112,17 @@ def test_initialize_logging(): assert base_logger.propagate assert module_logger.propagate - reset_logger("foo") - reset_logger("foo.bar") + reset_logger("foogle") + reset_logger("foogle.bar") def test_structured_log_formatter(): # TODO(https://github.com/googleapis/python-api-core/issues/761): Test additional fields when implemented. record = logging.LogRecord( - name="foo", + name="Appelation", level=logging.DEBUG, msg="This is a test message.", - pathname="foo/bar", + pathname="some/path", lineno=25, args=None, exc_info=None, @@ -134,7 +134,7 @@ def test_structured_log_formatter(): formatted_msg = StructuredLogFormatter().format(record) parsed_msg = json.loads(formatted_msg) - assert parsed_msg["name"] == "foo" + assert parsed_msg["name"] == "Appelation" assert parsed_msg["severity"] == "DEBUG" assert parsed_msg["message"] == "This is a test message." assert parsed_msg["rpcName"] == "bar" From a157d8c1a9d4ff18697c5b95c00444caf33c86a3 Mon Sep 17 00:00:00 2001 From: ohmayr Date: Mon, 2 Dec 2024 09:24:32 +0000 Subject: [PATCH 15/16] address PR feedback --- google/api_core/client_logging.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/google/api_core/client_logging.py b/google/api_core/client_logging.py index 3d02695b..021646f1 100644 --- a/google/api_core/client_logging.py +++ b/google/api_core/client_logging.py @@ -14,11 +14,17 @@ "httpRequest", "rpcName", "serviceName", + "credentialsType", + "credentialInfo", + "universeDomain", + "request", + "response", + "metadata", + "retryAttempt", ] # Additional fields to be Logged. -# TODO(https://github.com/googleapis/python-api-core/issues/763): Expand documentation. -def logger_configured(logger): +def logger_configured(logger) -> bool: """Determines whether `logger` has non-default configuration Args: @@ -32,7 +38,6 @@ def logger_configured(logger): ) -# TODO(https://github.com/googleapis/python-api-core/issues/763): Expand documentation. def initialize_logging(): """Initializes "google" loggers, partly based on the environment variable @@ -55,7 +60,6 @@ def initialize_logging(): _LOGGING_INITIALIZED = True -# TODO(https://github.com/googleapis/python-api-core/issues/763): Expand documentation. def parse_logging_scopes(scopes: Optional[str] = None) -> List[str]: """Returns a list of logger names. @@ -76,7 +80,6 @@ def parse_logging_scopes(scopes: Optional[str] = None) -> List[str]: return namespaces -# TODO(https://github.com/googleapis/python-api-core/issues/763): Expand documentation. def configure_defaults(logger): """Configures `logger` to emit structured info to stdout.""" if not logger_configured(logger): @@ -88,8 +91,7 @@ def configure_defaults(logger): logger.addHandler(console_handler) -# TODO(https://github.com/googleapis/python-api-core/issues/763): Expand documentation. -def setup_logging(scopes=""): +def setup_logging(scopes: str=""): """Sets up logging for the specified `scopes`. If the loggers specified in `scopes` have not been previously @@ -122,11 +124,11 @@ def setup_logging(scopes=""): base_logger.propagate = False -# TODO(https://github.com/googleapis/python-api-core/issues/763): Add documentation. +# TODO(https://github.com/googleapis/python-api-core/issues/763): Expand documentation. class StructuredLogFormatter(logging.Formatter): # TODO(https://github.com/googleapis/python-api-core/issues/761): ensure that additional fields such as # function name, file name, and line no. appear in a log output. - def format(self, record): + def format(self, record: logging.LogRecord): log_obj = { "timestamp": self.formatTime(record), "severity": record.levelname, From a15f59d43e088d1b299d116da0a77ed9d928cbec Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Mon, 2 Dec 2024 10:06:45 +0000 Subject: [PATCH 16/16] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- google/api_core/client_logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google/api_core/client_logging.py b/google/api_core/client_logging.py index 021646f1..02d3bf60 100644 --- a/google/api_core/client_logging.py +++ b/google/api_core/client_logging.py @@ -91,7 +91,7 @@ def configure_defaults(logger): logger.addHandler(console_handler) -def setup_logging(scopes: str=""): +def setup_logging(scopes: str = ""): """Sets up logging for the specified `scopes`. If the loggers specified in `scopes` have not been previously