Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Error Reporting GAPIC as dependency #2894

Merged
merged 14 commits into from
Feb 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 (

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

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:

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

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