From 28f4c982ae6f5ea5e78256ce4e759534298c5032 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Tue, 20 Dec 2016 18:46:34 -0800 Subject: [PATCH 01/14] Add Error Reporting GAPIC as dependency --- error_reporting/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/error_reporting/setup.py b/error_reporting/setup.py index 9d65198d9af6..153e0f8a9411 100644 --- a/error_reporting/setup.py +++ b/error_reporting/setup.py @@ -52,6 +52,7 @@ REQUIREMENTS = [ 'google-cloud-core >= 0.22.1, < 0.23dev', 'google-cloud-logging >= 0.22.0, < 0.23dev', + 'gapic-google-cloud-error-reporting-v1beta1 >= 0.13.0, < 0.14dev' ] setup( From df26d8004fa8fc5d94b00f8e588ebfa137cefbd9 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Thu, 29 Dec 2016 12:35:24 -0800 Subject: [PATCH 02/14] Add GAX to Error Reporting --- .../google/cloud/error_reporting/_gax.py | 56 +++++++++ .../google/cloud/error_reporting/_logging.py | 34 ++++++ .../google/cloud/error_reporting/client.py | 115 +++++++++++++++--- 3 files changed, 188 insertions(+), 17 deletions(-) create mode 100644 error_reporting/google/cloud/error_reporting/_gax.py create mode 100644 error_reporting/google/cloud/error_reporting/_logging.py diff --git a/error_reporting/google/cloud/error_reporting/_gax.py b/error_reporting/google/cloud/error_reporting/_gax.py new file mode 100644 index 000000000000..3bafea7fe1a8 --- /dev/null +++ b/error_reporting/google/cloud/error_reporting/_gax.py @@ -0,0 +1,56 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""GAX wrapper for Error Reporting API requests.""" + +from google.cloud.gapic.errorreporting.v1beta1 import report_errors_service_api +from google.devtools.clouderrorreporting.v1beta1 import report_errors_service_pb2 +from google.protobuf.json_format import ParseDict + + +def make_report_error_api(project): + """Create an instance of the GAX Logging API.""" + api = report_errors_service_api.ReportErrorsServiceApi() + return _ErrorReportingGaxApi(api, project) + + +class _ErrorReportingGaxApi(object): + """Helper mapping Error Reporting-related APIs + + :type gax_api: + :class:`google.cloud.gapic.errorreporting.v1beta1 + .report_errors_service_api.report_errors_service_api` + :param gax_api: API object used to make GAX requests. + """ + def __init__(self, gax_api, project): + self._gax_api = gax_api + self._project = project + + def report_error_event(self, error_report): + """Uses the GAX client to report the error. + + :type project: str + :param: project: Project ID to report the error to + + :type error: dict: + :param: error: dict payload of the error report formatted + according to + https://cloud.google.com/error-reporting/docs/formatting-error-messages + This object should be built using + Use :meth:~`google.cloud.error_reporting.client._build_error_report` + """ + project_name = self._gax_api.project_path(self._project) + error_report_payload = report_errors_service_pb2.ReportedErrorEvent() + ParseDict(error_report, error_report_payload) + self._gax_api.report_error_event(project_name, error_report_payload) diff --git a/error_reporting/google/cloud/error_reporting/_logging.py b/error_reporting/google/cloud/error_reporting/_logging.py new file mode 100644 index 000000000000..7737312cb860 --- /dev/null +++ b/error_reporting/google/cloud/error_reporting/_logging.py @@ -0,0 +1,34 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Interact with Stackdriver Error Reporting via Logging API. + +It's possible to report Stackdriver Error Reporting errors by formatting +structured log messages in Stackdriver Logging in a given format. This +client provides a mechanism to report errors using that technique. +""" + +import google.cloud.logging.client + + +class _ErrorReportingLoggingAPI(object): + """Report to Stackdriver Error Reporting via Logging API + """ + def __init__(self, project, credentials, http): + self.logging_client = google.cloud.logging.client.Client( + project, credentials, http) + + def report_error_event(self, project, error_report): + logger = self.logging_client.logger('errors') + logger.log_struct(error_report) diff --git a/error_reporting/google/cloud/error_reporting/client.py b/error_reporting/google/cloud/error_reporting/client.py index dfa0b0e2845b..546f63a69520 100644 --- a/error_reporting/google/cloud/error_reporting/client.py +++ b/error_reporting/google/cloud/error_reporting/client.py @@ -12,13 +12,27 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Client for interacting with the Stackdriver Logging API""" +"""Client for interacting with the Stackdriver Error Reporting API""" +import os import traceback -import google.cloud.logging.client +try: + from google.cloud.error_reporting._gax import make_report_error_api +except ImportError: # pragma: NO COVER + _HAVE_GAX = False +else: + _HAVE_GAX = True + +from google.cloud._helpers import _determine_default_project +from google.cloud.error_reporting._logging import _ErrorReportingLoggingAPI +from google.cloud.environment_vars import DISABLE_GRPC + import six +_DISABLE_GAX = os.getenv(DISABLE_GRPC, False) +_USE_GAX = _HAVE_GAX and not _DISABLE_GAX + class HTTPContext(object): """HTTPContext defines an object that captures the parameter for the @@ -96,6 +110,12 @@ class Client(object): SHA-1 hash, for example. If the developer did not provide a version, the value is set to default. + :type use_gax: bool + :param use_gax: (Optional) Explicitly specifies whether + to use the gRPC transport (via GAX) or HTTP. If unset, + falls back to the ``GOOGLE_CLOUD_DISABLE_GRPC`` environment + variable. + :raises: :class:`ValueError` if the project is neither passed in nor set in the environment. """ @@ -104,24 +124,48 @@ def __init__(self, project=None, credentials=None, http=None, service=None, - version=None): - self.logging_client = google.cloud.logging.client.Client( - project, credentials, http) + version=None, + use_gax=None): + if project is None: + self._project = _determine_default_project() + else: + self._project = project + self._credentials = credentials + self._http = http + + self._report_errors_api = None + self.service = service if service else self.DEFAULT_SERVICE self.version = version + if use_gax is None: + self._use_gax = _USE_GAX + else: + self._use_gax = use_gax DEFAULT_SERVICE = 'python' - def _send_error_report(self, message, - report_location=None, http_context=None, user=None): - """Makes the call to the Error Reporting API via the log stream. + @property + def report_errors_api(self): + """Helper for logging-related API calls. + + See: + https://cloud.google.com/logging/docs/api/reference/rest/v2/entries + https://cloud.google.com/logging/docs/api/reference/rest/v2/projects.logs + """ + if self._report_errors_api is None: + if self._use_gax: + self._report_errors_api = make_report_error_api(self._project) + else: + self._report_errors_api = _ErrorReportingLoggingAPI( + self._project, self._credentials, self._http) + return self._report_errors_api - This is the lower-level interface to build the payload, generally - users will use either report() or report_exception() to automatically - gather the parameters for this method. - Currently this method sends the Error Report by formatting a structured - log message according to + def _build_error_report(self, message, + report_location=None, http_context=None, user=None): + """Builds the Error Reporting object to report. + + This builds the object according to https://cloud.google.com/error-reporting/docs/formatting-error-messages @@ -151,7 +195,7 @@ def _send_error_report(self, message, logged in. In this case the Error Reporting system will use other data, such as remote IP address, to distinguish affected users. - """ + """ payload = { 'serviceContext': { 'service': self.service, @@ -174,13 +218,50 @@ def _send_error_report(self, message, payload['context']['httpContext'] = { key: value for key, value in six.iteritems(http_context_dict) if value is not None - } + } if user: payload['context']['user'] = user + return payload - logger = self.logging_client.logger('errors') - logger.log_struct(payload) + def _send_error_report(self, message, + report_location=None, http_context=None, user=None): + """Makes the call to the Error Reporting API. + + This is the lower-level interface to build and send the payload, + generally users will use either report() or report_exception() to + automatically gather the parameters for this method. + + :type message: str + :param message: The stack trace that was reported or logged by the + service. + + :type report_location: dict + :param report_location: The location in the source code where the + decision was made to report the error, usually the place + where it was logged. For a logged exception this would be the + source line where the exception is logged, usually close to + the place where it was caught. + + This should be a Python dict that contains the keys 'filePath', + 'lineNumber', and 'functionName' + + :type http_context: :class`google.cloud.error_reporting.HTTPContext` + :param http_context: The HTTP request which was processed when the + error was triggered. + + :type user: str + :param user: The user who caused or was affected by the crash. This can + be a user ID, an email address, or an arbitrary token that + uniquely identifies the user. When sending an error + report, leave this field empty if the user was not + logged in. In this case the Error Reporting system will + use other data, such as remote IP address, + to distinguish affected users. + """ + error_report = self._build_error_report(message, report_location, + http_context, user) + self.report_errors_api.report_error_event(error_report) def report(self, message, http_context=None, user=None): """ Reports a message to Stackdriver Error Reporting From e2eb576ad528b15810112fe694d45e568c404fe7 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Tue, 17 Jan 2017 15:39:35 -0800 Subject: [PATCH 03/14] fixing up stuff --- .../google/cloud/error_reporting/client.py | 1 - error_reporting/unit_tests/test_client.py | 37 ++++++++----------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/error_reporting/google/cloud/error_reporting/client.py b/error_reporting/google/cloud/error_reporting/client.py index 546f63a69520..ead78a252a2e 100644 --- a/error_reporting/google/cloud/error_reporting/client.py +++ b/error_reporting/google/cloud/error_reporting/client.py @@ -160,7 +160,6 @@ def report_errors_api(self): self._project, self._credentials, self._http) return self._report_errors_api - def _build_error_report(self, message, report_location=None, http_context=None, user=None): """Builds the Error Reporting object to report. diff --git a/error_reporting/unit_tests/test_client.py b/error_reporting/unit_tests/test_client.py index c1b105990f0d..97223d33059c 100644 --- a/error_reporting/unit_tests/test_client.py +++ b/error_reporting/unit_tests/test_client.py @@ -63,27 +63,26 @@ def test_ctor_params(self): self.assertEquals(target.service, self.SERVICE) self.assertEquals(target.version, self.VERSION) - def test_report_exception(self): + def test_report_exception_with_gax(self): CREDENTIALS = _make_credentials() target = self._make_one(project=self.PROJECT, credentials=CREDENTIALS) - logger = _Logger() - target.logging_client.logger = lambda _: logger - - try: - raise NameError - except NameError: - target.report_exception() - - payload = logger.log_struct_called_with + patch = mock.patch( + 'google.cloud.error_reporting.client.make_report_error_api') + with patch as make_api: + try: + raise NameError + except NameError: + target.report_exception() + payload = make_api.return_value.report_error_event.call_args[0][0] self.assertEquals(payload['serviceContext'], { 'service': target.DEFAULT_SERVICE, }) self.assertIn('test_report', payload['message']) self.assertIn('test_client.py', payload['message']) - def test_report_exception_with_service_version_in_constructor(self): + def test_build_report_with_service_version_in_constructor(self): CREDENTIALS = _make_credentials() SERVICE = "notdefault" VERSION = "notdefaultversion" @@ -92,9 +91,7 @@ def test_report_exception_with_service_version_in_constructor(self): service=SERVICE, version=VERSION) - logger = _Logger() - target.logging_client.logger = lambda _: logger - + MESSAGE = 'this is an error' http_context = self._makeHTTP(method="GET", response_status_code=500) USER = "user@gmail.com" @@ -103,7 +100,7 @@ def test_report_exception_with_service_version_in_constructor(self): except NameError: target.report_exception(http_context=http_context, user=USER) - payload = logger.log_struct_called_with + payload = target.build_error_report(MESSAGE) self.assertEquals(payload['serviceContext'], { 'service': SERVICE, 'version': VERSION @@ -118,18 +115,14 @@ def test_report_exception_with_service_version_in_constructor(self): payload['context']['httpContext']['method'], 'GET') self.assertEquals(payload['context']['user'], USER) - def test_report(self): + def test_build_report(self): CREDENTIALS = _make_credentials() target = self._make_one(project=self.PROJECT, credentials=CREDENTIALS) - logger = _Logger() - target.logging_client.logger = lambda _: logger - MESSAGE = 'this is an error' - target.report(MESSAGE) + payload = target._build_error_report(MESSAGE) - payload = logger.log_struct_called_with self.assertEquals(payload['message'], MESSAGE) report_location = payload['context']['reportLocation'] self.assertIn('test_client.py', report_location['filePath']) @@ -138,6 +131,7 @@ def test_report(self): self.assertLess(report_location['lineNumber'], 150) +""" class _Logger(object): def log_struct(self, payload, # pylint: disable=unused-argument @@ -147,3 +141,4 @@ def log_struct(self, payload, # pylint: disable=unused-argument severity=None, # pylint: disable=unused-argument http_request=None): # pylint: disable=unused-argument self.log_struct_called_with = payload +""" From e92e17d259d10441ca8b3f43686eed552b0f9865 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Fri, 27 Jan 2017 13:06:23 -0800 Subject: [PATCH 04/14] Add tests, make Travis pass --- .../google/cloud/error_reporting/_gax.py | 32 +++++++++-------- .../google/cloud/error_reporting/_logging.py | 16 ++++++--- .../google/cloud/error_reporting/client.py | 23 ++++++++++--- error_reporting/setup.py | 2 +- error_reporting/unit_tests/test_client.py | 34 +++++++++---------- error_reporting/unit_tests/test_gax.py | 13 +++++++ error_reporting/unit_tests/test_logging.py | 13 +++++++ 7 files changed, 91 insertions(+), 42 deletions(-) create mode 100644 error_reporting/unit_tests/test_gax.py create mode 100644 error_reporting/unit_tests/test_logging.py diff --git a/error_reporting/google/cloud/error_reporting/_gax.py b/error_reporting/google/cloud/error_reporting/_gax.py index 3bafea7fe1a8..9f6e95d1a659 100644 --- a/error_reporting/google/cloud/error_reporting/_gax.py +++ b/error_reporting/google/cloud/error_reporting/_gax.py @@ -14,24 +14,28 @@ """GAX wrapper for Error Reporting API requests.""" -from google.cloud.gapic.errorreporting.v1beta1 import report_errors_service_api -from google.devtools.clouderrorreporting.v1beta1 import report_errors_service_pb2 +from google.cloud.gapic.errorreporting.v1beta1 import ( + report_errors_service_client) +from google.cloud.grpc.devtools.clouderrorreporting.v1beta1 import ( + report_errors_service_pb2) from google.protobuf.json_format import ParseDict def make_report_error_api(project): """Create an instance of the GAX Logging API.""" - api = report_errors_service_api.ReportErrorsServiceApi() - return _ErrorReportingGaxApi(api, project) + client = report_errors_service_client.ReportErrorsServiceClient() + return _ErrorReportingGaxApi(client, project) class _ErrorReportingGaxApi(object): """Helper mapping Error Reporting-related APIs :type gax_api: - :class:`google.cloud.gapic.errorreporting.v1beta1 - .report_errors_service_api.report_errors_service_api` + :class:`v1beta1.report_errors_service_client.ReportErrorsServiceClient` :param gax_api: API object used to make GAX requests. + + :type project: str + :param project: Google Cloud Project ID """ def __init__(self, gax_api, project): self._gax_api = gax_api @@ -40,15 +44,13 @@ def __init__(self, gax_api, project): def report_error_event(self, error_report): """Uses the GAX client to report the error. - :type project: str - :param: project: Project ID to report the error to - - :type error: dict: - :param: error: dict payload of the error report formatted - according to - https://cloud.google.com/error-reporting/docs/formatting-error-messages - This object should be built using - Use :meth:~`google.cloud.error_reporting.client._build_error_report` + :type error_report: dict + :param error_report: + payload of the error report formatted according to + https://cloud.google.com/error-reporting/docs/formatting-error-messages + This object should be built using + Use + :meth:~`google.cloud.error_reporting.client._build_error_report` """ project_name = self._gax_api.project_path(self._project) error_report_payload = report_errors_service_pb2.ReportedErrorEvent() diff --git a/error_reporting/google/cloud/error_reporting/_logging.py b/error_reporting/google/cloud/error_reporting/_logging.py index 7737312cb860..6b50312bbe0c 100644 --- a/error_reporting/google/cloud/error_reporting/_logging.py +++ b/error_reporting/google/cloud/error_reporting/_logging.py @@ -23,12 +23,20 @@ class _ErrorReportingLoggingAPI(object): - """Report to Stackdriver Error Reporting via Logging API - """ - def __init__(self, project, credentials, http): + """Report to Stackdriver Error Reporting via Logging API""" + def __init__(self, project, credentials=None, http=None): self.logging_client = google.cloud.logging.client.Client( project, credentials, http) - def report_error_event(self, project, error_report): + def report_error_event(self, error_report): + """Report error payload. + + :type error_report: dict + :param: error_report: + dict payload of the error report formatted according to + https://cloud.google.com/error-reporting/docs/formatting-error-messages + This object should be built using + :meth:~`google.cloud.error_reporting.client._build_error_report` + """ logger = self.logging_client.logger('errors') logger.log_struct(error_report) diff --git a/error_reporting/google/cloud/error_reporting/client.py b/error_reporting/google/cloud/error_reporting/client.py index ead78a252a2e..57d76ea0873b 100644 --- a/error_reporting/google/cloud/error_reporting/client.py +++ b/error_reporting/google/cloud/error_reporting/client.py @@ -151,6 +151,12 @@ def report_errors_api(self): See: https://cloud.google.com/logging/docs/api/reference/rest/v2/entries https://cloud.google.com/logging/docs/api/reference/rest/v2/projects.logs + + :rtype: + :class:`_gax._ErrorReportingGaxApi` + or + :class:`._logging._ErrorReportingLoggingAPI` + :returns: A class that implements the report errors API. """ if self._report_errors_api is None: if self._use_gax: @@ -160,8 +166,11 @@ def report_errors_api(self): self._project, self._credentials, self._http) return self._report_errors_api - def _build_error_report(self, message, - report_location=None, http_context=None, user=None): + def _build_error_report(self, + message, + report_location=None, + http_context=None, + user=None): """Builds the Error Reporting object to report. This builds the object according to @@ -194,6 +203,9 @@ def _build_error_report(self, message, logged in. In this case the Error Reporting system will use other data, such as remote IP address, to distinguish affected users. + :rtype: dict + :returns: A dict payload ready to be serialized to JSON and sent to + the API. """ payload = { 'serviceContext': { @@ -223,8 +235,11 @@ def _build_error_report(self, message, payload['context']['user'] = user return payload - def _send_error_report(self, message, - report_location=None, http_context=None, user=None): + def _send_error_report(self, + message, + report_location=None, + http_context=None, + user=None): """Makes the call to the Error Reporting API. This is the lower-level interface to build and send the payload, diff --git a/error_reporting/setup.py b/error_reporting/setup.py index 153e0f8a9411..5035847a2108 100644 --- a/error_reporting/setup.py +++ b/error_reporting/setup.py @@ -52,7 +52,7 @@ REQUIREMENTS = [ 'google-cloud-core >= 0.22.1, < 0.23dev', 'google-cloud-logging >= 0.22.0, < 0.23dev', - 'gapic-google-cloud-error-reporting-v1beta1 >= 0.13.0, < 0.14dev' + 'gapic-google-cloud-error-reporting-v1beta1 >= 0.14.0, < 0.15dev' ] setup( diff --git a/error_reporting/unit_tests/test_client.py b/error_reporting/unit_tests/test_client.py index 97223d33059c..63beed79af1c 100644 --- a/error_reporting/unit_tests/test_client.py +++ b/error_reporting/unit_tests/test_client.py @@ -82,7 +82,10 @@ def test_report_exception_with_gax(self): self.assertIn('test_report', payload['message']) self.assertIn('test_client.py', payload['message']) - def test_build_report_with_service_version_in_constructor(self): + @mock.patch('google.cloud.error_reporting.client.make_report_error_api') + def test_report_exception_with_service_version_in_constructor( + self, + make_client): CREDENTIALS = _make_credentials() SERVICE = "notdefault" VERSION = "notdefaultversion" @@ -91,16 +94,18 @@ def test_build_report_with_service_version_in_constructor(self): service=SERVICE, version=VERSION) - MESSAGE = 'this is an error' http_context = self._makeHTTP(method="GET", response_status_code=500) USER = "user@gmail.com" + client = mock.Mock() + make_client.return_value = client + try: raise NameError except NameError: target.report_exception(http_context=http_context, user=USER) - payload = target.build_error_report(MESSAGE) + payload = client.report_error_event.call_args[0][0] self.assertEquals(payload['serviceContext'], { 'service': SERVICE, 'version': VERSION @@ -115,13 +120,19 @@ def test_build_report_with_service_version_in_constructor(self): payload['context']['httpContext']['method'], 'GET') self.assertEquals(payload['context']['user'], USER) - def test_build_report(self): + @mock.patch('google.cloud.error_reporting.client.make_report_error_api') + def test_report(self, make_client): CREDENTIALS = _make_credentials() target = self._make_one(project=self.PROJECT, credentials=CREDENTIALS) + client = mock.Mock() + make_client.return_value = client + MESSAGE = 'this is an error' - payload = target._build_error_report(MESSAGE) + target.report(MESSAGE) + + payload = client.report_error_event.call_args[0][0] self.assertEquals(payload['message'], MESSAGE) report_location = payload['context']['reportLocation'] @@ -129,16 +140,3 @@ def test_build_report(self): self.assertEqual(report_location['functionName'], 'test_report') self.assertGreater(report_location['lineNumber'], 100) self.assertLess(report_location['lineNumber'], 150) - - -""" -class _Logger(object): - - def log_struct(self, payload, # pylint: disable=unused-argument - client=None, # pylint: disable=unused-argument - labels=None, # pylint: disable=unused-argument - insert_id=None, # pylint: disable=unused-argument - severity=None, # pylint: disable=unused-argument - http_request=None): # pylint: disable=unused-argument - self.log_struct_called_with = payload -""" diff --git a/error_reporting/unit_tests/test_gax.py b/error_reporting/unit_tests/test_gax.py new file mode 100644 index 000000000000..7c07b241f066 --- /dev/null +++ b/error_reporting/unit_tests/test_gax.py @@ -0,0 +1,13 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/error_reporting/unit_tests/test_logging.py b/error_reporting/unit_tests/test_logging.py new file mode 100644 index 000000000000..7c07b241f066 --- /dev/null +++ b/error_reporting/unit_tests/test_logging.py @@ -0,0 +1,13 @@ +# Copyright 2017 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. From 128cc3a5b1a6efe2bccfc258ccfd4be638683441 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Fri, 27 Jan 2017 13:06:45 -0800 Subject: [PATCH 05/14] Add tests --- error_reporting/unit_tests/test_gax.py | 30 +++++++++++++++++ error_reporting/unit_tests/test_logging.py | 38 ++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/error_reporting/unit_tests/test_gax.py b/error_reporting/unit_tests/test_gax.py index 7c07b241f066..4141c20dab88 100644 --- a/error_reporting/unit_tests/test_gax.py +++ b/error_reporting/unit_tests/test_gax.py @@ -11,3 +11,33 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +import unittest + +import mock + + +class Test_ErrorReportingGaxApi(unittest.TestCase): + + PROJECT = 'PROJECT' + + def _call_fut(self, gax_api, project): + from google.cloud.error_reporting._gax import _ErrorReportingGaxApi + return _ErrorReportingGaxApi(gax_api, project) + + def test_constructor(self): + gax_api = mock.Mock() + gax_client_wrapper = self._call_fut(gax_api, self.PROJECT) + + self.assertEqual(gax_client_wrapper._project, self.PROJECT) + self.assertEqual(gax_client_wrapper._gax_api, gax_api) + + @mock.patch("google.cloud.error_reporting._gax.ParseDict") + def test_report_error_event(self, parse_dict): + gax_api = mock.Mock() + gax_client_wrapper = self._call_fut(gax_api, self.PROJECT) + + mock_error_report = mock.Mock() + gax_client_wrapper.report_error_event(mock_error_report) + self.assertTrue(gax_api.report_error_event.called_with, + mock_error_report) diff --git a/error_reporting/unit_tests/test_logging.py b/error_reporting/unit_tests/test_logging.py index 7c07b241f066..84a1134b62d2 100644 --- a/error_reporting/unit_tests/test_logging.py +++ b/error_reporting/unit_tests/test_logging.py @@ -11,3 +11,41 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +import unittest + +import mock + + +def _make_credentials(): + import google.auth.credentials + return mock.Mock(spec=google.auth.credentials.Credentials) + + +class Test_ErrorReportingLoggingAPI(unittest.TestCase): + + PROJECT = 'PROJECT' + SERVICE = 'SERVICE' + VERSION = 'myversion' + + def _call_fut(self, project, credentials): + from google.cloud.error_reporting._logging import ( + _ErrorReportingLoggingAPI) + return _ErrorReportingLoggingAPI(project, credentials) + + def test_constructor(self): + credentials = _make_credentials() + logger_client = self._call_fut(self.PROJECT, credentials) + + self.assertEqual(logger_client.logging_client._connection.credentials, + credentials) + self.assertEqual(logger_client.logging_client.project, self.PROJECT) + + @mock.patch('google.cloud.logging.client') + def test_report_error_event(self, logging_client): + credentials = _make_credentials() + logger_client = self._call_fut(self.PROJECT, credentials) + payload = mock.Mock() + logger_client.report_error_event(payload) + logger_mock = mock.Mock() + self.assertTrue(logger_mock.log_struct.called_with, payload) From a51d6871f7da0297ebb96833bc36c2aceffefaad Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Fri, 27 Jan 2017 13:12:50 -0800 Subject: [PATCH 06/14] Minor fixup --- error_reporting/google/cloud/error_reporting/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/error_reporting/google/cloud/error_reporting/client.py b/error_reporting/google/cloud/error_reporting/client.py index 57d76ea0873b..5dc583e3a2b4 100644 --- a/error_reporting/google/cloud/error_reporting/client.py +++ b/error_reporting/google/cloud/error_reporting/client.py @@ -229,7 +229,7 @@ def _build_error_report(self, payload['context']['httpContext'] = { key: value for key, value in six.iteritems(http_context_dict) if value is not None - } + } if user: payload['context']['user'] = user From e6c3547bba0f0268f1e2864fc297d97bf77f8e4a Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Fri, 27 Jan 2017 13:42:16 -0800 Subject: [PATCH 07/14] Fix coverage --- .../google/cloud/error_reporting/_logging.py | 20 ++++++++++++- error_reporting/tox.ini | 2 +- error_reporting/unit_tests/test_client.py | 30 ++++++++++++++++--- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/error_reporting/google/cloud/error_reporting/_logging.py b/error_reporting/google/cloud/error_reporting/_logging.py index 6b50312bbe0c..59e1154c2b17 100644 --- a/error_reporting/google/cloud/error_reporting/_logging.py +++ b/error_reporting/google/cloud/error_reporting/_logging.py @@ -23,7 +23,25 @@ class _ErrorReportingLoggingAPI(object): - """Report to Stackdriver Error Reporting via Logging API""" + """Report to Stackdriver Error Reporting via Logging API + + :type project: str + :param project: the project which the client acts on behalf of. If not + passed falls back to the default inferred from the + environment. + + :type credentials: :class:`oauth2client.client.OAuth2Credentials` or + :class:`NoneType` + :param credentials: The OAuth2 Credentials to use for the connection + owned by this client. If not passed (and if no ``http`` + object is passed), falls back to the default inferred + from the environment. + + :type http: :class:`httplib2.Http` or class that defines ``request()``. + :param http: An optional HTTP object to make requests. If not passed, an + ``http`` object is created that is bound to the + ``credentials`` for the current object. + """ def __init__(self, project, credentials=None, http=None): self.logging_client = google.cloud.logging.client.Client( project, credentials, http) diff --git a/error_reporting/tox.ini b/error_reporting/tox.ini index e18578ba64ac..63c0e63bb2cd 100644 --- a/error_reporting/tox.ini +++ b/error_reporting/tox.ini @@ -14,7 +14,7 @@ deps = pytest covercmd = py.test --quiet \ - --cov=google.cloud.error-reporting \ + --cov=google.cloud.error_reporting \ --cov=unit_tests \ --cov-config {toxinidir}/.coveragerc \ unit_tests diff --git a/error_reporting/unit_tests/test_client.py b/error_reporting/unit_tests/test_client.py index 63beed79af1c..e5d679e3fdb1 100644 --- a/error_reporting/unit_tests/test_client.py +++ b/error_reporting/unit_tests/test_client.py @@ -48,9 +48,7 @@ def _makeHTTP(self, *args, **kw): VERSION = 'myversion' def test_ctor_default(self): - CREDENTIALS = _make_credentials() - target = self._make_one(project=self.PROJECT, - credentials=CREDENTIALS) + target = self._make_one() self.assertEquals(target.service, target.DEFAULT_SERVICE) self.assertEquals(target.version, None) @@ -82,6 +80,30 @@ def test_report_exception_with_gax(self): self.assertIn('test_report', payload['message']) self.assertIn('test_client.py', payload['message']) + def test_report_exception_wo_gax(self): + CREDENTIALS = _make_credentials() + target = self._make_one(project=self.PROJECT, + credentials=CREDENTIALS, + use_gax=False) + + patch = mock.patch( + 'google.cloud.error_reporting.client._ErrorReportingLoggingAPI' + ) + with patch as _ErrorReportingLogging: + try: + raise NameError + except NameError: + target.report_exception() + mock_report = _ErrorReportingLogging.return_value.report_error_event + payload = mock_report.call_args[0][0] + + self.assertEquals(payload['serviceContext'], { + 'service': target.DEFAULT_SERVICE, + }) + self.assertIn('test_report', payload['message']) + self.assertIn('test_client.py', payload['message']) + + @mock.patch('google.cloud.error_reporting.client.make_report_error_api') def test_report_exception_with_service_version_in_constructor( self, @@ -139,4 +161,4 @@ def test_report(self, make_client): self.assertIn('test_client.py', report_location['filePath']) self.assertEqual(report_location['functionName'], 'test_report') self.assertGreater(report_location['lineNumber'], 100) - self.assertLess(report_location['lineNumber'], 150) + self.assertLess(report_location['lineNumber'], 250) From de8daaa48a5871993349d7c709d044da705e9255 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Fri, 27 Jan 2017 15:25:37 -0800 Subject: [PATCH 08/14] More mixups --- .../google/cloud/error_reporting/_gax.py | 24 +++++++++++++++---- .../unit_tests/{test_gax.py => test__gax.py} | 2 +- .../{test_logging.py => test__logging.py} | 2 +- error_reporting/unit_tests/test_client.py | 9 ++++--- 4 files changed, 26 insertions(+), 11 deletions(-) rename error_reporting/unit_tests/{test_gax.py => test__gax.py} (96%) rename error_reporting/unit_tests/{test_logging.py => test__logging.py} (96%) diff --git a/error_reporting/google/cloud/error_reporting/_gax.py b/error_reporting/google/cloud/error_reporting/_gax.py index 9f6e95d1a659..d23a84fbade4 100644 --- a/error_reporting/google/cloud/error_reporting/_gax.py +++ b/error_reporting/google/cloud/error_reporting/_gax.py @@ -14,6 +14,9 @@ """GAX wrapper for Error Reporting API requests.""" +from google.cloud._helpers import make_secure_channel +from google.cloud._http import DEFAULT_USER_AGENT + from google.cloud.gapic.errorreporting.v1beta1 import ( report_errors_service_client) from google.cloud.grpc.devtools.clouderrorreporting.v1beta1 import ( @@ -21,10 +24,22 @@ from google.protobuf.json_format import ParseDict -def make_report_error_api(project): - """Create an instance of the GAX Logging API.""" - client = report_errors_service_client.ReportErrorsServiceClient() - return _ErrorReportingGaxApi(client, project) +def make_report_error_api(client): + """Create an instance of the GAX Logging API. + + :type client::class:`google.cloud.error_reporting.Client` + :param client: Error Reporting client + + :rtype: :class:_ErrorReportingGaxApi + :returns: An Error Reporting API instane + """ + channel = make_secure_channel( + client._connection.credentials, + DEFAULT_USER_AGENT, + report_errors_service_client.ReportErrorsServiceClient.SERVICE_ADDRESS) + gax_client = report_errors_service_client.ReportErrorsServiceClient( + channel=channel) + return _ErrorReportingGaxApi(gax_client, client.project) class _ErrorReportingGaxApi(object): @@ -37,6 +52,7 @@ class _ErrorReportingGaxApi(object): :type project: str :param project: Google Cloud Project ID """ + def __init__(self, gax_api, project): self._gax_api = gax_api self._project = project diff --git a/error_reporting/unit_tests/test_gax.py b/error_reporting/unit_tests/test__gax.py similarity index 96% rename from error_reporting/unit_tests/test_gax.py rename to error_reporting/unit_tests/test__gax.py index 4141c20dab88..5a67fd739a95 100644 --- a/error_reporting/unit_tests/test_gax.py +++ b/error_reporting/unit_tests/test__gax.py @@ -33,7 +33,7 @@ def test_constructor(self): self.assertEqual(gax_client_wrapper._gax_api, gax_api) @mock.patch("google.cloud.error_reporting._gax.ParseDict") - def test_report_error_event(self, parse_dict): + def test_report_error_event(self, _): gax_api = mock.Mock() gax_client_wrapper = self._call_fut(gax_api, self.PROJECT) diff --git a/error_reporting/unit_tests/test_logging.py b/error_reporting/unit_tests/test__logging.py similarity index 96% rename from error_reporting/unit_tests/test_logging.py rename to error_reporting/unit_tests/test__logging.py index 84a1134b62d2..99371906eedc 100644 --- a/error_reporting/unit_tests/test_logging.py +++ b/error_reporting/unit_tests/test__logging.py @@ -42,7 +42,7 @@ def test_constructor(self): self.assertEqual(logger_client.logging_client.project, self.PROJECT) @mock.patch('google.cloud.logging.client') - def test_report_error_event(self, logging_client): + def test_report_error_event(self, _): credentials = _make_credentials() logger_client = self._call_fut(self.PROJECT, credentials) payload = mock.Mock() diff --git a/error_reporting/unit_tests/test_client.py b/error_reporting/unit_tests/test_client.py index e5d679e3fdb1..c1e03d0b1c65 100644 --- a/error_reporting/unit_tests/test_client.py +++ b/error_reporting/unit_tests/test_client.py @@ -85,24 +85,23 @@ def test_report_exception_wo_gax(self): target = self._make_one(project=self.PROJECT, credentials=CREDENTIALS, use_gax=False) - patch = mock.patch( 'google.cloud.error_reporting.client._ErrorReportingLoggingAPI' ) - with patch as _ErrorReportingLogging: + with patch as _error_api: try: raise NameError except NameError: target.report_exception() - mock_report = _ErrorReportingLogging.return_value.report_error_event + mock_report = _error_api.return_value.report_error_event payload = mock_report.call_args[0][0] self.assertEquals(payload['serviceContext'], { 'service': target.DEFAULT_SERVICE, - }) + }) self.assertIn('test_report', payload['message']) self.assertIn('test_client.py', payload['message']) - + self.assertIsNotNone(target.report_errors_api) @mock.patch('google.cloud.error_reporting.client.make_report_error_api') def test_report_exception_with_service_version_in_constructor( From 4b9c8faf1285385ee85ac51a9e31c0bcde599778 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Fri, 27 Jan 2017 15:30:49 -0800 Subject: [PATCH 09/14] Fix coverage --- error_reporting/unit_tests/test__gax.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/error_reporting/unit_tests/test__gax.py b/error_reporting/unit_tests/test__gax.py index 5a67fd739a95..af0c4247dae4 100644 --- a/error_reporting/unit_tests/test__gax.py +++ b/error_reporting/unit_tests/test__gax.py @@ -17,6 +17,16 @@ import mock +class Test_make_report_error_api(unittest.TestCase): + + def test_make_report_error_api(self): + from google.cloud.error_reporting._gax import make_report_error_api + client = mock.Mock() + client.project = mock.Mock() + report_error_client = make_report_error_api(client) + self.assertEqual(report_error_client._project, client.project) + + class Test_ErrorReportingGaxApi(unittest.TestCase): PROJECT = 'PROJECT' From 602e313fc5898bcd6088b0c6536d0fa9dc51faca Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Fri, 27 Jan 2017 15:32:53 -0800 Subject: [PATCH 10/14] Add credentials --- error_reporting/unit_tests/test_client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/error_reporting/unit_tests/test_client.py b/error_reporting/unit_tests/test_client.py index c1e03d0b1c65..30ccbd8782a9 100644 --- a/error_reporting/unit_tests/test_client.py +++ b/error_reporting/unit_tests/test_client.py @@ -48,7 +48,8 @@ def _makeHTTP(self, *args, **kw): VERSION = 'myversion' def test_ctor_default(self): - target = self._make_one() + CREDENTIALS = _make_credentials() + target = self._make_one(credentials=CREDENTIALS) self.assertEquals(target.service, target.DEFAULT_SERVICE) self.assertEquals(target.version, None) From 88873ba7922ae79a0f0c95448c6220a098c636a5 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Fri, 27 Jan 2017 15:39:12 -0800 Subject: [PATCH 11/14] Fix determine --- error_reporting/unit_tests/test_client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/error_reporting/unit_tests/test_client.py b/error_reporting/unit_tests/test_client.py index 30ccbd8782a9..4cb7f41c0e37 100644 --- a/error_reporting/unit_tests/test_client.py +++ b/error_reporting/unit_tests/test_client.py @@ -47,6 +47,8 @@ def _makeHTTP(self, *args, **kw): SERVICE = 'SERVICE' VERSION = 'myversion' + @mock.patch( + 'google.cloud.error_reporting.client._determine_default_project') def test_ctor_default(self): CREDENTIALS = _make_credentials() target = self._make_one(credentials=CREDENTIALS) From 3d956a5531bdf7476a2e25f27ed0aafc75af13aa Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Fri, 27 Jan 2017 15:53:48 -0800 Subject: [PATCH 12/14] Fix up --- error_reporting/unit_tests/test_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/error_reporting/unit_tests/test_client.py b/error_reporting/unit_tests/test_client.py index 4cb7f41c0e37..09b9065f59f9 100644 --- a/error_reporting/unit_tests/test_client.py +++ b/error_reporting/unit_tests/test_client.py @@ -49,7 +49,7 @@ def _makeHTTP(self, *args, **kw): @mock.patch( 'google.cloud.error_reporting.client._determine_default_project') - def test_ctor_default(self): + def test_ctor_default(self, _): CREDENTIALS = _make_credentials() target = self._make_one(credentials=CREDENTIALS) self.assertEquals(target.service, target.DEFAULT_SERVICE) From fa382b306e8476da0278309dda76e382125b0301 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Thu, 9 Feb 2017 15:37:02 -0800 Subject: [PATCH 13/14] sneeringer review --- error_reporting/google/cloud/error_reporting/client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/error_reporting/google/cloud/error_reporting/client.py b/error_reporting/google/cloud/error_reporting/client.py index 5dc583e3a2b4..4d8a0c2a4a48 100644 --- a/error_reporting/google/cloud/error_reporting/client.py +++ b/error_reporting/google/cloud/error_reporting/client.py @@ -19,10 +19,9 @@ try: from google.cloud.error_reporting._gax import make_report_error_api + _HAVE_GAX = True except ImportError: # pragma: NO COVER _HAVE_GAX = False -else: - _HAVE_GAX = True from google.cloud._helpers import _determine_default_project from google.cloud.error_reporting._logging import _ErrorReportingLoggingAPI From 58b1e76d5887b40e50ef206f7f1e0eb2ede796c2 Mon Sep 17 00:00:00 2001 From: Bill Prin Date: Thu, 9 Feb 2017 15:59:03 -0800 Subject: [PATCH 14/14] typos --- error_reporting/google/cloud/error_reporting/_gax.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/error_reporting/google/cloud/error_reporting/_gax.py b/error_reporting/google/cloud/error_reporting/_gax.py index d23a84fbade4..e480fcf7380a 100644 --- a/error_reporting/google/cloud/error_reporting/_gax.py +++ b/error_reporting/google/cloud/error_reporting/_gax.py @@ -28,10 +28,10 @@ def make_report_error_api(client): """Create an instance of the GAX Logging API. :type client::class:`google.cloud.error_reporting.Client` - :param client: Error Reporting client + :param client: Error Reporting client. :rtype: :class:_ErrorReportingGaxApi - :returns: An Error Reporting API instane + :returns: An Error Reporting API instance. """ channel = make_secure_channel( client._connection.credentials,