示例#1
0
    def test_does_not_email_for_whitelisted_errors(self):
        req = api.RecordErrorRequest(
            traceback=[
                api.StackLine("/site-packages/lib/test.py", 5, "test_func",
                              "code"),
                api.StackLine(
                    "/site-packages/lib/doghouse/authentication.py", 7,
                    "check_auth",
                    'raise errors.BadAuthenticationError("Something smells off...")'
                )
            ],
            exception_message="email text",
            hostname="localhost",
        )
        self.handler.record_error(req.dumps())

        self.assertDictEquals(
            {
                api.ErrorKey(
                    "lib/doghouse/authentication.py", 7, "check_auth", 'raise errors.BadAuthenticationError("Something smells off...")'):
                api.ErrorInfo(1,
                              "*****@*****.**",
                              "2017-07-30 00:00:00",
                              False,
                              "2020-01-01 00:00:00",
                              is_known_error=True,
                              last_error_data=req)
            }, self.handler.errors_seen.dict)
        self.assertEquals(None, self.smtp_stub.last_args)
示例#2
0
    def test_traces_up_stack_trace_for_errors_originating_from_building_blocks(
            self):
        req = api.RecordErrorRequest(
            traceback=[
                api.StackLine("/site-packages/lib/test.py", 5, "test_func",
                              "code"),
                api.StackLine("/site-packages/coreservices/service.py", 7,
                              "serve", "..."),
                api.StackLine(
                    "/site-packages/apps/shopkick/doghouse/lib/base.py", 9,
                    "_get_request_param",
                    'raise errors.BadRequestError("Missing param %s" % name)')
            ],
            exception_message="email text",
            hostname="localhost",
        )
        self.handler.record_error(req.dumps())

        self.assertDictEquals(
            {
                api.ErrorKey("coreservices/service.py", 7, "serve", "..."):
                api.ErrorInfo(1,
                              "*****@*****.**",
                              "2017-07-30 00:00:00",
                              True,
                              "2020-01-01 00:00:00",
                              is_known_error=False,
                              last_error_data=req)
            }, self.handler.errors_seen.dict)
示例#3
0
    def test_records_error_only_once(self):
        req = api.RecordErrorRequest(
            traceback=[
                api.StackLine("/site-packages/lib/test.py", 5, "test_func",
                              "code"),
                api.StackLine("/site-packages/coreservices/service.py", 7,
                              "serve", "..."),
                api.StackLine("/site-packages/thirdparty/3rdparty_lib.py", 9,
                              "call", "x")
            ],
            exception_message="email text",
            hostname="localhost",
        )
        self.handler.record_error(req.dumps())
        self._set_stub_time(datetime.datetime(2020, 1, 2))
        self.handler.record_error(req.dumps())

        self.assertDictEquals(
            {
                api.ErrorKey("coreservices/service.py", 7, "serve", "..."):
                api.ErrorInfo(2,
                              "*****@*****.**",
                              "2017-07-30 00:00:00",
                              True,
                              "2020-01-02 00:00:00",
                              is_known_error=False,
                              last_error_data=req)
            }, self.handler.errors_seen.dict)
        self.assertEqual(1, len(self.smtp_stub.args_list))
示例#4
0
    def test_doesnt_email_on_errors_before_cutoff_date(self):
        req = api.RecordErrorRequest(
            traceback=[
                api.StackLine("/site-packages/lib/test.py", 5, "test_func",
                              "code"),
                api.StackLine("/site-packages/coreservices/service.py", 7,
                              "serve", "..."),
                api.StackLine("/site-packages/thirdparty/3rdparty_lib.py", 9,
                              "call", "x")
            ],
            exception_message="email text",
            hostname="localhost",
        )
        self.popen_stub.stdout = StringIO.StringIO(
            "75563df6e9d1efe44b48f6643fde9ebbd822b0c5 25 25 1\n"
            "author John Egan\n"
            "author-mail <*****@*****.**>\n"
            "author-time %d\n"
            "author-tz -0800\n" %
            int(time.mktime(datetime.datetime(2009, 7, 30).timetuple())))

        self.handler.record_error(req.dumps())
        self.assertDictEquals(
            {
                api.ErrorKey("coreservices/service.py", 7, "serve", "..."):
                api.ErrorInfo(1,
                              "*****@*****.**",
                              "2009-07-30 00:00:00",
                              False,
                              "2020-01-01 00:00:00",
                              is_known_error=False,
                              last_error_data=req)
            }, self.handler.errors_seen.dict)
        self.assertEqual(None, self.smtp_stub.last_args)
