Skip to content

Commit 58ef6a5

Browse files
author
Bill Prin
committed
Add Error Reporting Client
1 parent 771bc7a commit 58ef6a5

File tree

7 files changed

+369
-0
lines changed

7 files changed

+369
-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: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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. You can also optionally specify a version for that service,
34+
which defaults to "default."
35+
36+
37+
.. doctest::
38+
39+
>>> from gcloud import error_reporting
40+
>>> client = error_reporting.Client(project='my-project',
41+
... service="login_service",
42+
... version="0.1.0")
43+
44+
Reporting an exception
45+
-----------------------
46+
47+
Report a stacktrace to Stackdriver Error Reporting after an exception
48+
49+
.. doctest::
50+
51+
>>> from gcloud import error_reporting
52+
>>> client = error_reporting.Client()
53+
>>> try:
54+
>>> raise NameError
55+
>>> except Exception:
56+
>>> client.report_error(message="Something went wrong")
57+
58+
59+
By default, the client will report the error using the service specified in the client's
60+
constructor, or the default service of "python". The service can also be manually specified
61+
in the parameters:
62+
63+
.. doctest::
64+
65+
>>> try:
66+
>>> raise NameError
67+
>>> except Exception:
68+
>>> 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: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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+
52+
:type version: str
53+
:param version: Represents the source code version that the developer
54+
provided, which could represent a version label or a Git
55+
SHA-1 hash, for example. If the developer did not provide
56+
a version, the value is set to default.
57+
58+
:raises: :class:`ValueError` if the project is neither passed in nor
59+
set in the environment.
60+
"""
61+
62+
def __init__(self, project=None,
63+
credentials=None,
64+
http=None,
65+
service=None,
66+
version=None):
67+
self.logging_client = gcloud.logging.client.Client(
68+
project, credentials, http)
69+
self.service = service
70+
self.version = version
71+
72+
DEFAULT_SERVICE = 'python'
73+
DEFAULT_VERSION = 'default'
74+
75+
def _get_default_service(self):
76+
"""Returns the service to use on method calls that don't specify an
77+
override.
78+
79+
:rtype: string
80+
:returns: The default service for error reporting calls
81+
"""
82+
if self.service:
83+
return self.service
84+
else:
85+
return self.DEFAULT_SERVICE
86+
87+
def _get_default_version(self):
88+
"""Returns the service to use on method calls that don't specify an
89+
override.
90+
91+
:rtype: string
92+
:returns: The default version for error reporting calls.
93+
"""
94+
if self.version:
95+
return self.version
96+
else:
97+
return self.DEFAULT_VERSION
98+
99+
def report_error(self, message="", service=None, version=None):
100+
""" Reports the details of the latest exceptions to Stackdriver Error
101+
Reporting.
102+
103+
https://cloud.google.com/error-reporting/docs/formatting-error-messages
104+
105+
:type message: str
106+
:param message: An optional message to include with the exception
107+
detail
108+
109+
:type service: str
110+
:param service: An identifier of the service, such as the name of
111+
the executable, job, or Google App Engine module
112+
name. This field is expected to have a low number
113+
of values that are relatively stable over time,
114+
as opposed to version, which can be changed
115+
whenever new code is deployed.
116+
117+
:type version: str
118+
:param version: Represents the source code version that the
119+
developer provided, which could represent a
120+
version label or a Git SHA-1 hash, for example. If
121+
the developer did not provide a version, the value
122+
is set to default.
123+
124+
125+
Example::
126+
127+
>>> try:
128+
>>> raise NameError
129+
>>> except Exception:
130+
>>> client.report_error("Something went wrong!")
131+
"""
132+
if not service:
133+
service = self._get_default_service()
134+
if not version:
135+
version = self._get_default_version()
136+
payload = {
137+
'serviceContext': {
138+
'service': service,
139+
'version': version
140+
},
141+
'message': '{0} : {1}'.format(message, traceback.format_exc())
142+
}
143+
payload['serviceContext']['version'] = version
144+
logger = self.logging_client.logger('errors')
145+
logger.log_struct(payload)
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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+
VERSION = 'myversion'
32+
33+
def test_ctor_default(self):
34+
CREDENTIALS = _Credentials()
35+
target = self._makeOne(project=self.PROJECT,
36+
credentials=CREDENTIALS)
37+
self.assertEquals(target._get_default_service(),
38+
target.DEFAULT_SERVICE)
39+
self.assertEquals(target._get_default_version(),
40+
target.DEFAULT_VERSION)
41+
42+
def test_ctor_params(self):
43+
CREDENTIALS = _Credentials()
44+
target = self._makeOne(project=self.PROJECT,
45+
credentials=CREDENTIALS,
46+
service=self.SERVICE,
47+
version=self.VERSION)
48+
self.assertEquals(target.service, self.SERVICE)
49+
self.assertEquals(target._get_default_service(), self.SERVICE)
50+
self.assertEquals(target._get_default_version(), self.VERSION)
51+
52+
def test_report_error(self):
53+
CREDENTIALS = _Credentials()
54+
target = self._makeOne(project=self.PROJECT,
55+
credentials=CREDENTIALS)
56+
MESSAGE = 'hello world'
57+
58+
logger = _Logger()
59+
target.logging_client.logger = lambda _: logger
60+
61+
try:
62+
raise NameError
63+
except NameError:
64+
target.report_error(MESSAGE)
65+
66+
payload = logger.log_struct_called_with
67+
self.assertEquals(payload['serviceContext'], {
68+
'service': target.DEFAULT_SERVICE,
69+
'version': target.DEFAULT_VERSION
70+
})
71+
self.assertIn(MESSAGE, payload['message'])
72+
self.assertIn('test_report_error', payload['message'])
73+
self.assertIn('test_client.py', payload['message'])
74+
75+
def test_report_error_specify_service(self):
76+
CREDENTIALS = _Credentials()
77+
target = self._makeOne(project=self.PROJECT,
78+
credentials=CREDENTIALS)
79+
MESSAGE = 'hello world'
80+
SERVICE = "notdefault"
81+
VERSION = "notdefaultversion"
82+
83+
logger = _Logger()
84+
target.logging_client.logger = lambda _: logger
85+
86+
try:
87+
raise NameError
88+
except NameError:
89+
target.report_error(MESSAGE, service=SERVICE, version=VERSION)
90+
91+
payload = logger.log_struct_called_with
92+
self.assertEquals(payload['serviceContext'], {
93+
'service': SERVICE,
94+
'version': VERSION
95+
})
96+
self.assertIn(MESSAGE, payload['message'])
97+
self.assertIn('test_report_error', payload['message'])
98+
self.assertIn('test_client.py', payload['message'])
99+
100+
101+
class _Credentials(object):
102+
103+
_scopes = None
104+
105+
@staticmethod
106+
def create_scoped_required():
107+
return True
108+
109+
def create_scoped(self, scope):
110+
self._scopes = scope
111+
return self
112+
113+
114+
class _Logger(object):
115+
116+
def log_struct(self, payload, # pylint: disable=unused-argument
117+
client=None, # pylint: disable=unused-argument
118+
labels=None, # pylint: disable=unused-argument
119+
insert_id=None, # pylint: disable=unused-argument
120+
severity=None, # pylint: disable=unused-argument
121+
http_request=None): # pylint: disable=unused-argument
122+
self.log_struct_called_with = payload

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)