Skip to content

Commit

Permalink
Add sentry events (#13)
Browse files Browse the repository at this point in the history
* chalice-events-sentry

* format

* add-data-key

* clear-breadcrumbs

* add-old-if

* add-test

* add-info-lambda

* format

* add-more-test

* bump version

Co-authored-by: Matin Tamizi <[email protected]>
  • Loading branch information
Gleekzone and matin authored Aug 17, 2020
1 parent fd45fda commit e29a592
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 65 deletions.
99 changes: 41 additions & 58 deletions sentry_chalice/sentry_chalice.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import sys
import traceback
from datetime import datetime, timedelta
from os import environ
from datetime import datetime

import chalice
from chalice import Chalice, ChaliceViewError, Response
from chalice.app import EventSourceHandler as ChaliceEventSourceHandler
from sentry_sdk._compat import reraise
from sentry_sdk._types import MYPY
from sentry_sdk.hub import Hub, _should_send_default_pii
from sentry_sdk.integrations import Integration
from sentry_sdk.integrations._wsgi_common import _filter_headers
from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration
from sentry_sdk.integrations.aws_lambda import _get_cloudwatch_logs_url
from sentry_sdk.utils import (
AnnotatedValue,
capture_internal_exceptions,
Expand All @@ -21,6 +23,30 @@
from sentry_sdk._types import Event, EventProcessor, Hint


class EventSourceHandler(ChaliceEventSourceHandler):
def __call__(self, event, context):
hub = Hub.current
client = hub.client

with hub.push_scope() as scope:
try:
event_obj = self.event_class(event, context)
return self.func(event_obj)
except Exception:
scope.add_event_processor(
_make_request_event_processor(event, context)
)
exc_info = sys.exc_info()
event, hint = event_from_exception(
exc_info,
client_options=client.options,
mechanism={"type": "chalice", "handled": False},
)
hub.capture_event(event, hint=hint)
hub.flush()
reraise(*exc_info)


def _get_view_function_response(app, view_function, function_args):
hub = Hub.current
client = hub.client
Expand Down Expand Up @@ -83,7 +109,7 @@ def setup_once():
# for @app.route()
Chalice._get_view_function_response = _get_view_function_response
# for everything else (like events)
AwsLambdaIntegration.setup_once()
chalice.app.EventSourceHandler = EventSourceHandler


def _make_request_event_processor(current_request, lambda_context):
Expand All @@ -92,36 +118,32 @@ def _make_request_event_processor(current_request, lambda_context):

def event_processor(event, hint, start_time=start_time):
# type: (Event, Hint, datetime) -> Optional[Event]

extra = event.setdefault("extra", {})
extra["lambda"] = {

extra["Chalice-lambda"] = {
"function_name": lambda_context.function_name,
"function_version": lambda_context.function_version,
"invoked_function_arn": lambda_context.invoked_function_arn,
"Lambda ARN": lambda_context.invoked_function_arn,
"aws_request_id": lambda_context.aws_request_id,
}

extra["cloudwatch logs"] = {
extra["cloudwatch info"] = {
"url": _get_cloudwatch_logs_url(lambda_context, start_time),
"log_group": lambda_context.log_group_name,
"log_stream": lambda_context.log_stream_name,
}

request = event.get("request", {})
if "httpMethod" in current_request.context:
request["method"] = current_request.context["httpMethod"]
request_info = event.get("request", {})

request["url"] = _get_url(current_request, lambda_context)
request_info["method"] = current_request.context["httpMethod"]

if "query_params" in current_request.to_dict():
request["query_string"] = current_request.query_params
request_info["query_string"] = current_request.query_params

if "headers" in current_request.to_dict():
request["headers"] = _filter_headers(current_request.headers)
request_info["headers"] = _filter_headers(current_request.headers)

if current_request._body is None:
# Unfortunately couldn't find a way to get structured body from AWS
# event. Meaning every body is unstructured to us.
request["data"] = AnnotatedValue(
request_info["data"] = AnnotatedValue(
"", {"rem": [["!raw", "x", 0, 0]]}
)

Expand All @@ -136,47 +158,8 @@ def event_processor(event, hint, start_time=start_time):
if ip is not None:
user_info.setdefault("ip_address", ip)

event["request"] = request
event["request"] = request_info

return event

return event_processor


def _get_url(event, context):
# type: (Any, Any) -> str
path = event.context["path"]
headers = event.headers
host = headers.get("Host", None)
proto = headers.get("X-Forwarded-Proto", None)
if proto and host and path:
return "{}://{}{}".format(proto, host, path)
return "awslambda:///{}".format(context.function_name)


def _get_cloudwatch_logs_url(context, start_time):
# type: (Any, datetime) -> str
"""
Generates a CloudWatchLogs console URL based on the context object
Arguments:
context {Any} -- context from lambda handler
Returns:
str -- AWS Console URL to logs.
"""
formatstring = "%Y-%m-%dT%H:%M:%S"

url = (
"https://console.aws.amazon.com/cloudwatch/home?region={region}"
"#logEventViewer:group={log_group};stream={log_stream}"
";start={start_time};end={end_time}"
).format(
region=environ.get("AWS_REGION"),
log_group=context.log_group_name,
log_stream=context.log_stream_name,
start_time=(start_time - timedelta(seconds=1)).strftime(formatstring),
end_time=(datetime.now() + timedelta(seconds=2)).strftime(
formatstring
),
)

return url
2 changes: 1 addition & 1 deletion sentry_chalice/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.2.1'
__version__ = '0.3.0'
6 changes: 5 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest
import sentry_sdk
from chalice import Chalice
from chalice import BadRequestError, Chalice

from sentry_chalice import ChaliceIntegration

Expand All @@ -20,6 +20,10 @@ def boom():
def has_request():
raise Exception('boom goes the dynamite!')

@app.route('/badrequest')
def badrequest():
raise BadRequestError('bad-request')

return app


Expand Down
47 changes: 42 additions & 5 deletions tests/test_sentry_chalice.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pytest
from chalice.test import Client


Expand All @@ -20,8 +21,44 @@ def test_has_request(app, capture_events):
response = client.http.get('/context')
assert response.status_code == 500

(event,) = events
assert event["transaction"] == "api_handler"
assert "data" not in event["request"]
assert event["request"]["url"] == "awslambda:///api_handler"
assert event["request"]["headers"] == {}
(event,) = events
assert event["transaction"] == "api_handler"
assert "data" not in event["request"]
assert event["request"]["headers"] == {}


def test_scheduled_event(app):
@app.schedule('rate(1 minutes)')
def every_hour(event):
raise Exception('only chalice event!')

lambda_event = {
"version": "0",
"account": "123456789012",
"region": "us-west-2",
"detail": {},
"detail-type": "Scheduled Event",
"source": "aws.events",
"time": "1970-01-01T00:00:00Z",
"id": "event-id",
"resources": [
"arn:aws:events:us-west-2:123456789012:rule/my-schedule"
],
}
with pytest.raises(Exception) as exc_info:
every_hour(lambda_event, context=None)
assert str(exc_info.value) == 'only chalice event!'


def test_bad_reques(app) -> None:
with Client(app) as client:

response = client.http.get('/badrequest')

assert response.status_code == 400
assert response.json_body == dict(
[
('Code', 'BadRequestError'),
('Message', 'BadRequestError: bad-request'),
]
)

0 comments on commit e29a592

Please sign in to comment.