Skip to content

Commit

Permalink
Add Error Reporting Client
Browse files Browse the repository at this point in the history
  • Loading branch information
Bill Prin committed Jun 30, 2016
1 parent d958f74 commit 1f59346
Show file tree
Hide file tree
Showing 7 changed files with 322 additions and 0 deletions.
7 changes: 7 additions & 0 deletions docs/error-reporting-client.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Error Reporting Client
=======================

.. automodule:: gcloud.error_reporting.client
:members:
:show-inheritance:

65 changes: 65 additions & 0 deletions docs/error-reporting-usage.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
Using the API
=============


Authentication and Configuration
--------------------------------

- For an overview of authentication in ``gcloud-python``,
see :doc:`gcloud-auth`.

- In addition to any authentication configuration, you should also set the
:envvar:`GCLOUD_PROJECT` environment variable for the project you'd like
to interact with. If you are Google App Engine or Google Compute Engine
this will be detected automatically.

- After configuring your environment, create a
:class:`Client <gcloud.logging.client.Client>`

.. doctest::

>>> from gcloud import error_reporting
>>> client = error_reporting.Client()

or pass in ``credentials`` and ``project`` explicitly

.. doctest::

>>> from gcloud import error_reporting
>>> client = error_reporting.Client(project='my-project', credentials=creds)

Error Reporting associates errors with a service, which is an identifier for an executable,
App Engine module, or job. The default service is "python", but a default can be specified
for the client on construction time.

.. doctest::

>>> from gcloud import error_reporting
>>> client = error_reporting.Client(project='my-project', service="login_service")


Reporting an exception
-----------------------

Report a stacktrace to Stackdriver Error Reporting after an exception

.. doctest::

>>> from gcloud import error_reporting
>>> client = error_reporting.Client()
>>> try:
>>> raise NameError
>>> except Exception:
>>> client.report_error(message="Something went wrong")


By default, the client will report the error using the service specified in the client's
constructor, or the default service of "python". The service can also be manually specified
in the parameters:

.. doctest::

>>> try:
>>> raise NameError
>>> except Exception:
>>> client.report_error(message="Something went wrong", service="login_service")
8 changes: 8 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@
logging-metric
logging-sink

.. toctree::
:maxdepth: 0
:hidden:
:caption: Stackdriver Error Reporting

error-reporting-usage
Client <error-reporting-client>

.. toctree::
:maxdepth: 0
:hidden:
Expand Down
18 changes: 18 additions & 0 deletions gcloud/error_reporting/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env python
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Client library for Stackdriver Error Reporting"""

from gcloud.error_reporting.client import Client
109 changes: 109 additions & 0 deletions gcloud/error_reporting/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env python
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Client for interacting with the Stackdriver Logging API"""

import traceback

import gcloud.logging.client


class Client(object):
"""Error Reporting client. Currently Error Reporting is done by creating
a Logging client.
:type project: string
:param project: the project which the client acts on behalf of. If not
passed falls back to the default inferred from the
environment.
:type credentials: :class:`oauth2client.client.OAuth2Credentials` or
:class:`NoneType`
:param credentials: The OAuth2 Credentials to use for the connection
owned by this client. If not passed (and if no ``http``
object is passed), falls back to the default inferred
from the environment.
:type http: :class:`httplib2.Http` or class that defines ``request()``.
:param http: An optional HTTP object to make requests. If not passed, an
``http`` object is created that is bound to the
``credentials`` for the current object.
:type service: str
:param service: An identifier of the service, such as the name of the
executable, job, or Google App Engine module name. This
field is expected to have a low number of values that are
relatively stable over time, as opposed to version,
which can be changed whenever new code is deployed.
:raises: :class:`ValueError` if the project is neither passed in nor
set in the environment.
"""

def __init__(self, project=None,
credentials=None,
http=None,
service=None):
self.logging_client = gcloud.logging.client.Client(
project, credentials, http)
self.service = service

DEFAULT_SERVICE = 'python'

