Skip to content

Commit 69603fb

Browse files
author
Bill Prin
committed
Add Error Reporting Client
1 parent d958f74 commit 69603fb

File tree

7 files changed

+324
-0
lines changed

7 files changed

+324
-0
lines changed

docs/error-reporting-client.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Error Reporting Client
2+
=======================
3+
4+
.. automodule:: gcloud.error_reporting.client
5+
:members:
6+
:show-inheritance:
7+

docs/error-reporting-usage.rst

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
Using the API
2+
=============
3+
4+
5+
Authentication and Configuration
6+
--------------------------------
7+
8+
- For an overview of authentication in ``gcloud-python``,
9+
see :doc:`gcloud-auth`.
10+
11+
- In addition to any authentication configuration, you should also set the
12+
:envvar:`GCLOUD_PROJECT` environment variable for the project you'd like
13+
to interact with. If you are Google App Engine or Google Compute Engine
14+
this will be detected automatically.
15+
16+
- After configuring your environment, create a
17+
:class:`Client <gcloud.logging.client.Client>`
18+
19+
.. doctest::
20+
21+
>>> from gcloud import error_reporting
22+
>>> client = error_reporting.Client()
23+
24+
or pass in ``credentials`` and ``project`` explicitly
25+
26+
.. doctest::
27+
28+
>>> from gcloud import error_reporting
29+
>>> client = error_reporting.Client(project='my-project', credentials=creds)
30+
31+
Error Reporting associates errors with a service, which is an identifier for an executable,
32+
App Engine module, or job. The default service is "python", but a default can be specified
33+
for the client on construction time.
34+
35+
.. doctest::
36+
37+
>>> from gcloud import error_reporting
38+
>>> client = error_reporting.Client(project='my-project', service="login_service")
39+
40+
41+
Reporting an exception
42+
-----------------------
43+
44+
Report a stacktrace to Stackdriver Error Reporting after an exception
45+
46+
.. doctest::
47+
48+
>>> from gcloud import error_reporting
49+
>>> client = error_reporting.Client()
50+
>>> try:
51+
>>> raise NameError
52+
>>> except Exception:
53+
>>> client.report_error(message="Something went wrong")
54+
55+
56+
By default, the client will report the error using the service specified in the client's
57+
constructor, or the default service of "python". The service can also be manually specified
58+
in the parameters:
59+
60+
.. doctest::
61+
62+
>>> try:
63+
>>> raise NameError
64+
>>> except Exception:
65+
>>> client.report_error(message="Something went wrong", service="login_service")

docs/index.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,14 @@
111111
logging-metric
112112
logging-sink
113113

114+
.. toctree::
115+
:maxdepth: 0
116+
:hidden:
117+
:caption: Stackdriver Error Reporting
118+
119+
error-reporting-usage
120+
Client <error-reporting-client>
121+
114122
.. toctree::
115123
:maxdepth: 0
116124
:hidden:

gcloud/error_reporting/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env python
2+
# Copyright 2016 Google Inc. All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""Client library for Stackdriver Error Reporting"""
17+
18+
from gcloud.error_reporting.client import Client

gcloud/error_reporting/client.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/usr/bin/env python
2+
# Copyright 2016 Google Inc. All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""Client for interacting with the Stackdriver Logging API"""
17+
18+
import traceback
19+
20+
import gcloud.logging.client
21+
22+
23+
class Client(object):
24+
"""Error Reporting client. Currently Error Reporting is done by creating
25+
a Logging client.
26+
27+
:type project: string
28+
:param project: the project which the client acts on behalf of. If not
29+
passed falls back to the default inferred from the
30+
environment.
31+
32+
:type credentials: :class:`oauth2client.client.OAuth2Credentials` or
33+
:class:`NoneType`
34+
:param credentials: The OAuth2 Credentials to use for the connection
35+
owned by this client. If not passed (and if no ``http``
36+
object is passed), falls back to the default inferred
37+
from the environment.
38+
39+
:type http: :class:`httplib2.Http` or class that defines ``request()``.
40+
:param http: An optional HTTP object to make requests. If not passed, an
41+
``http`` object is created that is bound to the
42+
``credentials`` for the current object.
43+
44+
:type service: str
45+
:param service: An identifier of the service, such as the name of the
46+
executable, job, or Google App Engine module name. This
47+
field is expected to have a low number of values that are
48+
relatively stable over time, as opposed to version,
49+
which can be changed whenever new code is deployed.
50+
51+
:raises: :class:`ValueError` if the project is neither passed in nor
52+
set in the environment.
53+
"""
54+
55+
def __init__(self, project=None,
56+
credentials=None,
57+
http=None,
58+
service=None):
59+
self.logging_client = gcloud.logging.client.Client(
60+
project, credentials, http)
61+
self.service = service
62+
63+
DEFAULT_SERVICE = 'python'
64+
65+
def _get_default_service(self):
66+
"""Returns the service to use on method calls that don't specify an
67+
override.
68+
69+
:rtype: string
70+
:returns: The default service for error reporting calls
71+
"""
72+
if self.service:
73+
return self.service
74+
else:
75+
return self.DEFAULT_SERVICE
76+
77+
def report_error(self, message="", service=None):
78+
""" Reports the details of the latest exceptions to Stackdriver Error
79+
Reporting.
80+
81+
https://cloud.google.com/error-reporting/docs/formatting-error-messages
82+
83+
:type message: str
84+
:param message: An optional message to include with the exception
85+
detail
86+
87+
:type service: str
88+
:param service: An identifier of the service, such as the name of
89+
the executable, job, or Google App Engine module
90+
name. This field is expected to have a low number
91+
of values that are relatively stable over time,
92+
as opposed to version, which can be changed
93+
whenever new code is deployed.
94+
95+
Example::
96+
97+
>>> try:
98+
>>> raise NameError
99+
>>> except Exception:
100+
>>> client.report_error("Something went wrong!")
101+
"""
102+
if not service:
103+
service = self._get_default_service()
104+
payload = {
105+
'serviceContext': {'service': service},
106+
'message': '{0} : {1}'.format(message, traceback.format_exc())
107+
}
108+
logger = self.logging_client.logger('errors')
109+
logger.log_struct(payload)

