Skip to content

Commit 26984b2

Browse files
author
Bill Prin
committed
Add Error Reporting Client
1 parent 473de8e commit 26984b2

File tree

7 files changed

+537
-0
lines changed

7 files changed

+537
-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: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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.error_reporting.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 service, 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_exception()
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".
61+
62+
The user and HTTP context can also be included in the exception. The HTTP context
63+
can be constructed using :class:`gcloud.error_reporting.HTTPContext`. This will
64+
be used by Stackdriver Error Reporting to help group exceptions.
65+
66+
.. doctest::
67+
68+
>>> from gcloud import error_reporting
69+
>>> client = error_reporting.Client()
70+
>>> user = '[email protected]'
71+
>>> http_context = HTTPContext(method='GET', url='/', userAgent='test agent',
72+
... referrer='example.com', responseStatusCode=500,
73+
... remote_ip='1.2.3.4')
74+
>>> try:
75+
>>> raise NameError
76+
>>> except Exception:
77+
>>> client.report_exception(http_context=http_context, user=user))
78+
79+
Reporting an error without an exception
80+
-----------------------------------------
81+
82+
Errors can also be reported to Stackdriver Error Reporting outside the context of an exception.
83+
The library will include the file path, function name, and line number of the location where the
84+
error was reported.
85+
86+
.. doctest::
87+
88+
>>> from gcloud import error_reporting
89+
>>> client = error_reporting.Client()
90+
>>> error_reporting.report("Found an error!")
91+
92+
Similarly to reporting an exception, the user and HTTP context can be provided:
93+
94+
.. doctest::
95+
96+
>>> from gcloud import error_reporting
97+
>>> client = error_reporting.Client()
98+
>>> user = '[email protected]'
99+
>>> http_context = HTTPContext(method='GET', url='/', userAgent='test agent',
100+
... referrer='example.com', responseStatusCode=500,
101+
... remote_ip='1.2.3.4')
102+
>>> error_reporting.report("Found an error!", http_context=http_context, user=user))

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: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
"""Client library for Stackdriver Error Reporting"""
16+
17+
from gcloud.error_reporting.client import Client