def _get_default_service(self):
"""Returns the service to use on method calls that don't specify an
override.
:rtype: string
:returns: The default service for error reporting calls
"""
if self.service:
return self.service
else:
return self.DEFAULT_SERVICE

def report_error(self, message="", service=None):
""" Reports the details of the latest exceptions to Stackdriver Error
Reporting.
https://cloud.google.com/error-reporting/docs/formatting-error-messages
:type message: str
:param message: An optional message to include with the exception
detail
:type service: str
:param service: An identifier of the service, such as the name of
the executable, job, or Google App Engine module
name. This field is expected to have a low number
of values that are relatively stable over time,
as opposed to version, which can be changed
whenever new code is deployed.
Example::
>>> try:
>>> raise NameError
>>> except Exception:
>>> client.report_error("Something went wrong!")
"""
if not service:
service = self._get_default_service()
payload = {
'serviceContext': {'service': service},
'message': '{0} : {1}'.format(message, traceback.format_exc())
}
logger = self.logging_client.logger('errors')
logger.log_struct(payload)
114 changes: 114 additions & 0 deletions gcloud/error_reporting/test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env python
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import unittest2


class TestClient(unittest2.TestCase):

def _getTargetClass(self):
from gcloud.error_reporting.client import Client
return Client

def _makeOne(self, *args, **kw):
return self._getTargetClass()(*args, **kw)

PROJECT = 'PROJECT'
SERVICE = 'SERVICE'

def test_ctor(self):
CREDENTIALS = _Credentials()
target = self._makeOne(project=self.PROJECT,
credentials=CREDENTIALS)
self.assertEquals(target._get_default_service(),
target.DEFAULT_SERVICE)

def test_ctor_service(self):
CREDENTIALS = _Credentials()
target = self._makeOne(project=self.PROJECT,
credentials=CREDENTIALS,
service=self.SERVICE)
self.assertEquals(target.service, self.SERVICE)
self.assertEquals(target._get_default_service(), self.SERVICE)

def test_report_error(self):
CREDENTIALS = _Credentials()
target = self._makeOne(project=self.PROJECT,
credentials=CREDENTIALS)
MESSAGE = 'hello world'

logger = _Logger()
target.logging_client.logger = lambda _: logger

try:
raise NameError
except NameError:
target.report_error(MESSAGE)

payload = logger.log_struct_called_with
self.assertEquals(payload['serviceContext'], {
'service': target.DEFAULT_SERVICE
})
self.assertIn(MESSAGE, payload['message'])
self.assertIn('test_report_error', payload['message'])
self.assertIn('test_client.py', payload['message'])

def test_report_error_specify_service(self):
CREDENTIALS = _Credentials()
target = self._makeOne(project=self.PROJECT,
credentials=CREDENTIALS)
MESSAGE = 'hello world'
SERVICE = "notdefault"

logger = _Logger()
target.logging_client.logger = lambda _: logger

try:
raise NameError
except NameError:
target.report_error(MESSAGE, service=SERVICE)

payload = logger.log_struct_called_with
self.assertEquals(payload['serviceContext'], {
'service': SERVICE
})
self.assertIn(MESSAGE, payload['message'])
self.assertIn('test_report_error', payload['message'])
self.assertIn('test_client.py', payload['message'])


class _Credentials(object):

_scopes = None

@staticmethod
def create_scoped_required():
return True

def create_scoped(self, scope):
self._scopes = scope
return self


class _Logger(object):

def log_struct(self, payload, # pylint: disable=unused-argument
client=None, # pylint: disable=unused-argument
labels=None, # pylint: disable=unused-argument
insert_id=None, # pylint: disable=unused-argument
severity=None, # pylint: disable=unused-argument
http_request=None): # pylint: disable=unused-argument
self.log_struct_called_with = payload
1 change: 1 addition & 0 deletions scripts/verify_included_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
'gcloud.bigtable.__init__',
'gcloud.datastore.__init__',
'gcloud.dns.__init__',
'gcloud.error_reporting.__init__',
'gcloud.iterator',
'gcloud.logging.__init__',
'gcloud.monitoring.__init__',
Expand Down

0 comments on commit 1f59346

Please sign in to comment.