Skip to content

Commit ee4f2ad

Browse files
authored
Add Error Reporting GAPIC as dependency (googleapis#2894)
* Add Error Reporting GAPIC as dependency This both adds the GAPIC to be used directly via this package, as well as hooking it up to the existing helpers to provide gRPC support.
1 parent 0fb8746 commit ee4f2ad

File tree

8 files changed

+399
-49
lines changed

8 files changed

+399
-49
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Copyright 2016 Google Inc. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""GAX wrapper for Error Reporting API requests."""
16+
17+
from google.cloud._helpers import make_secure_channel
18+
from google.cloud._http import DEFAULT_USER_AGENT
19+
20+
from google.cloud.gapic.errorreporting.v1beta1 import (
21+
report_errors_service_client)
22+
from google.cloud.grpc.devtools.clouderrorreporting.v1beta1 import (
23+
report_errors_service_pb2)
24+
from google.protobuf.json_format import ParseDict
25+
26+
27+
def make_report_error_api(client):
28+
"""Create an instance of the GAX Logging API.
29+
30+
:type client::class:`google.cloud.error_reporting.Client`
31+
:param client: Error Reporting client.
32+
33+
:rtype: :class:_ErrorReportingGaxApi
34+
:returns: An Error Reporting API instance.
35+
"""
36+
channel = make_secure_channel(
37+
client._connection.credentials,
38+
DEFAULT_USER_AGENT,
39+
report_errors_service_client.ReportErrorsServiceClient.SERVICE_ADDRESS)
40+
gax_client = report_errors_service_client.ReportErrorsServiceClient(
41+
channel=channel)
42+
return _ErrorReportingGaxApi(gax_client, client.project)
43+
44+
45+
class _ErrorReportingGaxApi(object):
46+
"""Helper mapping Error Reporting-related APIs
47+
48+
:type gax_api:
49+
:class:`v1beta1.report_errors_service_client.ReportErrorsServiceClient`
50+
:param gax_api: API object used to make GAX requests.
51+
52+
:type project: str
53+
:param project: Google Cloud Project ID
54+
"""
55+
56+
def __init__(self, gax_api, project):
57+
self._gax_api = gax_api
58+
self._project = project
59+
60+
def report_error_event(self, error_report):
61+
"""Uses the GAX client to report the error.
62+
63+
:type error_report: dict
64+
:param error_report:
65+
payload of the error report formatted according to
66+
https://cloud.google.com/error-reporting/docs/formatting-error-messages
67+
This object should be built using
68+
Use
69+
:meth:~`google.cloud.error_reporting.client._build_error_report`
70+
"""
71+
project_name = self._gax_api.project_path(self._project)
72+
error_report_payload = report_errors_service_pb2.ReportedErrorEvent()
73+
ParseDict(error_report, error_report_payload)
74+
self._gax_api.report_error_event(project_name, error_report_payload)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Copyright 2016 Google Inc. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Interact with Stackdriver Error Reporting via Logging API.
16+
17+
It's possible to report Stackdriver Error Reporting errors by formatting
18+
structured log messages in Stackdriver Logging in a given format. This
19+
client provides a mechanism to report errors using that technique.
20+
"""
21+
22+
import google.cloud.logging.client
23+
24+
25+
class _ErrorReportingLoggingAPI(object):
26+
"""Report to Stackdriver Error Reporting via Logging API
27+
28+
:type project: str
29+
:param project: the project which the client acts on behalf of. If not
30+
passed falls back to the default inferred from the
31+
environment.
32+
33+
:type credentials: :class:`oauth2client.client.OAuth2Credentials` or
34+
:class:`NoneType`
35+
:param credentials: The OAuth2 Credentials to use for the connection
36+
owned by this client. If not passed (and if no ``http``
37+
object is passed), falls back to the default inferred
38+
from the environment.
39+
40+
:type http: :class:`httplib2.Http` or class that defines ``request()``.
41+
:param http: An optional HTTP object to make requests. If not passed, an
42+
``http`` object is created that is bound to the
43+
``credentials`` for the current object.
44+
"""
45+
def __init__(self, project, credentials=None, http=None):
46+
self.logging_client = google.cloud.logging.client.Client(
47+
project, credentials, http)
48+
49+
def report_error_event(self, error_report):
50+
"""Report error payload.
51+
52+
:type error_report: dict
53+
:param: error_report:
54+
dict payload of the error report formatted according to
55+
https://cloud.google.com/error-reporting/docs/formatting-error-messages
56+
This object should be built using
57+
:meth:~`google.cloud.error_reporting.client._build_error_report`
58+
"""
59+
logger = self.logging_client.logger('errors')
60+
logger.log_struct(error_report)

error_reporting/google/cloud/error_reporting/client.py

Lines changed: 110 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,26 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
"""Client for interacting with the Stackdriver Logging API"""
15+
"""Client for interacting with the Stackdriver Error Reporting API"""
1616

17+
import os
1718
import traceback
1819

19-
import google.cloud.logging.client
20+
try:
21+
from google.cloud.error_reporting._gax import make_report_error_api
22+
_HAVE_GAX = True
23+
except ImportError: # pragma: NO COVER
24+
_HAVE_GAX = False
25+
26+
from google.cloud._helpers import _determine_default_project
27+
from google.cloud.error_reporting._logging import _ErrorReportingLoggingAPI
28+
from google.cloud.environment_vars import DISABLE_GRPC
29+
2030
import six
2131

32+
_DISABLE_GAX = os.getenv(DISABLE_GRPC, False)
33+
_USE_GAX = _HAVE_GAX and not _DISABLE_GAX
34+
2235

2336
class HTTPContext(object):
2437
"""HTTPContext defines an object that captures the parameter for the
@@ -96,6 +109,12 @@ class Client(object):
96109
SHA-1 hash, for example. If the developer did not provide
97110
a version, the value is set to default.
98111
112+
:type use_gax: bool
113+
:param use_gax: (Optional) Explicitly specifies whether
114+
to use the gRPC transport (via GAX) or HTTP. If unset,
115+
falls back to the ``GOOGLE_CLOUD_DISABLE_GRPC`` environment
116+
variable.
117+
99118
:raises: :class:`ValueError` if the project is neither passed in nor
100119
set in the environment.
101120
"""
@@ -104,24 +123,56 @@ def __init__(self, project=None,
104123
credentials=None,
105124
http=None,
106125
service=None,
107-
version=None):
108-
self.logging_client = google.cloud.logging.client.Client(
109-
project, credentials, http)
126+
version=None,
127+
use_gax=None):
128+
if project is None:
129+
self._project = _determine_default_project()
130+
else:
131+
self._project = project
132+
self._credentials = credentials
133+
self._http = http
134+
135+
self._report_errors_api = None
136+
110137
self.service = service if service else self.DEFAULT_SERVICE
111138
self.version = version
139+
if use_gax is None:
140+
self._use_gax = _USE_GAX
141+
else:
142+
self._use_gax = use_gax
112143