示例#5
0
    def test_doesnt_report_errors_under_threshold(self):
        self.test_config.report_error_threshold = 2
        req = api.RecordErrorRequest(
            traceback=[
                api.StackLine("/site-packages/lib/test.py", 5, "test_func",
                              "code"),
                api.StackLine("/site-packages/coreservices/service.py", 7,
                              "serve", "..."),
                api.StackLine("/site-packages/thirdparty/3rdparty_lib.py", 9,
                              "call", "x")
            ],
            exception_message="email text",
            hostname="localhost",
        )

        self.handler.record_error(req.dumps())
        self.assertDictEquals(
            {
                api.ErrorKey("coreservices/service.py", 7, "serve", "..."):
                api.ErrorInfo(1,
                              "*****@*****.**",
                              "2017-07-30 00:00:00",
                              False,
                              "2020-01-01 00:00:00",
                              is_known_error=False,
                              last_error_data=req)
            }, self.handler.errors_seen.dict)
        self.assertEquals(None, self.smtp_stub.last_args)
示例#6
0
    def test_records_error_with_thrift_in_file_name(self):
        req = api.RecordErrorRequest(
            traceback=[
                api.StackLine("/site-packages/coreservices/thrift_file.py", 7,
                              "serve", "..."),
                api.StackLine("/site-packages/thirdparty/3rdparty_lib.py", 9,
                              "call", "x")
            ],
            exception_message="email text",
            hostname="localhost",
        )

        self.handler.record_error(req.dumps())
        self.assertDictEquals(
            {
                api.ErrorKey("coreservices/thrift_file.py", 7, "serve", "..."):
                api.ErrorInfo(1,
                              "*****@*****.**",
                              "2017-07-30 00:00:00",
                              True,
                              "2020-01-01 00:00:00",
                              is_known_error=False,
                              last_error_data=req)
            }, self.handler.errors_seen.dict)
示例#7
0
    def test_records_error(self):
        req = api.RecordErrorRequest(
            traceback=[
                api.StackLine("/site-packages/lib/test.py", 5, "test_func",
                              "code"),
                api.StackLine("/site-packages/coreservices/service.py", 7,
                              "serve", "..."),
                api.StackLine("/site-packages/thirdparty/3rdparty_lib.py", 9,
                              "call", "x")
            ],
            exception_message="email text",
            hostname="localhost",
        )

        self.handler.record_error(req.dumps())
        self.assertDictEquals(
            {
                api.ErrorKey("coreservices/service.py", 7, "serve", "..."):
                api.ErrorInfo(1,
                              "*****@*****.**",
                              "2017-07-30 00:00:00",
                              True,
                              "2020-01-01 00:00:00",
                              is_known_error=False,
                              last_error_data=req)
            }, self.handler.errors_seen.dict)
        self.assertEqual([
            "git", "--git-dir=/tmp/.git", "--work-tree=/tmp", "blame", "-p",
            "/tmp/coreservices/service.py", "-L", "7,+1"
        ], self.popen_stub.last_args)
        self.assertEmailEquals(
            dict(to_addresses=["*****@*****.**"],
                 from_address="*****@*****.**",
                 subject="Error on localhost in coreservices/service.py",
                 body="email text",
                 smtp_server_host_port=None), self.smtp_stub.last_args)
