"""Lambda campaign function module.""" import copy import json import logging from lambdacommon.common_config import logger from sentry_sdk import capture_exception from sentry_sdk import init as sentry_init from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration from sentry_sdk.integrations.logging import LoggingIntegration import config from src import constants from src.connectors.kafka_producer import send_message_to_kaka_topic from src.connectors.snowflake import get_snowflake_connector logging_integration = LoggingIntegration( level=logging.INFO, event_level=logging.CRITICAL ) sentry_init( config.SENTRY_DSN, integrations=[AwsLambdaIntegration(), logging_integration] ) def handler(event, context): """Lambda entry point.""" try: return main_handler(event, context) except Exception as err: logger.error(f'Uncaught exception: {err} Sending it to DLQ topic.') request_id = event.get('requestContext', {}).get('requestId', '') send_message_to_kaka_topic(event, request_id, config.DLQ_TOPIC) def main_handler(event, context): """Lambda entry point.""" if config.ENVIRONMENT != config.PROD_ENVIRONMENT: logger.info(f'event: {event}') if not event.get('headers') or not event.get('body'): logger.warning(constants.INVALID_MESSAGE) return _compose_response( 400, {'status': 'IGNORED', 'message': constants.INVALID_MESSAGE}) try: # Verify json body request_body = json.loads(event.get('body')) except Exception as e: logger.error(e) capture_exception(e) return _compose_response( 400, {'status': 'IGNORED', 'message': constants.INVALID_MESSAGE_BODY}) source = request_body.get('SOURCE') if not source or not isinstance(source, str): logger.warning(constants.MISSING_SOURCE) return _compose_response( 400, {'status': 'IGNORED', 'message': constants.MISSING_SOURCE}) if source not in constants.CAMPAIGN_SOURCES: msg = constants.INVALID_SOURCE.format(source=source) logger.warning(msg) return _compose_response( 400, {'status': 'IGNORED', 'message': msg}) # write to snowflake. # We might have to move get_snowflake_connector to common so that we don't create # new connection for every invocation and cause too many connections. with get_snowflake_connector() as connector: metadata = _prepare_metadata(event) connector.cursor().execute( f'INSERT INTO {config.SNOWFLAKE_TABLE}(RECORD_METADATA, RECORD_CONTENT) ' + 'SELECT PARSE_JSON(%s), PARSE_JSON(%s)', (json.dumps(metadata), json.dumps(request_body)) ) logger.info(constants.SUCCESS_MESSAGE.format( source=source, table=config.SNOWFLAKE_TABLE)) return _compose_response(200, {'status': 'SUCCESS'}) def _prepare_metadata(event): """Prepare metadata from the event.""" metadata = copy.deepcopy(event) del metadata['body'] # Remove the Authorization headers if exist if 'Authorization' in metadata.get('headers', {}): del metadata['headers']['Authorization'] if 'Authorization' in metadata.get('multiValueHeaders', {}): del metadata['multiValueHeaders']['Authorization'] return metadata def _compose_response(status_code, body): """Compose response.""" return { 'isBase64Encoded': False, 'statusCode': status_code, 'body': json.dumps(body) }