def __init__(self, script_name, level=logging.WARN, logger=None): logging.Handler.__init__(self, level) # Context for OOPS reports. self.request = ScriptRequest([('script_name', script_name), ('path', sys.argv[0])]) self.setFormatter(LaunchpadFormatter()) self.logger = logger
def report_oops(file_alias_url=None, error_msg=None): """Record an OOPS for the current exception and return the OOPS ID.""" info = sys.exc_info() properties = [] if file_alias_url is not None: properties.append(('Sent message', file_alias_url)) if error_msg is not None: properties.append(('Error message', error_msg)) request = ScriptRequest(properties) request.principal = get_current_principal() errorUtility = ErrorReportingUtility() # Report all exceptions: the mail handling code doesn't expect any in # normal operation. errorUtility._ignored_exceptions = set() report = errorUtility.raising(info, request) # Note that this assert is arguably bogus: raising is permitted to filter # reports. assert report is not None, ('No OOPS generated.') return report['id']
def test_raising_unauthorized_without_principal(self): """Unauthorized exceptions are logged when the request has no principal.""" utility = ErrorReportingUtility() utility._oops_config.publisher = None request = ScriptRequest([('name2', 'value2')]) try: raise Unauthorized('xyz') except Unauthorized: self.assertNotEqual(None, utility.raising(sys.exc_info(), request))
def _reportError(self, path, exception, hosting_path=None): properties = [ ("path", path), ("error-explanation", unicode(exception)), ] if hosting_path is not None: properties.append(("hosting_path", hosting_path)) request = ScriptRequest(properties) getUtility(IErrorReportingUtility).raising(sys.exc_info(), request) raise faults.OopsOccurred("creating a Git repository", request.oopsid)
def process(self): """Process an upload that is the result of a build. The name of the leaf is the build id of the build. Build uploads always contain a single package per leaf. """ logger = BufferLogger() if self.build.status != BuildStatus.UPLOADING: self.processor.log.warn( "Expected build status to be 'UPLOADING', was %s. Ignoring." % self.build.status.name) return try: # The recipe may have been deleted so we need to flag that here # and will handle below. We check so that we don't go to the # expense of doing an unnecessary upload. We don't just exit here # because we want the standard cleanup to occur. recipe_deleted = (ISourcePackageRecipeBuild.providedBy(self.build) and self.build.recipe is None) if recipe_deleted: result = UploadStatusEnum.FAILED else: self.processor.log.debug("Build %s found" % self.build.id) [changes_file] = self.locateChangesFiles() logger.debug("Considering changefile %s" % changes_file) result = self.processChangesFile(changes_file, logger) except (KeyboardInterrupt, SystemExit): raise except: info = sys.exc_info() message = ('Exception while processing upload %s' % self.upload_path) properties = [('error-explanation', message)] request = ScriptRequest(properties) error_utility = ErrorReportingUtility() error_utility.raising(info, request) logger.error('%s (%s)' % (message, request.oopsid)) result = UploadStatusEnum.FAILED if (result != UploadStatusEnum.ACCEPTED or not self.build.verifySuccessfulUpload()): self.build.updateStatus(BuildStatus.FAILEDTOUPLOAD) if self.build.status != BuildStatus.FULLYBUILT: if recipe_deleted: # For a deleted recipe, no need to notify that uploading has # failed - we just log a warning. self.processor.log.warn( "Recipe for build %s was deleted. Ignoring." % self.upload) else: self.build.storeUploadLog(logger.getLogBuffer()) self.build.notify(extra_info="Uploading build %s failed." % self.upload) else: self.build.notify() self.processor.ztm.commit() self.moveProcessedUpload(result, logger)
def main(self): notifications_sent = False bug_notification_set = getUtility(IBugNotificationSet) deferred_notifications = \ bug_notification_set.getDeferredNotifications() process_deferred_notifications(deferred_notifications) pending_notifications = get_email_notifications( bug_notification_set.getNotificationsToSend()) for (bug_notifications, omitted_notifications, messages) in pending_notifications: try: for message in messages: try: self.logger.info("Notifying %s about bug %d." % ( message['To'], bug_notifications[0].bug.id)) sendmail(message) self.logger.debug(message.as_string()) except SMTPException: request = ScriptRequest([ ("script_name", self.name), ("path", sys.argv[0]), ]) error_utility = getUtility(IErrorReportingUtility) oops_vars = { "message_id": message.get("Message-Id"), "notification_type": "bug", "recipient": message["To"], "subject": message["Subject"], } with error_utility.oopsMessage(oops_vars): error_utility.raising(sys.exc_info(), request) self.logger.info(request.oopsid) self.txn.abort() # Re-raise to get out of this loop and go to the # next iteration of the outer loop. raise except SMTPException: continue for notification in bug_notifications: notification.date_emailed = UTC_NOW notification.status = BugNotificationStatus.SENT for notification in omitted_notifications: notification.date_emailed = UTC_NOW notification.status = BugNotificationStatus.OMITTED notifications_sent = True # Commit after each batch of email sent, so that we won't # re-mail the notifications in case of something going wrong # in the middle. self.txn.commit() if not notifications_sent: self.logger.debug("No notifications are pending to be sent.")
def report_oops(message=None, properties=None, info=None, transaction_manager=None): """Record an oops for the current exception. This must only be called while handling an exception. Searches for 'URL', 'url', or 'baseurl' properties, in order of preference, to use as the linked URL of the OOPS report. :param message: custom explanatory error message. Do not use str(exception) to fill in this parameter, it should only be set when a human readable error has been explicitly generated. :param properties: Properties to record in the OOPS report. :type properties: An iterable of (name, value) tuples. :param info: Exception info. :type info: The return value of `sys.exc_info()`. :param transaction_manager: A transaction manager. If specified, further commit() calls will be logged. """ # Get the current exception info first of all. if info is None: info = sys.exc_info() # Collect properties to report. if properties is None: properties = [] else: properties = list(properties) if message is not None: properties.append(('error-explanation', message)) # Find a candidate for the request URL. def find_url(): for name in 'URL', 'url', 'baseurl': for key, value in properties: if key == name: return value return None url = find_url() # Create the dummy request object. request = ScriptRequest(properties, url) error_utility = ErrorReportingUtility() error_utility.configure(section_name='checkwatches') error_utility.raising(info, request) return request
def test__makeErrorReport_combines_request_and_error_vars(self): """The oops messages should be distinct from real request vars.""" utility = ErrorReportingUtility() utility._oops_config.publisher = None request = ScriptRequest([('c', 'd')]) with utility.oopsMessage(dict(a='b')): try: raise ArbitraryException('foo') except ArbitraryException: info = sys.exc_info() oops = utility._oops_config.create( dict(exc_info=info, http_request=request)) self.assertEqual({ '<oops-message-0>': "{'a': 'b'}", 'c': 'd' }, oops['req_vars'])
def process(self): """Process an upload's changes files, and move it to a new directory. The destination directory depends on the result of the processing of the changes files. If there are no changes files, the result is 'failed', otherwise it is the worst of the results from the individual changes files, in order 'failed', 'rejected', 'accepted'. """ changes_files = self.locateChangesFiles() results = set() for changes_file in changes_files: self.processor.log.debug("Considering changefile %s" % changes_file) try: results.add( self.processChangesFile(changes_file, self.processor.log)) except (KeyboardInterrupt, SystemExit): raise except: info = sys.exc_info() message = ('Exception while processing upload %s' % self.upload_path) properties = [('error-explanation', message)] request = ScriptRequest(properties) error_utility = ErrorReportingUtility() error_utility.raising(info, request) self.processor.log.error('%s (%s)' % (message, request.oopsid)) results.add(UploadStatusEnum.FAILED) if len(results) == 0: destination = UploadStatusEnum.FAILED else: for destination in [ UploadStatusEnum.FAILED, UploadStatusEnum.REJECTED, UploadStatusEnum.ACCEPTED ]: if destination in results: break self.moveProcessedUpload(destination, self.processor.log)
def processQueueItem(self, queue_item): """Attempt to process `queue_item`. This method swallows exceptions that occur while processing the item. :param queue_item: A `PackageUpload` to process. :return: True on success, or False on failure. """ self.logger.debug("Processing queue item %d" % queue_item.id) try: queue_item.realiseUpload(self.logger) except Exception: message = "Failure processing queue_item %d" % queue_item.id properties = [('error-explanation', message)] request = ScriptRequest(properties) ErrorReportingUtility().raising(sys.exc_info(), request) self.logger.error('%s (%s)', message, request.oopsid) return False else: self.logger.debug("Successfully processed queue item %d", queue_item.id) return True
def test_raising_for_script(self): """Test ErrorReportingUtility.raising with a ScriptRequest.""" utility = ErrorReportingUtility() utility._oops_config.publisher = None # A list because code using ScriptRequest expects that - ScriptRequest # translates it to a dict for now. req_vars = [ ('name2', 'value2'), ('name1', 'value1'), ('name1', 'value3'), ] url = 'https://launchpad.net/example' try: raise ArbitraryException('xyz\nabc') except ArbitraryException: # Do not test escaping of request vars here, it is already tested # in test_raising_with_request. request = ScriptRequest(req_vars, URL=url) report = utility.raising(sys.exc_info(), request) self.assertEqual(url, report['url']) self.assertEqual(dict(req_vars), report['req_vars'])