diff options
author | Ashley Camba <ashwoods@gmail.com> | 2017-10-13 11:56:10 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-10-13 11:56:10 +0200 |
commit | e1fd30be2af23f049f6ccdcdb81061e7359f99dc (patch) | |
tree | e26d860e9e73d29954a9f14a5bcd7826e71ffc4a /raven/contrib | |
parent | 608d34ebb811dbaa8f013936b6f0579201a57301 (diff) | |
download | raven-e1fd30be2af23f049f6ccdcdb81061e7359f99dc.tar.gz |
AWS Lambda Integration (#1107)
* Initial lambda integration
* feature(lambda): Add lambda tests and client
* Add D107 to ignore flake8 docs
* Initial lambda integration
* feature(lambda): Add lambda tests and client
* Add reference to raven-python-lambda
Diffstat (limited to 'raven/contrib')
-rw-r--r-- | raven/contrib/awslambda/__init__.py | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/raven/contrib/awslambda/__init__.py b/raven/contrib/awslambda/__init__.py new file mode 100644 index 0000000..7d6f2b8 --- /dev/null +++ b/raven/contrib/awslambda/__init__.py @@ -0,0 +1,173 @@ +""" +raven.contrib.awslambda +~~~~~~~~~~~~~~~~~~~~ + +Raven wrapper for AWS Lambda handlers. + +:copyright: (c) 2010-2012 by the Sentry Team, see AUTHORS for more details. +:license: BSD, see LICENSE for more details. +""" +# flake8: noqa + +from __future__ import absolute_import + +import os +import logging +import functools +from types import FunctionType + +from raven.base import Client +from raven.transport.http import HTTPTransport + +logger = logging.getLogger('sentry.errors.client') + + +def get_default_tags(): + return { + 'lambda': 'AWS_LAMBDA_FUNCTION_NAME', + 'version': 'AWS_LAMBDA_FUNCTION_VERSION', + 'memory_size': 'AWS_LAMBDA_FUNCTION_MEMORY_SIZE', + 'log_group': 'AWS_LAMBDA_LOG_GROUP_NAME', + 'log_stream': 'AWS_LAMBDA_LOG_STREAM_NAME', + 'region': 'AWS_REGION' + } + + +class LambdaClient(Client): + """ + Raven decorator for AWS Lambda. + + By default, the lambda integration will capture unhandled exceptions and instrument logging. + + Usage: + + >>> from raven.contrib.awslambda import LambdaClient + >>> + >>> + >>> client = LambdaClient() + >>> + >>> @client.capture_exceptions + >>> def handler(event, context): + >>> ... + >>> raise Exception('I will be sent to sentry!') + + """ + + def __init__(self, *args, **kwargs): + transport = kwargs.get('transport', HTTPTransport) + super(LambdaClient, self).__init__(*args, transport=transport, **kwargs) + + def capture(self, *args, **kwargs): + if 'data' not in kwargs: + kwargs['data'] = data = {} + else: + data = kwargs['data'] + event = kwargs.get('event', None) + context = kwargs.get('context', None) + user_info = self._get_user_interface(event) + if user_info: + data.update(user_info) + if event: + http_info = self._get_http_interface(event) + if http_info: + data.update(http_info) + data['extra'] = self._get_extra_data(event, context) + return super(LambdaClient, self).capture(*args, **kwargs) + + def build_msg(self, *args, **kwargs): + + data = super(LambdaClient, self).build_msg(*args, **kwargs) + for option, default in get_default_tags().items(): + data['tags'].setdefault(option, os.environ.get(default)) + data.setdefault('release', os.environ.get('SENTRY_RELEASE')) + data.setdefault('environment', os.environ.get('SENTRY_ENVIRONMENT')) + return data + + def capture_exceptions(self, f=None, exceptions=None): # TODO: Ash fix kwargs in base + """ + Wrap a function or code block in try/except and automatically call + ``.captureException`` if it raises an exception, then the exception + is reraised. + + By default, it will capture ``Exception`` + + >>> @client.capture_exceptions + >>> def foo(): + >>> raise Exception() + + >>> with client.capture_exceptions(): + >>> raise Exception() + + You can also specify exceptions to be caught specifically + + >>> @client.capture_exceptions((IOError, LookupError)) + >>> def bar(): + >>> ... + + ``kwargs`` are passed through to ``.captureException``. + """ + if not isinstance(f, FunctionType): + # when the decorator has args which is not a function we except + # f to be the exceptions tuple + return functools.partial(self.capture_exceptions, exceptions=f) + + exceptions = exceptions or (Exception,) + + @functools.wraps(f) + def wrapped(event, context, *args, **kwargs): + try: + return f(event, context, *args, **kwargs) + except exceptions: + self.captureException(event=event, context=context, **kwargs) + self.context.clear() + raise + return wrapped + + @staticmethod + def _get_user_interface(event): + if event.get('requestContext'): + identity = event['requestContext']['identity'] + if identity: + user = { + 'id': identity.get('cognitoIdentityId', None) or identity.get('user', None), + 'username': identity.get('user', None), + 'ip_address': identity.get('sourceIp', None), + 'cognito_identity_pool_id': identity.get('cognitoIdentityPoolId', None), + 'cognito_authentication_type': identity.get('cognitoAuthenticationType', None), + 'user_agent': identity.get('userAgent') + } + return {'user': user} + + @staticmethod + def _get_http_interface(event): + if event.get('path') and event.get('httpMethod'): + request = { + "url": event.get('path'), + "method": event.get('httpMethod'), + "query_string": event.get('queryStringParameters', None), + "headers": event.get('headers', None) or [], + } + return {'request': request} + + @staticmethod + def _get_extra_data(event, context): + extra_context = { + 'event': event, + 'aws_request_id': context.aws_request_id, + 'context': vars(context), + } + + if context.client_context: + extra_context['client_context'] = { + 'client.installation_id': context.client_context.client.installation_id, + 'client.app_title': context.client_context.client.app_title, + 'client.app_version_name': context.client_context.client.app_version_name, + 'client.app_version_code': context.client_context.client.app_version_code, + 'client.app_package_name': context.client_context.client.app_package_name, + 'custom': context.client_context.custom, + 'env': context.client_context.env, + } + return extra_context + + + |