gcloud/error_reporting/client.py

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
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+
"""Client for interacting with the Stackdriver Logging API"""
16+
17+
import traceback
18+
19+
import gcloud.logging.client
20+
21+
22+
class HTTPContext(object):
23+
"""HTTPContext defines an object that captures the parameter for the
24+
httpRequest part of Error Reporting API
25+
26+
:type method: string
27+
:param method: The type of HTTP request, such as GET, POST, etc.
28+
29+
:type url: string
30+
:param url: The URL of the request
31+
32+
:type user_agent: string
33+
:param user_agent: The user agent information that is provided with the
34+
request.
35+
36+
:type referrer: string
37+
:param referrer: The referrer information that is provided with the
38+
request.
39+
40+
:type response_status_code: int
41+
:param response_status_code: The HTTP response status code for the request.
42+
43+
:type remote_ip: string
44+
:param remote_ip: The IP address from which the request originated. This
45+
can be IPv4, IPv6, or a token which is derived from
46+
the IP address, depending on the data that has been
47+
provided in the error report.
48+
"""
49+
50+
def __init__(self, method=None, url=None,
51+
user_agent=None, referrer=None,
52+
response_status_code=None, remote_ip=None):
53+
self.method = method
54+
self.url = url
55+
# intentionally camel case for mapping to JSON API expects
56+
# pylint: disable=invalid-name
57+
self.userAgent = user_agent
58+
self.referrer = referrer
59+
self.responseStatusCode = response_status_code
60+
self.remoteIp = remote_ip
61+
62+
63+
class Client(object):
64+
"""Error Reporting client. Currently Error Reporting is done by creating
65+
a Logging client.
66+
67+
:type project: string
68+
:param project: the project which the client acts on behalf of. If not
69+
passed falls back to the default inferred from the
70+
environment.
71+
72+
:type credentials: :class:`oauth2client.client.OAuth2Credentials` or
73+
:class:`NoneType`
74+
:param credentials: The OAuth2 Credentials to use for the connection
75+
owned by this client. If not passed (and if no ``http``
76+
object is passed), falls back to the default inferred
77+
from the environment.
78+
79+
:type http: :class:`httplib2.Http` or class that defines ``request()``.
80+
:param http: An optional HTTP object to make requests. If not passed, an
81+
``http`` object is created that is bound to the
82+
``credentials`` for the current object.
83+
84+
:type service: str
85+
:param service: An identifier of the service, such as the name of the
86+
executable, job, or Google App Engine service name. This
87+
field is expected to have a low number of values that are
88+
relatively stable over time, as opposed to version,
89+
which can be changed whenever new code is deployed.
90+
91+
92+
:type version: str
93+
:param version: Represents the source code version that the developer
94+
provided, which could represent a version label or a Git
95+
SHA-1 hash, for example. If the developer did not provide
96+
a version, the value is set to default.
97+
98+
:raises: :class:`ValueError` if the project is neither passed in nor
99+
set in the environment.
100+
"""
101+
102+
def __init__(self, project=None,
103+
credentials=None,
104+
http=None,
105+
service=None,
106+
version=None):
107+
self.logging_client = gcloud.logging.client.Client(
108+
project, credentials, http)
109+
self.service = service if service else self.DEFAULT_SERVICE
110+
self.version = version
111+
112+
DEFAULT_SERVICE = 'python'
113+
114+
def _send_error_report(self, message,
115+
report_location=None, http_context=None, user=None):
116+
"""Makes the call to the Error Reporting API via the log stream.
117+
118+
This is the lower-level interface to build the payload, generally
119+
users will use either report() or report_exception() to automatically
120+
gather the parameters for this method.
121+
122+
Currently this method sends the Error Report by formatting a structured
123+
log message according to
124+
125+
https://cloud.google.com/error-reporting/docs/formatting-error-messages
126+
127+
:type message: string
128+
:param message: The stack trace that was reported or logged by the
129+
service.
130+
131+
:type report_location: dict
132+
:param report_location: The location in the source code where the
133+
decision was made to report the error, usually the place
134+
where it was logged. For a logged exception this would be the
135+
source line where the exception is logged, usually close to
136+
the place where it was caught.
137+
138+
This should be a Python dict that contains the keys 'filePath',
139+
'lineNumber', and 'functionName'
140+
141+
:type http_context: :class`gcloud.error_reporting.HTTPContext`
142+
:param http_context: The HTTP request which was processed when the
143+
error was triggered.
144+
145+
:type user: string
146+
:param user: The user who caused or was affected by the crash. This can
147+
be a user ID, an email address, or an arbitrary token that
148+
uniquely identifies the user. When sending an error
149+
report, leave this field empty if the user was not
150+
logged in. In this case the Error Reporting system will
151+
use other data, such as remote IP address,
152+
to distinguish affected users.
153+
"""
154+
payload = {
155+
'serviceContext': {
156+
'service': self.service,
157+
},
158+
'message': '{0}'.format(message)
159+
}
160+
161+
if self.version:
162+
payload['serviceContext']['version'] = self.version
163+
164+
if report_location or http_context or user:
165+
payload['context'] = {}
166+
167+
if report_location:
168+
payload['context']['reportLocation'] = report_location
169+
170+
if http_context:
171+
http_context_dict = http_context.__dict__
172+
# strip out None values
173+
# once py26 support is dropped this can use dict comprehension
174+
payload['context']['httpContext'] = dict(
175+
(k, v) for (k, v) in http_context_dict.iteritems()
176+
if v is not None
177+
)
178+
179+
if user:
180+
payload['context']['user'] = user
181+
182+
logger = self.logging_client.logger('errors')
183+
logger.log_struct(payload)
184+
185+
def report(self, message, http_context=None, user=None):
186+
""" Reports a message to Stackdriver Error Reporting
187+
https://cloud.google.com/error-reporting/docs/formatting-error-messages
188+
189+
:type message: str
190+
:param message: A user-supplied message to report
191+
192+
193+
:type http_context: :class`gcloud.error_reporting.HTTPContext`
194+
:param http_context: The HTTP request which was processed when the
195+
error was triggered.
196+
197+
:type user: string
198+
:param user: The user who caused or was affected by the crash. This
199+
can be a user ID, an email address, or an arbitrary
200+
token that uniquely identifies the user. When sending
201+
an error report, leave this field empty if the user
202+
was not logged in. In this case the Error Reporting
203+
system will use other data, such as remote IP address,
204+
to distinguish affected users.
205+
206+
Example::
207+
>>> client.report("Something went wrong!")
208+
"""
209+
stack = traceback.extract_stack()
210+
last_call = stack[-2]
211+
file_path = last_call[0]
212+
line_number = last_call[1]
213+
function_name = last_call[2]
214+
report_location = {
215+
'filePath': file_path,
216+
'lineNumber': line_number,
217+
'functionName': function_name
218+
}
219+
220+
self._send_error_report(message,
221+
http_context=http_context,
222+
user=user,
223+
report_location=report_location)
224+
225+
def report_exception(self, http_context=None, user=None):
226+
""" Reports the details of the latest exceptions to Stackdriver Error
227+
Reporting.
228+
229+
:type http_context: :class`gcloud.error_reporting.HTTPContext`
230+
:param http_context: The HTTP request which was processed when the
231+
error was triggered.
232+
233+
:type user: string
234+
:param user: The user who caused or was affected by the crash. This
235+
can be a user ID, an email address, or an arbitrary
236+
token that uniquely identifies the user. When sending an
237+
error report, leave this field empty if the user was
238+
not logged in. In this case the Error Reporting system
239+
will use other data, such as remote IP address,
240+
to distinguish affected users.
241+
242+
Example::
243+
244+
>>> try:
245+
>>> raise NameError
246+
>>> except Exception:
247+
>>> client.report_exception()
248+
"""
249+
self._send_error_report(traceback.format_exc(),
250+
http_context=http_context,
251+
user=user)

0 commit comments

Comments
 (0)