Fetching contributors…
Cannot retrieve contributors at this time
178 lines (143 sloc) 6.27 KB
"""Event tracking backend that sends events to Amazon Lambda"""
from __future__ import absolute_import
from datetime import datetime
from datetime import date
import logging
import json
from pytz import UTC
from django.conf import settings
import os
import boto3
from django.contrib.auth.models import User
# Temp: logging to tracker's log
log = logging.getLogger('track.backends.application_log')
class AwsLambdaBackend(object):
Send events to Amazon Lambda, where it can be routed to other AWS resources or 3rd party applications.
Requires all emitted events to have the following structure (at a minimum):
'name': '',
'context': {
'user_id': 10,
'email': '"
def __init__(self):
Connect to Lambda
self.lambda_arn = settings.AWS_EVENT_TRACKER_ARN
access_key = settings.AWS_ACCESS_KEY_ID
secret_key = settings.AWS_SECRET_ACCESS_KEY
aws_region = getattr(settings, "AWS_EVENT_TRACKER_REGION", "us-west-2")
self.client = boto3.client('lambda',
def send(self, event):
Use the boto3 to send async events to AWS Lambda
# Lookup user's email and set in context.
# We're only do this b/c we whitelisted only a few events, so this
# db operation won't happen on *every* event. Ideally, email should arrive here
# already with email set, but that's not the case at the moment...
if not event:
log.warning("AWSLambdaService: No 'event' argument was provided. Not sending to AWSLambda.")
return None
event_name = event.get('name')
if not event_name:
log.warning('AWSLambdaService: Event was missing name property. Not sending to AWSLambda.', event)
return None"AWSLambdaService: aws lambda call for event name {} ".format(event_name))
context = event.get('context')
if not context:
log.warning("AWSLambdaService: Event was missing context. Not sending to AWSLambda.", event)
return None
user_id = context.get('user_id')
# Some events include user_id in context, and some in the actual event body, so
# check both places
if not user_id:
user_id = event.get('user_id')
if not user_id:
log.warning("AWSLambdaService: event {} no user_id in context or event body. Not sending to AWSLambda.".format(event_name))
return None
user = User.objects.get(pk=user_id)
except User.DoesNotExist:
log.error("Cannot find a user with user_id: {} . Not sending to AWSLambda.".format(user_id))
return None
# Make sure user's email is included, since we use that
# to uniquely identify student in automated email system
context['email'] =
# User in can be different from user in context
# (e.g. instructor user uses dashboard to enroll student user)
# So find and set email and username in the data object if user_id is different
# Our databroker needs email and username set in the 'data' object.
data = event.get('data')
if not data:
log.warning("AWSLambdaService: event {} no data object in event body. Not sending to AWSLambda.".format(
return None
# if user_id doesn't appear in data, let's assume the event
# applies to the user in event.context
data_user_id = data.get('user_id')
if not data_user_id:
data_user_id = user_id
if data_user_id == user_id:
data['email'] =
data['username'] = user.username
# this is a different user, must look up their email separately
data_user = User.objects.get(pk=data_user_id)
data['email'] =
data['username'] = data_user.username
except User.DoesNotExist:
log.error("Cannot find a user in with user_id: {} . Not sending to AWSLambda.".format(data_user_id))
return None
# Encode event info
event_str = json.dumps(event, cls=DateTimeJSONEncoder)
# Send event to the target AWS Lambda function
# Use 'Event' for Invocation type so that the call is async (?)
# Note that boto3 call should return a response as a dictionary like:
# {
# 'StatusCode': 123,
# 'FunctionError': 'string',
# 'LogResult': 'string',
# 'Payload': StreamingBody()
# }
payload = event_str.encode('utf-8')
log.exception("Couldn't encode event_str. event_str=".format(event_str))
response = self.client.invoke(
# TODO: Do we want to log error response codes?"AWSLambdaService: aws lambda send event: {} ".format(event_name))
class DateTimeJSONEncoder(json.JSONEncoder):
"""JSON encoder aware of datetime.datetime and objects"""
def default(self, obj):
Serialize datetime and date objects of iso format.
datatime objects are converted to UTC.
if isinstance(obj, datetime):
if obj.tzinfo is None:
# Localize to UTC naive datetime objects
obj = UTC.localize(obj) # pylint: disable=no-value-for-parameter
# Convert to UTC datetime objects from other timezones
obj = obj.astimezone(UTC)
return obj.isoformat()
elif isinstance(obj, date):
return obj.isoformat()
return super(DateTimeJSONEncoder, self).default(obj)