Skip to content

Commit b3eba02

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

File tree

7 files changed

+389
-0
lines changed

7 files changed

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

0 commit comments

Comments
 (0)