Skip to content

Commit 286c39a

Browse files
author
Bill Prin
committed
Add Error Reporting Client
1 parent 473de8e commit 286c39a

File tree

7 files changed

+542
-0
lines changed

7 files changed

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

0 commit comments

Comments
 (0)