Skip to content

Commit

Permalink
Add Error Reporting GAPIC as dependency (googleapis#2894)
Browse files Browse the repository at this point in the history
* Add Error Reporting GAPIC as dependency

This both adds the GAPIC to be used directly via this package, as well as hooking it up to the existing helpers to provide gRPC support.
  • Loading branch information
waprin authored Feb 14, 2017
1 parent 01dc331 commit cf68403
Show file tree
Hide file tree
Showing 8 changed files with 399 additions and 49 deletions.
74 changes: 74 additions & 0 deletions error_reporting/google/cloud/error_reporting/_gax.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# 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.

"""GAX wrapper for Error Reporting API requests."""

from google.cloud._helpers import make_secure_channel
from google.cloud._http import DEFAULT_USER_AGENT

from google.cloud.gapic.errorreporting.v1beta1 import (
report_errors_service_client)
from google.cloud.grpc.devtools.clouderrorreporting.v1beta1 import (
report_errors_service_pb2)
from google.protobuf.json_format import ParseDict


def make_report_error_api(client):
"""Create an instance of the GAX Logging API.
:type client::class:`google.cloud.error_reporting.Client`
:param client: Error Reporting client.
:rtype: :class:_ErrorReportingGaxApi
:returns: An Error Reporting API instance.
"""
channel = make_secure_channel(
client._connection.credentials,
DEFAULT_USER_AGENT,
report_errors_service_client.ReportErrorsServiceClient.SERVICE_ADDRESS)
gax_client = report_errors_service_client.ReportErrorsServiceClient(
channel=channel)
return _ErrorReportingGaxApi(gax_client, client.project)


class _ErrorReportingGaxApi(object):
"""Helper mapping Error Reporting-related APIs
:type gax_api:
:class:`v1beta1.report_errors_service_client.ReportErrorsServiceClient`
:param gax_api: API object used to make GAX requests.
:type project: str
:param project: Google Cloud Project ID
"""

def __init__(self, gax_api, project):
self._gax_api = gax_api
self._project = project

def report_error_event(self, error_report):
"""Uses the GAX client to report the error.
:type error_report: dict
:param error_report:
payload of the error report formatted according to
https://cloud.google.com/error-reporting/docs/formatting-error-messages
This object should be built using
Use
:meth:~`google.cloud.error_reporting.client._build_error_report`
"""
project_name = self._gax_api.project_path(self._project)
error_report_payload = report_errors_service_pb2.ReportedErrorEvent()
ParseDict(error_report, error_report_payload)
self._gax_api.report_error_event(project_name, error_report_payload)
60 changes: 60 additions & 0 deletions error_reporting/google/cloud/error_reporting/_logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# 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.

"""Interact with Stackdriver Error Reporting via Logging API.
It's possible to report Stackdriver Error Reporting errors by formatting
structured log messages in Stackdriver Logging in a given format. This
client provides a mechanism to report errors using that technique.
"""

import google.cloud.logging.client


class _ErrorReportingLoggingAPI(object):
"""Report to Stackdriver Error Reporting via Logging API
:type project: str
: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.
"""
def __init__(self, project, credentials=None, http=None):
self.logging_client = google.cloud.logging.client.Client(
project, credentials, http)

def report_error_event(self, error_report):
"""Report error payload.
:type error_report: dict
:param: error_report:
dict payload of the error report formatted according to
https://cloud.google.com/error-reporting/docs/formatting-error-messages
This object should be built using
:meth:~`google.cloud.error_reporting.client._build_error_report`
"""
logger = self.logging_client.logger('errors')
logger.log_struct(error_report)
126 changes: 110 additions & 16 deletions error_reporting/google/cloud/error_reporting/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,26 @@
# See the License for the specific language governing permissions and
# limitations under the License.

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

import os
import traceback

import google.cloud.logging.client
try:
from google.cloud.error_reporting._gax import make_report_error_api
_HAVE_GAX = True
except ImportError: # pragma: NO COVER
_HAVE_GAX = False

from google.cloud._helpers import _determine_default_project
from google.cloud.error_reporting._logging import _ErrorReportingLoggingAPI
from google.cloud.environment_vars import DISABLE_GRPC

import six

_DISABLE_GAX = os.getenv(DISABLE_GRPC, False)
_USE_GAX = _HAVE_GAX and not _DISABLE_GAX


class HTTPContext(object):
"""HTTPContext defines an object that captures the parameter for the
Expand Down Expand Up @@ -96,6 +109,12 @@ class Client(object):
SHA-1 hash, for example. If the developer did not provide
a version, the value is set to default.
:type use_gax: bool
:param use_gax: (Optional) Explicitly specifies whether
to use the gRPC transport (via GAX) or HTTP. If unset,
falls back to the ``GOOGLE_CLOUD_DISABLE_GRPC`` environment
variable.
:raises: :class:`ValueError` if the project is neither passed in nor
set in the environment.
"""
Expand All @@ -104,24 +123,56 @@ def __init__(self, project=None,
credentials=None,
http=None,
service=None,
version=None):
self.logging_client = google.cloud.logging.client.Client(
project, credentials, http)
version=None,
use_gax=None):
if project is None:
self._project = _determine_default_project()
else:
self._project = project
self._credentials = credentials
self._http = http

self._report_errors_api = None