113144
DEFAULT_SERVICE = 'python'
114145

115-
def _send_error_report(self, message,
116-
report_location=None, http_context=None, user=None):
117-
"""Makes the call to the Error Reporting API via the log stream.
146+
@property
147+
def report_errors_api(self):
148+
"""Helper for logging-related API calls.
118149
119-
This is the lower-level interface to build the payload, generally
120-
users will use either report() or report_exception() to automatically
121-
gather the parameters for this method.
150+
See:
151+
https://cloud.google.com/logging/docs/api/reference/rest/v2/entries
152+
https://cloud.google.com/logging/docs/api/reference/rest/v2/projects.logs
122153
123-
Currently this method sends the Error Report by formatting a structured
124-
log message according to
154+
:rtype:
155+
:class:`_gax._ErrorReportingGaxApi`
156+
or
157+
:class:`._logging._ErrorReportingLoggingAPI`
158+
:returns: A class that implements the report errors API.
159+
"""
160+
if self._report_errors_api is None:
161+
if self._use_gax:
162+
self._report_errors_api = make_report_error_api(self._project)
163+
else:
164+
self._report_errors_api = _ErrorReportingLoggingAPI(
165+
self._project, self._credentials, self._http)
166+
return self._report_errors_api
167+
168+
def _build_error_report(self,
169+
message,
170+
report_location=None,
171+
http_context=None,
172+
user=None):
173+
"""Builds the Error Reporting object to report.
174+
175+
This builds the object according to
125176
126177
https://cloud.google.com/error-reporting/docs/formatting-error-messages
127178
@@ -151,7 +202,10 @@ def _send_error_report(self, message,
151202
logged in. In this case the Error Reporting system will
152203
use other data, such as remote IP address,
153204
to distinguish affected users.
154-
"""
205+
:rtype: dict
206+
:returns: A dict payload ready to be serialized to JSON and sent to
207+
the API.
208+
"""
155209
payload = {
156210
'serviceContext': {
157211
'service': self.service,
@@ -178,9 +232,49 @@ def _send_error_report(self, message,
178232

179233
if user:
180234
payload['context']['user'] = user
235+
return payload
236+
237+
def _send_error_report(self,
238+
message,
239+
report_location=None,
240+
http_context=None,
241+
user=None):
242+
"""Makes the call to the Error Reporting API.
243+
244+
This is the lower-level interface to build and send the payload,
245+
generally users will use either report() or report_exception() to
246+
automatically gather the parameters for this method.
181247
182-
logger = self.logging_client.logger('errors')
183-
logger.log_struct(payload)
248+
:type message: str
249+
:param message: The stack trace that was reported or logged by the
250+
service.
251+
252+
:type report_location: dict
253+
:param report_location: The location in the source code where the
254+
decision was made to report the error, usually the place
255+
where it was logged. For a logged exception this would be the
256+
source line where the exception is logged, usually close to
257+
the place where it was caught.
258+
259+
This should be a Python dict that contains the keys 'filePath',
260+
'lineNumber', and 'functionName'
261+
262+
:type http_context: :class`google.cloud.error_reporting.HTTPContext`
263+
:param http_context: The HTTP request which was processed when the
264+
error was triggered.
265+
266+
:type user: str
267+
:param user: The user who caused or was affected by the crash. This can
268+
be a user ID, an email address, or an arbitrary token that
269+
uniquely identifies the user. When sending an error
270+
report, leave this field empty if the user was not
271+
logged in. In this case the Error Reporting system will
272+
use other data, such as remote IP address,
273+
to distinguish affected users.
274+
"""
275+
error_report = self._build_error_report(message, report_location,
276+
http_context, user)
277+
self.report_errors_api.report_error_event(error_report)
184278

185279
def report(self, message, http_context=None, user=None):
186280
""" Reports a message to Stackdriver Error Reporting

error_reporting/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
REQUIREMENTS = [
5353
'google-cloud-core >= 0.22.1, < 0.23dev',
5454
'google-cloud-logging >= 0.22.0, < 0.23dev',
55+
'gapic-google-cloud-error-reporting-v1beta1 >= 0.14.0, < 0.15dev'
5556
]
5657

5758
setup(

error_reporting/tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ deps =
1414
pytest
1515
covercmd =
1616
py.test --quiet \
17-
--cov=google.cloud.error-reporting \
17+
--cov=google.cloud.error_reporting \
1818
--cov=unit_tests \
1919
--cov-config {toxinidir}/.coveragerc \
2020
unit_tests
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Copyright 2017 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import unittest
16+
17+
import mock
18+
19+
20+
class Test_make_report_error_api(unittest.TestCase):
21+
22+
def test_make_report_error_api(self):
23+
from google.cloud.error_reporting._gax import make_report_error_api
24+
client = mock.Mock()
25+
client.project = mock.Mock()
26+
report_error_client = make_report_error_api(client)
27+
self.assertEqual(report_error_client._project, client.project)
28+
29+
30+
class Test_ErrorReportingGaxApi(unittest.TestCase):
31+
32+
PROJECT = 'PROJECT'
33+
34+
def _call_fut(self, gax_api, project):
35+
from google.cloud.error_reporting._gax import _ErrorReportingGaxApi
36+
return _ErrorReportingGaxApi(gax_api, project)
37+
38+
def test_constructor(self):
39+
gax_api = mock.Mock()
40+
gax_client_wrapper = self._call_fut(gax_api, self.PROJECT)
41+
42+
self.assertEqual(gax_client_wrapper._project, self.PROJECT)
43+
self.assertEqual(gax_client_wrapper._gax_api, gax_api)
44+
45+
@mock.patch("google.cloud.error_reporting._gax.ParseDict")
46+
def test_report_error_event(self, _):
47+
gax_api = mock.Mock()
48+
gax_client_wrapper = self._call_fut(gax_api, self.PROJECT)
49+
50+
mock_error_report = mock.Mock()
51+
gax_client_wrapper.report_error_event(mock_error_report)
52+
self.assertTrue(gax_api.report_error_event.called_with,
53+
mock_error_report)

0 commit comments

Comments
 (0)