Exemple #1
0
    def __init__(self, event, context):
        """

        :param dict event:
        :param context:
        """

        # Make Boto3 not be so noisy
        logging.getLogger('boto3').setLevel(logging.ERROR)
        logging.getLogger('botocore').setLevel(logging.ERROR)

        # Make USFM-tools not be so noisy
        logging.getLogger('usfm_tools').setLevel(logging.WARNING)

        # Set up logger
        self.logger = logging.getLogger()  # type: logging._loggerClass
        self.logger.setLevel(logging.DEBUG)
        self.logger.addHandler(logging.StreamHandler())

        self.event = event
        self.context = context

        # get stage name
        if event and 'context' in event and 'stage' in event['context']:
            self.aws_stage = event['context']['stage']
        elif event and 'stage' in event:
            # TRICKY: the stage must be manually given for cloudwatch events
            self.aws_stage = event['stage']
        else:
            self.logger.warning('AWS Stage is not specified.')
            self.aws_stage = None

        # get request id
        if context:
            self.aws_request_id = context.aws_request_id
        else:
            self.aws_request_id = None

        # get logging level
        log_level = self.__find_stage_var('log_level', event)
        if log_level:
            self.__set_logging_level(log_level)

        # get emails
        to_email = self.__find_stage_var('to_email', event)
        from_email = self.__find_stage_var('from_email', event)

        # set up error reporter
        lambda_name = self.__class__.__name__
        if self.context:
            lambda_name = self.context.function_name
        table_name = '{}d43-catalog-errors'.format(self.stage_prefix())
        self.reporter = ErrorReporter(reporter=lambda_name,
                                      table=table_name,
                                      request_id=self.aws_request_id,
                                      to_email=to_email,
                                      from_email=from_email,
                                      error_threshold=8)