self.service = service if service else self.DEFAULT_SERVICE
self.version = version
if use_gax is None:
self._use_gax = _USE_GAX
else:
self._use_gax = use_gax

DEFAULT_SERVICE = 'python'

def _send_error_report(self, message,
report_location=None, http_context=None, user=None):
"""Makes the call to the Error Reporting API via the log stream.
@property
def report_errors_api(self):
"""Helper for logging-related API calls.
This is the lower-level interface to build the payload, generally
users will use either report() or report_exception() to automatically
gather the parameters for this method.
See:
https://cloud.google.com/logging/docs/api/reference/rest/v2/entries
https://cloud.google.com/logging/docs/api/reference/rest/v2/projects.logs
Currently this method sends the Error Report by formatting a structured
log message according to
:rtype:
:class:`_gax._ErrorReportingGaxApi`
or
:class:`._logging._ErrorReportingLoggingAPI`
:returns: A class that implements the report errors API.
"""
if self._report_errors_api is None:
if self._use_gax:
self._report_errors_api = make_report_error_api(self._project)
else:
self._report_errors_api = _ErrorReportingLoggingAPI(
self._project, self._credentials, self._http)
return self._report_errors_api

def _build_error_report(self,
message,
report_location=None,
http_context=None,
user=None):
"""Builds the Error Reporting object to report.
This builds the object according to
https://cloud.google.com/error-reporting/docs/formatting-error-messages
Expand Down Expand Up @@ -151,7 +202,10 @@ def _send_error_report(self, message,
logged in. In this case the Error Reporting system will
use other data, such as remote IP address,
to distinguish affected users.
"""
:rtype: dict
:returns: A dict payload ready to be serialized to JSON and sent to
the API.
"""
payload = {
'serviceContext': {
'service': self.service,
Expand All @@ -178,9 +232,49 @@ def _send_error_report(self, message,

if user:
payload['context']['user'] = user
return payload

def _send_error_report(self,
message,
report_location=None,
http_context=None,
user=None):
"""Makes the call to the Error Reporting API.
This is the lower-level interface to build and send the payload,
generally users will use either report() or report_exception() to
automatically gather the parameters for this method.
logger = self.logging_client.logger('errors')
logger.log_struct(payload)
:type message: str
:param message: The stack trace that was reported or logged by the
service.
:type report_location: dict
:param report_location: The location in the source code where the
decision was made to report the error, usually the place
where it was logged. For a logged exception this would be the
source line where the exception is logged, usually close to
the place where it was caught.
This should be a Python dict that contains the keys 'filePath',
'lineNumber', and 'functionName'
:type http_context: :class`google.cloud.error_reporting.HTTPContext`
:param http_context: The HTTP request which was processed when the
error was triggered.
:type user: str
:param user: The user who caused or was affected by the crash. This can
be a user ID, an email address, or an arbitrary token that
uniquely identifies the user. When sending an error
report, leave this field empty if the user was not
logged in. In this case the Error Reporting system will
use other data, such as remote IP address,
to distinguish affected users.
"""
error_report = self._build_error_report(message, report_location,
http_context, user)
self.report_errors_api.report_error_event(error_report)

def report(self, message, http_context=None, user=None):
""" Reports a message to Stackdriver Error Reporting
Expand Down
1 change: 1 addition & 0 deletions error_reporting/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
REQUIREMENTS = [
'google-cloud-core >= 0.22.1, < 0.23dev',
'google-cloud-logging >= 0.22.0, < 0.23dev',
'gapic-google-cloud-error-reporting-v1beta1 >= 0.14.0, < 0.15dev'
]

setup(
Expand Down
2 changes: 1 addition & 1 deletion error_reporting/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ deps =
pytest
covercmd =
py.test --quiet \
--cov=google.cloud.error-reporting \
--cov=google.cloud.error_reporting \
--cov=unit_tests \
--cov-config {toxinidir}/.coveragerc \
unit_tests
Expand Down
53 changes: 53 additions & 0 deletions error_reporting/unit_tests/test__gax.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Copyright 2017 Google Inc.
#
# 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 unittest

import mock


class Test_make_report_error_api(unittest.TestCase):

def test_make_report_error_api(self):
from google.cloud.error_reporting._gax import make_report_error_api
client = mock.Mock()
client.project = mock.Mock()
report_error_client = make_report_error_api(client)
self.assertEqual(report_error_client._project, client.project)


class Test_ErrorReportingGaxApi(unittest.TestCase):

PROJECT = 'PROJECT'

def _call_fut(self, gax_api, project):
from google.cloud.error_reporting._gax import _ErrorReportingGaxApi
return _ErrorReportingGaxApi(gax_api, project)

def test_constructor(self):
gax_api = mock.Mock()
gax_client_wrapper = self._call_fut(gax_api, self.PROJECT)

self.assertEqual(gax_client_wrapper._project, self.PROJECT)
self.assertEqual(gax_client_wrapper._gax_api, gax_api)

@mock.patch("google.cloud.error_reporting._gax.ParseDict")
def test_report_error_event(self, _):
gax_api = mock.Mock()
gax_client_wrapper = self._call_fut(gax_api, self.PROJECT)

mock_error_report = mock.Mock()
gax_client_wrapper.report_error_event(mock_error_report)
self.assertTrue(gax_api.report_error_event.called_with,
mock_error_report)
Loading

0 comments on commit cf68403

Please sign in to comment.