示例#8
0
    def _record_error(self, request):
        # Parse request
        request = api.RecordErrorRequest.loads(request)

        # Figure out which line in the stack trace is to blame for the error
        key, blamed_entry, email_recipients = self._blame_line(
            request.traceback)
        if not key:
            return

        # If this error hasn't been reported before, then find the dev responsible
        err_info = None
        if key not in self.errors_seen:
            # If flawless is being flooded wih errors, limit the number of git blames so the
            # service doesn't fall over. We don't use a thread safe counter, because 10
            # git blames is just a soft limit
            if self.number_of_git_blames_running > config.max_concurrent_git_blames:
                log.error(
                    "Unable to process %s because %d git blames already running"
                    % (str(key), self.number_of_git_blames_running))
                return
            try:
                self.number_of_git_blames_running += 1
                email, last_touched_ts = self.repository.blame(
                    key.filename, key.line_number)
            finally:
                self.number_of_git_blames_running -= 1
            dev_email = self._get_email(email)
            last_touched_ts = last_touched_ts or 0

            cur_time = self._convert_epoch_ms(
                datetime.datetime).strftime("%Y-%m-%d %H:%M:%S")
            mod_time = self._convert_epoch_ms(datetime.datetime,
                                              epoch_ms=last_touched_ts * 1000)
            mod_time = mod_time.strftime("%Y-%m-%d %H:%M:%S")
            known_entry = self._get_entry(blamed_entry, self.known_errors)
            err_info = api.ErrorInfo(error_count=1,
                                     developer_email=dev_email or "unknown",
                                     date=mod_time,
                                     email_sent=False,
                                     last_occurrence=cur_time,
                                     is_known_error=bool(known_entry),
                                     last_error_data=request)
            self.errors_seen[key] = err_info
            log.info("Error %s caused by %s on %s" %
                     (str(key), dev_email, mod_time))

            if not dev_email:
                self._handle_flawless_issue(
                    "Unable to do blame for %s. You may want to consider setting "
                    "only_blame_filepaths_matching in your flawless.cfg " %
                    str(key))
                err_info.email_sent = True
                return
        # If we've already seen this error then update the error count
        elif key in self.errors_seen:
            err_info = self.errors_seen[key]
            err_info.error_count += 1
            err_info.last_error_data = request
            cur_dt = self._convert_epoch_ms(datetime.datetime)
            err_info.last_occurrence = cur_dt.strftime("%Y-%m-%d %H:%M:%S")
            self.errors_seen[key] = err_info

        # Figure out if we should send an email or not
        send_email = False
        known_entry = None
        if blamed_entry not in self.known_errors[blamed_entry.filename]:
            # If it is an unknown error, then it must meet certain criteria. The code must have been
            # touched after report_only_after_minimum_date so errors in old code can be ignored. It
            # also has to have occurred at least report_error_threshold times (although the client
            # is allowed to override that value).
            if (not err_info.email_sent
                    and err_info.date >= config.report_only_after_minimum_date
                    and err_info.error_count >=
                (request.error_threshold or config.report_error_threshold)):
                send_email = True
        else:
            # If it is a known error, we allow fine grainted control of how frequently emails will
            # be sent. An email will be sent if it has passed the min_alert_threshold, and/or this
            # is the Nth occurrence as defined alert_every_n_occurences. If it has passed
            # max_alert_threshold then no emails will be sent.
            known_entry = self._get_entry(blamed_entry, self.known_errors)
            if (known_entry.min_alert_threshold
                    and err_info.error_count >= known_entry.min_alert_threshold
                    and not err_info.email_sent):
                send_email = True
            if (known_entry.alert_every_n_occurences and err_info.error_count %
                    known_entry.alert_every_n_occurences == 0):
                send_email = True
            if (known_entry.max_alert_threshold is not None and
                    err_info.error_count > known_entry.max_alert_threshold):
                send_email = False

        # Send email if applicable
        if send_email:
            email_body = []
            dev_email = self._get_email(err_info.developer_email)
            if dev_email:
                email_recipients.append(dev_email)

            # Add additional recipients that have registered for this error
            if blamed_entry.filename in self.watch_only_if_blamed:
                email_recipients.extend(
                    self.watch_only_if_blamed[blamed_entry.filename])
            if known_entry:
                email_recipients.extend(known_entry.email_recipients or [])
                email_body.append(known_entry.email_header or "")

            email_body.append(self._format_traceback(request))
            email_body.append(
                "<br /><br /><a href='http://%s/add_known_error?%s'>Add to whitelist</a>"
                % (config.hostname + ":" + str(config.port),
                   urllib.urlencode(
                       dict(filename=key.filename,
                            function_name=key.function_name,
                            code_fragment=key.text))))

            # Send the email
            log.info("Sending email for %s to %s" %
                     (str(key), ", ".join(email_recipients)))
            self._sendmail(
                to_addresses=email_recipients,
                subject="Error on %s in %s" % (request.hostname, key.filename),
                body="<br />".join([s for s in email_body if s]),
            )
            err_info.email_sent = True
            self.errors_seen[key] = err_info