gcloud/error_reporting/test_client.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env python
2+
# Copyright 2016 Google Inc. All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
17+
import unittest2
18+
19+
20+
class TestClient(unittest2.TestCase):
21+
22+
def _getTargetClass(self):
23+
from gcloud.error_reporting.client import Client
24+
return Client
25+
26+
def _makeOne(self, *args, **kw):
27+
return self._getTargetClass()(*args, **kw)
28+
29+
PROJECT = 'PROJECT'
30+
SERVICE = 'SERVICE'
31+
32+
def test_ctor(self):
33+
CREDENTIALS = _Credentials()
34+
target = self._makeOne(project=self.PROJECT,
35+
credentials=CREDENTIALS)
36+
self.assertEquals(target._get_default_service(),
37+
target.DEFAULT_SERVICE)
38+
39+
def test_ctor_service(self):
40+
CREDENTIALS = _Credentials()
41+
target = self._makeOne(project=self.PROJECT,
42+
credentials=CREDENTIALS,
43+
service=self.SERVICE)
44+
self.assertEquals(target.service, self.SERVICE)
45+
self.assertEquals(target._get_default_service(), self.SERVICE)
46+
47+
def test_report_error(self):
48+
CREDENTIALS = _Credentials()
49+
target = self._makeOne(project=self.PROJECT,
50+
credentials=CREDENTIALS)
51+
MESSAGE = 'hello world'
52+
53+
logger = _Logger()
54+
target.logging_client.logger = lambda _: logger
55+
56+
try:
57+
raise NameError
58+
except NameError:
59+
target.report_error(MESSAGE)
60+
61+
payload = logger.log_struct_called_with
62+
self.assertEquals(payload['serviceContext'], {
63+
'service': target.DEFAULT_SERVICE
64+
})
65+
self.assertIn(MESSAGE, payload['message'])
66+
self.assertIn('test_report_error', payload['message'])
67+
self.assertIn('test_client.py', payload['message'])
68+
69+
def test_report_error_specify_service(self):
70+
CREDENTIALS = _Credentials()
71+
target = self._makeOne(project=self.PROJECT,
72+
credentials=CREDENTIALS)
73+
MESSAGE = 'hello world'
74+
SERVICE = "notdefault"
75+
76+
logger = _Logger()
77+
target.logging_client.logger = lambda _: logger
78+
79+
try:
80+
raise NameError
81+
except NameError:
82+
target.report_error(MESSAGE, service=SERVICE)
83+
84+
payload = logger.log_struct_called_with
85+
self.assertEquals(payload['serviceContext'], {
86+
'service': SERVICE
87+
})
88+
self.assertIn(MESSAGE, payload['message'])
89+
self.assertIn('test_report_error', payload['message'])
90+
self.assertIn('test_client.py', payload['message'])
91+
92+
93+
class _Credentials(object):
94+
95+
_scopes = None
96+
97+
@staticmethod
98+
def create_scoped_required():
99+
return True
100+
101+
def create_scoped(self, scope):
102+
self._scopes = scope
103+
return self
104+
105+
106+
class _Logger(object):
107+
108+
def log_struct(self, payload, # pylint: disable=unused-argument
109+
client=None, # pylint: disable=unused-argument
110+
labels=None, # pylint: disable=unused-argument
111+
insert_id=None, # pylint: disable=unused-argument
112+
severity=None, # pylint: disable=unused-argument
113+
http_request=None): # pylint: disable=unused-argument
114+
self.log_struct_called_with = payload
115+
116+

scripts/verify_included_modules.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
'gcloud.bigtable.__init__',
3636
'gcloud.datastore.__init__',
3737
'gcloud.dns.__init__',
38+
'gcloud.error_reporting.__init__',
3839
'gcloud.iterator',
3940
'gcloud.logging.__init__',
4041
'gcloud.monitoring.__init__',

0 commit comments

Comments
 (0)