Exemple #2
0
class Handler(object):
    """
    Provides a base for lambda handlers
    """
    __metaclass__ = ABCMeta

    # The number of consecutive lambda instances that must report errors before an email is sent
    __ERROR_THRESHOLD = 4

    def __init__(self, event, context):
        """

        :param dict event:
        :param context:
        """

        # Make Boto3 not be so noisy
        logging.getLogger('boto3').setLevel(logging.ERROR)
        logging.getLogger('botocore').setLevel(logging.ERROR)

        # Make USFM-tools not be so noisy
        logging.getLogger('usfm_tools').setLevel(logging.WARNING)

        # Set up logger
        self.logger = logging.getLogger()  # type: logging._loggerClass
        self.logger.setLevel(logging.DEBUG)
        self.event = event
        self.context = context

        # get stage name
        if event and 'context' in event and 'stage' in event['context']:
            self.aws_stage = event['context']['stage']
        elif event and 'stage' in event:
            # TRICKY: the stage must be manually given for cloudwatch events
            self.aws_stage = event['stage']
        else:
            self.logger.warning('AWS Stage is not specified.')
            self.aws_stage = None

        # get request id
        if context:
            self.aws_request_id = context.aws_request_id
        else:
            self.aws_request_id = None

        # get logging level
        log_level = self.__find_stage_var('log_level', event)
        if log_level:
            self.__set_logging_level(log_level)

        # get emails
        to_email = self.__find_stage_var('to_email', event)
        from_email = self.__find_stage_var('from_email', event)

        # set up error reporter
        lambda_name = self.__class__.__name__
        if self.context:
            lambda_name = self.context.function_name
        table_name = '{}d43-catalog-errors'.format(self.stage_prefix())
        self.reporter = ErrorReporter(reporter=lambda_name,
                                      table=table_name,
                                      request_id=self.aws_request_id,
                                      to_email=to_email,
                                      from_email=from_email,
                                      error_threshold=8)

    def __find_stage_var(self, key, dict):
        """
        Searches for a stage variable in the dictionary.
        The key may exist in 'stage-variables' or in the root of the dictionary
        :param key:
        :param dict:
        :return:
        """
        if not dict:
            return None
        if 'stage-variables' in dict and key in dict['stage-variables']:
            return dict['stage-variables'][key]
        elif key in dict:
            return dict[key]
        else:
            return None

    def __set_logging_level(self, level):
        """
        Sets the logging level of the global logger
        :param level:
        :return:
        """
        level = level.lower()
        if level == 'info':
            self.logger.setLevel(logging.INFO)
        elif level == 'warning':
            self.logger.setLevel(logging.WARNING)
        elif level == 'error':
            self.logger.setLevel(logging.ERROR)
        else:
            self.logger.setLevel(logging.DEBUG)

    @staticmethod
    def sanitize_identifier(identifier, lower=True, logger=None):
        """
        Sanitizes an identifier.
        Warnings will be produced if the identifier is malformed
        :param string identifier:
        :param bool lower: returns the identifier in lower case
        :param logger:
        :return:
        """
        # errors
        if not isinstance(identifier, basestring):
            if logger:
                logger.error(
                    'Identifier "{}" is not a string'.format(identifier))
            return identifier
        if not identifier.strip():
            if logger:
                logger.error('Identifier "{}" is empty'.format(identifier))
            return identifier

        # warnings
        if '_' in identifier and logger:
            logger.warning(
                'Identifier "{}" contains an underscore'.format(identifier))

        if lower:
            return identifier.strip().lower()
        else:
            return identifier.strip()

    def stage_prefix(self):
        """
        Returns the prefix that should be used for operations within this stage. e.g. database names etc.
        The prefix for an undefined or production stages will be an empty string.
        :return:
        """
        if self.aws_stage and not self.aws_stage.lower().startswith('prod'):
            return '{}-'.format(self.aws_stage.lower())
        else:
            return ''

    def report_error(self, message):
        """
        Records an error that will be reported to administrators if not automatically resolved.
        :param string|list message: the error message
        :return:
        """
        self.reporter.add_error(message)

    def run(self, **kwargs):
        """
        :param kwargs:
        :return dict:
        """
        self.logger.debug("EVENT:")
        self.logger.debug(json.dumps(self.event))
        self.logger.debug('Stage Prefix: {}'.format(self.stage_prefix()))
        try:
            return self._run(**kwargs)
        except Exception as e:
            self.logger.error(e.message, exc_info=1)
            raise Exception, EnvironmentError('Bad Request: {}'.format(
                e.message)), sys.exc_info()[2]
        finally:
            self.reporter.commit()

    @abstractmethod
    def _run(self, **kwargs):
        """
        Dummy function for handlers.

        Override this so handle() will catch the exception and make it a "Bad Request: "

        :param dict event:
        :param context:
        :param kwargs:
        :return dict:
        """
        raise NotImplementedError()

    @staticmethod
    def retrieve(dictionary, key, dict_name=None):
        """
        Retrieves a value from a dictionary.

        raises an error message if the specified key is not valid

        :param dict dictionary:
        :param any key:
        :param str|unicode dict_name: name of dictionary, for error message
        :return: value corresponding to key
        """
        if key in dictionary:
            return dictionary[key]
        dict_name = "dictionary" if dict_name is None else dict_name
        raise Exception('\'{k}\' not found in {d}'.format(k=key, d=dict_name))

    @staticmethod
    def retrieve_with_default(dictionary, key, default=None):
        """
        Retrieves a value from a dictionary.

        raises an error message if the specified key is not valid

        :param dict dictionary:
        :param any key:
        :param str|unicode dict_name: name of dictionary, for error message
        :return: value corresponding to key
        """
        if key in dictionary:
            return dictionary[key]
        else:
            return default
 def make_reporter(self):
     return ErrorReporter(reporter='my-lambda',
                          table='db-table',
                          request_id='my-request-id',
                          to_email='*****@*****.**',
                          from_email='*****@*****.**')