launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #04588
[Merge] lp:~lifeless/launchpad/useoops into lp:launchpad
Robert Collins has proposed merging lp:~lifeless/launchpad/useoops into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~lifeless/launchpad/useoops/+merge/71506
Delete a bunch of too-big test contents and narrow the tests to testing the thing they are really interested in; refactor oops creation into three parts: make, filter and send. Upgrade to python-oops 0.0.3 which supports missing keys better.
--
https://code.launchpad.net/~lifeless/launchpad/useoops/+merge/71506
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~lifeless/launchpad/useoops into lp:launchpad.
=== modified file 'configs/development/launchpad-lazr.conf'
--- configs/development/launchpad-lazr.conf 2011-07-22 15:35:08 +0000
+++ configs/development/launchpad-lazr.conf 2011-08-15 04:48:24 +0000
@@ -30,7 +30,6 @@
[bzr_lpserve]
error_dir: /var/tmp/codehosting.test
oops_prefix: BZR
-copy_to_zlog: false
[canonical]
show_tracebacks: True
@@ -53,7 +52,6 @@
secret_path: configs/development/codebrowse-secret
error_dir: /var/tmp/codebrowse.launchpad.dev/errors
oops_prefix: CB
-copy_to_zlog: false
[codehosting]
launch: True
@@ -70,7 +68,6 @@
port: tcp:5022:interface=127.0.0.88
error_dir: /var/tmp/codehosting.test
oops_prefix: SMPSSH
-copy_to_zlog: false
bzr_lp_prefix: lp://dev/
lp_url_hosts: dev
access_log: /var/tmp/bazaar.launchpad.dev/codehosting-access.log
@@ -88,7 +85,6 @@
[codeimportworker]
error_dir: /var/tmp/codehosting.test
oops_prefix: CIW
-copy_to_zlog: false
[commercial]
voucher_proxy_url: http://launchpad.dev
@@ -121,7 +117,6 @@
[error_reports]
oops_prefix: X
error_dir: /var/tmp/lperr
-copy_to_zlog: True
[google]
# Development and the testrunner should use the stub service be default.
=== modified file 'configs/testrunner/launchpad-lazr.conf'
--- configs/testrunner/launchpad-lazr.conf 2011-07-07 10:26:19 +0000
+++ configs/testrunner/launchpad-lazr.conf 2011-08-15 04:48:24 +0000
@@ -56,7 +56,6 @@
[error_reports]
oops_prefix: T
error_dir: /var/tmp/lperr.test
-copy_to_zlog: false
[gina_target.hoary]
architectures: i386
=== modified file 'lib/canonical/config/schema-lazr.conf'
--- lib/canonical/config/schema-lazr.conf 2011-07-27 20:14:40 +0000
+++ lib/canonical/config/schema-lazr.conf 2011-08-15 04:48:24 +0000
@@ -55,9 +55,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
[builddmaster]
# The database user which will be used by this process.
@@ -178,8 +175,6 @@
oops_prefix: none
# See [error_reports].
error_dir: none
-# See [error_reports].
-copy_to_zlog: false
[calculate_bug_heat]
@@ -188,7 +183,6 @@
dbuser: calculate-bug-heat
oops_prefix: none
error_dir: none
-copy_to_zlog: false
max_heat_age: 7
@@ -225,9 +219,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
# datatype: integer
batch_query_threshold: 10
@@ -297,7 +288,7 @@
# datatype: string
secret_path:
-# See [error_reports].
+# Ignored; delete when lp-prod-configs updated.
copy_to_zlog: False
# See [error_reports].
@@ -417,9 +408,6 @@
# datatype: string
port: tcp:5022
-# See [error_reports].
-copy_to_zlog: False
-
# Private mirror hosts
# A comma separated list of domain names that should not be shown
# to people not associated with the branch. Any branches with a mirror
@@ -527,9 +515,6 @@
max_jobs_per_machine: 3
# See [error_reports].
-copy_to_zlog: False
-
-# See [error_reports].
error_dir: /var/tmp/codehosting.test/
# See [error_reports].
@@ -557,9 +542,6 @@
working_directory_root: /var/tmp/codeimport/data
# See [error_reports].
-copy_to_zlog: False
-
-# See [error_reports].
error_dir: /var/tmp/codehosting.test/
# See [error_reports].
@@ -597,9 +579,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
[packaging_translations]
# The database user which will be used by this process.
# datatype: string
@@ -612,9 +591,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
[cveupdater]
# The database user which will be used by this process.
@@ -742,9 +718,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
[error_reports]
# A prefix for "OOPS" codes for this process instance.
@@ -757,11 +730,6 @@
# datatype: string
error_dir: none
-# Should exceptions be sent to the zope log as well as being
-# saved to disk?
-# datatype: boolean
-copy_to_zlog: False
-
[expiredmembershipsflagger]
# The database user which will be used by this process.
@@ -988,8 +956,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
[IPlainPackageCopyJobSource]
module: lp.soyuz.interfaces.packagecopyjob
@@ -1004,8 +970,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
[karmacacheupdater]
# The database user which will be used by this process.
@@ -1342,8 +1306,7 @@
oops_prefix: none
# Must be set per librarian instance.
error_dir: none
-# Must be false: librarian has no zlog.
-copy_to_zlog: False
+
[librarian_gc]
# The database user which will be used by this process.
@@ -1452,9 +1415,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
# Whether Mailman should be built if it is not already.
# datatype: boolean
build: false
@@ -1588,9 +1548,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
##
## TODO: delete mpcreationjobs section after 10.04 rollout.
@@ -1608,9 +1565,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
[person_notification]
# User for person notification db access
@@ -1667,8 +1621,6 @@
oops_prefix: none
# See [error_reports].
error_dir: none
-# See [error_reports].
-copy_to_zlog: false
[poppy]
@@ -1701,9 +1653,6 @@
# datatype: string
ftp_port: 2121
-# See [error_reports].
-copy_to_zlog: False
-
# The poppy access log location. Information such as connection, SSH
# login and session start times will be logged here.
access_log: /tmp/poppy-access.log
@@ -1730,7 +1679,6 @@
dbuser: process-apport-blobs
oops_prefix: APPORTBLOB
error_dir: none
-copy_to_zlog: false
[productreleasefinder]
@@ -1785,14 +1733,12 @@
error_dir: none
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
[request_daily_builds]
dbuser: request-daily-builds
error_dir: none
oops_prefix: none
-copy_to_zlog: false
+
[revisionkarma]
# The database user which will be used by this process.
@@ -1853,8 +1799,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
[translations_export_to_branch]
# See [error_reports].
@@ -1863,9 +1807,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
[sendbranchmail]
# The database user which will be used by this process.
@@ -1880,9 +1821,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
# For the personal standing updater cron script.
[standingupdater]
@@ -1920,9 +1858,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
[supermirror_import_puller]
# See [error_reports].
@@ -1931,9 +1866,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
[supermirror_mirror_puller]
# See [error_reports].
@@ -1942,9 +1874,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
[supermirror_upload_puller]
# See [error_reports].
@@ -1953,9 +1882,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
[targetnamecacheupdater]
# The database user which will be used by this process.
@@ -2001,9 +1927,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
[upgrade_branches]
dbuser: upgrade-branches
@@ -2014,9 +1937,6 @@
# See [error_reports].
oops_prefix: none
-# See [error_reports].
-copy_to_zlog: false
-
[uploader]
# The database user which will be used by this process.
=== modified file 'lib/canonical/launchpad/webapp/errorlog.py'
--- lib/canonical/launchpad/webapp/errorlog.py 2011-08-12 21:38:36 +0000
+++ lib/canonical/launchpad/webapp/errorlog.py 2011-08-15 04:48:24 +0000
@@ -189,7 +189,6 @@
"""
if section_name is None:
section_name = self._default_config_section
- self.copy_to_zlog = config[section_name].copy_to_zlog
# Start a new UniqueFileAllocator to activate the new configuration.
self.log_namer = UniqueFileAllocator(
output_root=config[section_name].error_dir,
@@ -288,44 +287,59 @@
def _raising(self, info, request=None, now=None, informational=False):
"""Private method used by raising() and handling()."""
- entry, filename = self._makeErrorReport(info, request, now, informational)
- if entry is None:
+ report = self._makeReport(info, request, now, informational)
+ if self._filterReport(report):
return
- oops.serializer_rfc822.write(entry, open(filename, 'wb'))
+ self._sendReport(report, now=now)
+ if request:
+ request.oopsid = report['id']
+ request.oops = report
+ return report
+
+ def _sendReport(self, report, now=None):
+ if now is not None:
+ now = now.astimezone(UTC)
+ else:
+ now = datetime.datetime.now(UTC)
+ oopsid, filename = self.log_namer.newId(now)
+ report['id'] = oopsid
+ oops.serializer_rfc822.write(report, open(filename, 'wb'))
# Set file permission to: rw-r--r--
wanted_permission = (
stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
os.chmod(filename, wanted_permission)
- if request:
- request.oopsid = entry['id']
- request.oops = entry
-
- if self.copy_to_zlog:
- self._do_copy_to_zlog(
- entry['time'], entry['type'], entry['url'], info, entry['id'])
- notify(ErrorReportEvent(entry))
- return entry
-
- def _isIgnoredException(self, strtype, request=None, exception=None):
- """Should the given exception generate an OOPS or be ignored?
-
- Exceptions will be ignored if they
- - are specially tagged as being ignorable by having the marker
- interface IUnloggedException
- - are of a type included in self._ignored_exceptions, or
- - were requested with an off-site REFERRER header and are of a
- type included in self._ignored_exceptions_for_offsite_referer
+ notify(ErrorReportEvent(report))
+
+ def filter_session_statement(self, database_id, statement):
+ """Replace quoted strings with '%s' in statements on session DB."""
+ if database_id == 'SQL-' + PGSessionBase.store_name:
+ return re.sub("'[^']*'", "'%s'", statement)
+ else:
+ return statement
+
+ def _filterReport(self, report):
+ """Return True if the report should be filtered and not emitted.
+
+ Reports are filtered if:
+ - There is a key 'ignore':True in the report. This is set during
+ _makeReport.
+ - have a type listed in self._ignored_exceptions.
+ - have a missing or offset REFERER header with a type listed in
+ self._ignored_exceptions_for_offsite_referer
"""
- if IUnloggedException.providedBy(exception):
- return True
- if strtype in self._ignored_exceptions:
- return True
- if strtype in self._ignored_exceptions_for_offsite_referer:
- if request is not None:
- referer = request.get('HTTP_REFERER')
- # If there is no referrer then we can't tell if this exception
- # should be ignored or not, so we'll be conservative and
- # ignore it.
+ if report.get('ignore'):
+ return True
+ if report['type'] in self._ignored_exceptions:
+ return True
+ if report['type'] in self._ignored_exceptions_for_offsite_referer:
+ was_http = report.get('url', '').lower().startswith('http')
+ if was_http:
+ req_vars = dict(report.get('req_vars', ()))
+ referer = req_vars.get('HTTP_REFERER')
+ # If there is no referrer then either the user has refer
+ # disabled, or its someone coming from offsite or from some
+ # saved bookmark. Any which way, its not a sign of a current
+ # broken-url-generator in LP: ignore it.
if referer is None:
return True
referer_parts = urlparse.urlparse(referer)
@@ -335,16 +349,8 @@
return True
return False
- def filter_session_statement(self, database_id, statement):
- """Replace quoted strings with '%s' in statements on session DB."""
- if database_id == 'SQL-' + PGSessionBase.store_name:
- return re.sub("'[^']*'", "'%s'", statement)
- else:
- return statement
-
- def _makeErrorReport(self, info, request=None, now=None,
- informational=False):
- """Return an ErrorReport for the supplied data.
+ def _makeReport(self, info, request=None, now=None, informational=False):
+ """Create an unallocated OOPS.
:param info: Output of sys.exc_info()
:param request: The IErrorReportRequest which provides context to the
@@ -358,82 +364,35 @@
now = now.astimezone(UTC)
else:
now = datetime.datetime.now(UTC)
- tb_text = None
-
- strtype = str(getattr(info[0], '__name__', info[0]))
- if self._isIgnoredException(strtype, request, info[1]):
- return None, None
-
+ report = {}
+ report['type'] = _safestr(getattr(info[0], '__name__', info[0]))
+ report['value'] = _safestr(info[1])
if not isinstance(info[2], basestring):
tb_text = ''.join(format_exception(*info,
**{'as_html': False}))
else:
tb_text = info[2]
- tb_text = _safestr(tb_text)
-
- url = None
- username = None
- req_vars = []
- pageid = ''
-
+ report['tb_text'] = _safestr(tb_text)
+ report['req_vars'] = []
+ report['time'] = now
+ report['informational'] = informational
+ report['branch_nick'] = versioninfo.branch_nick
+ report['revno'] = versioninfo.revno
+ # Because of IUnloggedException being a sidewards lookup we must
+ # capture this here to filter on later.
+ report['ignore'] = IUnloggedException.providedBy(info[1])
if request:
- # XXX jamesh 2005-11-22: Temporary fix, which Steve should
- # undo. URL is just too HTTPRequest-specific.
- if safe_hasattr(request, 'URL'):
- url = request.URL
-
- if WebServiceLayer.providedBy(request):
- webservice_error = getattr(
- info[1], '__lazr_webservice_error__', 500)
- if webservice_error / 100 != 5:
- request.oopsid = None
- # Return so the OOPS is not generated.
- return None, None
-
- missing = object()
- principal = getattr(request, 'principal', missing)
- if safe_hasattr(principal, 'getLogin'):
- login = principal.getLogin()
- elif principal is missing or principal is None:
- # Request has no principal.
- login = None
- else:
- # Request has an UnauthenticatedPrincipal.
- login = 'unauthenticated'
- if strtype in (
- self._ignored_exceptions_for_unauthenticated_users):
- return None, None
-
- if principal is not None and principal is not missing:
- username = _safestr(
- ', '.join([
- unicode(login),
- unicode(request.principal.id),
- unicode(request.principal.title),
- unicode(request.principal.description)]))
-
- if getattr(request, '_orig_env', None):
- pageid = request._orig_env.get('launchpad.pageid', '')
-
- for key, value in request.items():
- if _is_sensitive(request, key):
- req_vars.append((_safestr(key), '<hidden>'))
- else:
- req_vars.append((_safestr(key), _safestr(value)))
- if IXMLRPCRequest.providedBy(request):
- args = request.getPositionalArguments()
- req_vars.append(('xmlrpc args', _safestr(args)))
+ self._gather_request(report, request, info)
# XXX AaronBentley 2009-11-26 bug=488950: There should be separate
# storage for oops messages.
- req_vars.extend(
+ report['req_vars'].extend(
('<oops-message-%d>' % key, str(message)) for key, message
in self._oops_messages.iteritems())
- req_vars.sort()
- strv = _safestr(info[1])
-
- strurl = _safestr(url)
-
- duration = get_request_duration()
+ report['req_vars'].sort()
+
+ # More generic than HTTP requests - e.g. how long a script was running
+ # for.
+ report['duration'] = get_request_duration()
# In principle the timeline is per-request, but see bug=623199 -
# at this point the request is optional, but get_request_timeline
# does not care; when it starts caring, we will always have a
@@ -446,26 +405,60 @@
detail = self.filter_session_statement(category, detail)
statements.append(
(start, end, _safestr(category), _safestr(detail)))
-
- oopsid, filename = self.log_namer.newId(now)
-
- result = {
- 'id': oopsid,
- 'type': strtype,
- 'value': strv,
- 'time': now,
- 'pageid': pageid,
- 'tb_text': tb_text,
- 'username': username,
- 'url': strurl,
- 'duration': duration,
- 'req_vars': req_vars,
- 'db_statements': statements,
- 'informational': informational,
- 'branch_nick': versioninfo.branch_nick,
- 'revno': versioninfo.revno,
- }
- return result, filename
+ report['db_statements'] = statements
+ return report
+
+ def _gather_request(self, report, request, info):
+ """Add request metadata into the error report."""
+ # XXX jamesh 2005-11-22: Temporary fix, which Steve should
+ # undo. URL is just too HTTPRequest-specific.
+ if safe_hasattr(request, 'URL'):
+ report['url'] = _safestr(request.URL)
+
+ if WebServiceLayer.providedBy(request):
+ webservice_error = getattr(
+ info[1], '__lazr_webservice_error__', 500)
+ if webservice_error / 100 != 5:
+ request.oopsid = None
+ # Tell the oops machinery to ignore this error
+ report['ignore'] = True
+
+ missing = object()
+ principal = getattr(request, 'principal', missing)
+ if safe_hasattr(principal, 'getLogin'):
+ login = principal.getLogin()
+ elif principal is missing or principal is None:
+ # Request has no principal (e.g. scriptrequest)
+ login = None
+ else:
+ # Request has an UnauthenticatedPrincipal.
+ login = 'unauthenticated'
+ if report['type'] in (
+ self._ignored_exceptions_for_unauthenticated_users):
+ report['ignore'] = True
+
+ if principal is not None and principal is not missing:
+ username = _safestr(
+ ', '.join([
+ unicode(login),
+ unicode(request.principal.id),
+ unicode(request.principal.title),
+ unicode(request.principal.description)]))
+ report['username'] = username
+
+ if getattr(request, '_orig_env', None):
+ report['pageid'] = request._orig_env.get(
+ 'launchpad.pageid', '')
+
+ for key, value in request.items():
+ if _is_sensitive(request, key):
+ report['req_vars'].append((_safestr(key), '<hidden>'))
+ else:
+ report['req_vars'].append(
+ (_safestr(key), _safestr(value)))
+ if IXMLRPCRequest.providedBy(request):
+ args = request.getPositionalArguments()
+ report['req_vars'].append(('xmlrpc args', _safestr(args)))
def handling(self, info, request=None, now=None):
"""Flag ErrorReport as informational only.
@@ -480,29 +473,6 @@
return self._raising(
info, request=request, now=now, informational=True)
- def _do_copy_to_zlog(self, now, strtype, url, info, oopsid):
- distant_past = datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=UTC)
- when = _rate_restrict_pool.get(strtype, distant_past)
- if now > when:
- next_when = max(
- when, now - _rate_restrict_burst * _rate_restrict_period)
- next_when += _rate_restrict_period
- _rate_restrict_pool[strtype] = next_when
- # Sometimes traceback information can be passed in as a string. In
- # those cases, we don't (can't!) log the traceback. The traceback
- # information is still preserved in the actual OOPS report.
- traceback = info[2]
- if not isinstance(traceback, types.TracebackType):
- traceback = None
- # The logging module doesn't provide a way to pass in exception
- # info, so we temporarily raise the exception so it can be logged.
- # We disable the pylint warning for the blank except.
- try:
- raise info[0], info[1], traceback
- except info[0]:
- logging.getLogger('SiteError').exception(
- '%s (%s)' % (url, oopsid))
-
@contextlib.contextmanager
def oopsMessage(self, message):
"""Add an oops message to be included in oopses from this context."""
=== modified file 'lib/canonical/launchpad/webapp/tests/test_errorlog.py'
--- lib/canonical/launchpad/webapp/tests/test_errorlog.py 2011-08-13 04:07:10 +0000
+++ lib/canonical/launchpad/webapp/tests/test_errorlog.py 2011-08-15 04:48:24 +0000
@@ -30,7 +30,6 @@
from zope.publisher.interfaces import NotFound
from zope.publisher.interfaces.xmlrpc import IXMLRPCRequest
from zope.security.interfaces import Unauthorized
-from zope.testing.loggingsupport import InstalledHandler
from canonical.config import config
from lp.app import versioninfo
@@ -161,7 +160,6 @@
# current error directory.
test_data = dedent("""
[error_reports]
- copy_to_zlog: true
error_dir: %s
""" % tempfile.mkdtemp())
config.push('test_data', test_data)
@@ -186,8 +184,6 @@
utility.oops_prefix)
self.assertEqual(config.error_reports.error_dir,
utility.log_namer._output_root)
- self.assertEqual(
- config.error_reports.copy_to_zlog, utility.copy_to_zlog)
# Some external processes may use another config section to
# provide the error log configuration.
utility.configure(section_name='branchscanner')
@@ -195,8 +191,6 @@
utility.oops_prefix)
self.assertEqual(config.branchscanner.error_dir,
utility.log_namer._output_root)
- self.assertEqual(
- config.branchscanner.copy_to_zlog, utility.copy_to_zlog)
# The default error section can be restored.
utility.configure()
@@ -204,8 +198,6 @@
utility.oops_prefix)
self.assertEqual(config.error_reports.error_dir,
utility.log_namer._output_root)
- self.assertEqual(
- config.error_reports.copy_to_zlog, utility.copy_to_zlog)
def test_setOopsToken(self):
"""Test ErrorReportingUtility.setOopsToken()."""
@@ -217,19 +209,18 @@
utility.setOopsToken('1')
self.assertEqual('T1', utility.oops_prefix)
- def test_raising(self):
+ def test_raising_permissions(self):
"""Test ErrorReportingUtility.raising() with no request"""
utility = ErrorReportingUtility()
+ report = {'id': 'OOPS-91T1'}
now = datetime.datetime(2006, 04, 01, 00, 30, 00, tzinfo=UTC)
- # Set up default file creation mode to rwx------.
+ # Set up default file creation mode to rwx------ as some restrictive
+ # servers do.
umask_permission = stat.S_IRWXG | stat.S_IRWXO
old_umask = os.umask(umask_permission)
-
- try:
- raise ArbitraryException('xyz')
- except ArbitraryException:
- utility.raising(sys.exc_info(), now=now)
+ self.addCleanup(os.umask, old_umask)
+ utility._sendReport(report, now)
errorfile = os.path.join(
utility.log_namer.output_dir(now), '01800.T1')
@@ -242,36 +233,6 @@
# Get only the permission bits for this file.
file_permission = stat.S_IMODE(st.st_mode)
self.assertEqual(file_permission, wanted_permission)
- # Restore the umask to the original value.
- os.umask(old_umask)
-
- lines = open(errorfile, 'r').readlines()
-
- # the header
- self.assertEqual(lines[0], 'Oops-Id: OOPS-91T1\n')
- self.assertEqual(lines[1], 'Exception-Type: ArbitraryException\n')
- self.assertEqual(lines[2], 'Exception-Value: xyz\n')
- self.assertEqual(lines[3], 'Date: 2006-04-01T00:30:00+00:00\n')
- self.assertEqual(lines[4], 'Page-Id: \n')
- self.assertEqual(lines[5], 'Branch: %s\n' % versioninfo.branch_nick)
- self.assertEqual(lines[6], 'Revision: %s\n' % versioninfo.revno)
- self.assertEqual(lines[7], 'User: None\n')
- self.assertEqual(lines[8], 'URL: None\n')
- self.assertEqual(lines[9], 'Duration: -1\n')
- self.assertEqual(lines[10], 'Informational: False\n')
- self.assertEqual(lines[11], '\n')
-
- # no request vars
- self.assertEqual(lines[12], '\n')
-
- # no database statements
- self.assertEqual(lines[13], '\n')
-
- # traceback
- self.assertEqual(lines[14], 'Traceback (most recent call last):\n')
- # Module canonical.launchpad.webapp.ftests.test_errorlog, ...
- # raise ArbitraryException(\'xyz\')
- self.assertEqual(lines[17], 'ArbitraryException: xyz\n')
def test_raising_with_request(self):
"""Test ErrorReportingUtility.raising() with a request"""
@@ -354,16 +315,11 @@
directlyProvides(request, IXMLRPCRequest)
request.getPositionalArguments = lambda: (1, 2)
utility = ErrorReportingUtility()
- now = datetime.datetime(2006, 04, 01, 00, 30, 00, tzinfo=UTC)
try:
raise ArbitraryException('xyz\nabc')
except ArbitraryException:
- utility.raising(sys.exc_info(), request, now=now)
- errorfile = os.path.join(
- utility.log_namer.output_dir(now), '01800.T1')
- self.assertTrue(os.path.exists(errorfile))
- lines = open(errorfile, 'r').readlines()
- self.assertEqual(lines[16], 'xmlrpc args=(1, 2)\n')
+ report = utility.raising(sys.exc_info(), request)
+ self.assertEqual(('xmlrpc args', '(1, 2)'), report['req_vars'][-1])
def test_raising_with_webservice_request(self):
# Test ErrorReportingUtility.raising() with a WebServiceRequest
@@ -405,120 +361,47 @@
def test_raising_for_script(self):
"""Test ErrorReportingUtility.raising with a ScriptRequest."""
utility = ErrorReportingUtility()
- now = datetime.datetime(2006, 04, 01, 00, 30, 00, tzinfo=UTC)
+ 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([
- ('name2', 'value2'), ('name1', 'value1'),
- ('name1', 'value3')], URL='https://launchpad.net/example')
- utility.raising(sys.exc_info(), request, now=now)
-
- errorfile = os.path.join(
- utility.log_namer.output_dir(now), '01800.T1')
- self.assertTrue(os.path.exists(errorfile))
- lines = open(errorfile, 'r').readlines()
-
- # the header
- self.assertEqual(lines[0], 'Oops-Id: OOPS-91T1\n')
- self.assertEqual(lines[1], 'Exception-Type: ArbitraryException\n')
- self.assertEqual(lines[2], 'Exception-Value: xyz abc\n')
- self.assertEqual(lines[3], 'Date: 2006-04-01T00:30:00+00:00\n')
- self.assertEqual(lines[4], 'Page-Id: \n')
- self.assertEqual(lines[5], 'Branch: %s\n' % versioninfo.branch_nick)
- self.assertEqual(lines[6], 'Revision: %s\n' % versioninfo.revno)
- self.assertEqual(lines[7], 'User: None\n')
- self.assertEqual(lines[8], 'URL: https://launchpad.net/example\n')
- self.assertEqual(lines[9], 'Duration: -1\n')
- self.assertEqual(lines[10], 'Informational: False\n')
- self.assertEqual(lines[11], '\n')
-
- # request vars
- self.assertEqual(lines[12], 'name1=value1\n')
- self.assertEqual(lines[13], 'name1=value3\n')
- self.assertEqual(lines[14], 'name2=value2\n')
- self.assertEqual(lines[15], '\n')
-
- # no database statements
- self.assertEqual(lines[16], '\n')
-
- # traceback
- self.assertEqual(lines[17], 'Traceback (most recent call last):\n')
- # Module canonical.launchpad.webapp.ftests.test_errorlog, ...
- # raise ArbitraryException(\'xyz\')
- self.assertEqual(lines[20], 'ArbitraryException: xyz\n')
-
- # verify that the oopsid was set on the request
- self.assertEqual(request.oopsid, 'OOPS-91T1')
+ request = ScriptRequest(req_vars, URL=url)
+ report = utility.raising(sys.exc_info(), request)
+
+ self.assertEqual(url, report['url'])
+ self.assertEqual(sorted(req_vars), report['req_vars'])
def test_raising_with_unprintable_exception(self):
- # Test ErrorReportingUtility.raising() with an unprintable exception.
- utility = ErrorReportingUtility()
- now = datetime.datetime(2006, 01, 01, 00, 30, 00, tzinfo=UTC)
-
class UnprintableException(Exception):
-
def __str__(self):
raise RuntimeError('arrgh')
__repr__ = __str__
- log = InstalledHandler('SiteError')
+ utility = ErrorReportingUtility()
try:
raise UnprintableException()
except UnprintableException:
- utility.raising(sys.exc_info(), now=now)
- log.uninstall()
-
- errorfile = os.path.join(
- utility.log_namer.output_dir(now), '01800.T1')
- self.assertTrue(os.path.exists(errorfile))
- lines = open(errorfile, 'r').readlines()
-
- # the header
- self.assertEqual(lines[0], 'Oops-Id: OOPS-1T1\n')
- self.assertEqual(lines[1], 'Exception-Type: UnprintableException\n')
- self.assertEqual(
- lines[2],
- 'Exception-Value: <unprintable UnprintableException object>\n')
- self.assertEqual(lines[3], 'Date: 2006-01-01T00:30:00+00:00\n')
- self.assertEqual(lines[4], 'Page-Id: \n')
- self.assertEqual(lines[5], 'Branch: %s\n' % versioninfo.branch_nick)
- self.assertEqual(lines[6], 'Revision: %s\n' % versioninfo.revno)
- self.assertEqual(lines[7], 'User: None\n')
- self.assertEqual(lines[8], 'URL: None\n')
- self.assertEqual(lines[9], 'Duration: -1\n')
- self.assertEqual(lines[10], 'Informational: False\n')
- self.assertEqual(lines[11], '\n')
-
- # no request vars
- self.assertEqual(lines[12], '\n')
-
- # no database statements
- self.assertEqual(lines[13], '\n')
-
- # traceback
- self.assertEqual(lines[14], 'Traceback (most recent call last):\n')
- # Module canonical.launchpad.webapp.ftests.test_errorlog, ...
- # raise UnprintableException()
- self.assertEqual(
- lines[17],
- 'UnprintableException:'
- ' <unprintable UnprintableException object>\n')
+ report = utility.raising(sys.exc_info())
+
+ unprintable = '<unprintable UnprintableException object>'
+ self.assertEqual(unprintable, report['value'])
+ self.assertIn( 'UnprintableException: ' + unprintable,
+ report['tb_text'])
def test_raising_unauthorized_without_request(self):
"""Unauthorized exceptions are logged when there's no request."""
utility = ErrorReportingUtility()
- now = datetime.datetime(2006, 04, 01, 00, 30, 00, tzinfo=UTC)
try:
raise Unauthorized('xyz')
except Unauthorized:
- utility.raising(sys.exc_info(), now=now)
- errorfile = os.path.join(
- utility.log_namer.output_dir(now), '01800.T1')
- self.failUnless(os.path.exists(errorfile))
+ oops = utility.raising(sys.exc_info())
+ self.assertNotEqual(None, oops)
def test_raising_unauthorized_without_principal(self):
"""Unauthorized exceptions are logged when the request has no
@@ -681,9 +564,8 @@
def test_raising_with_string_as_traceback(self):
# ErrorReportingUtility.raising() can be called with a string in the
# place of a traceback. This is useful when the original traceback
- # object is unavailable.
- utility = ErrorReportingUtility()
- now = datetime.datetime(2006, 04, 01, 00, 30, 00, tzinfo=UTC)
+ # object is unavailable - e.g. when logging a failure reported by a
+ # non-oops-enabled service.
try:
raise RuntimeError('hello')
@@ -694,76 +576,21 @@
# one generated by format_exc is sometimes passed instead.
exc_tb = traceback.format_exc()
- utility.raising((exc_type, exc_value, exc_tb), now=now)
- errorfile = os.path.join(
- utility.log_namer.output_dir(now), '01800.T1')
-
- self.assertTrue(os.path.exists(errorfile))
- lines = open(errorfile, 'r').readlines()
-
- # the header
- self.assertEqual(lines[0], 'Oops-Id: OOPS-91T1\n')
- self.assertEqual(lines[1], 'Exception-Type: RuntimeError\n')
- self.assertEqual(lines[2], 'Exception-Value: hello\n')
- self.assertEqual(lines[3], 'Date: 2006-04-01T00:30:00+00:00\n')
- self.assertEqual(lines[4], 'Page-Id: \n')
- self.assertEqual(lines[5], 'Branch: %s\n' % versioninfo.branch_nick)
- self.assertEqual(lines[6], 'Revision: %s\n' % versioninfo.revno)
- self.assertEqual(lines[7], 'User: None\n')
- self.assertEqual(lines[8], 'URL: None\n')
- self.assertEqual(lines[9], 'Duration: -1\n')
- self.assertEqual(lines[10], 'Informational: False\n')
- self.assertEqual(lines[11], '\n')
-
- # no request vars
- self.assertEqual(lines[12], '\n')
-
- # no database statements
- self.assertEqual(lines[13], '\n')
-
- # traceback
- self.assertEqual(''.join(lines[14:18]), exc_tb)
+ utility = ErrorReportingUtility()
+ report = utility.raising((exc_type, exc_value, exc_tb))
+ # traceback is what we supplied.
+ self.assertEqual(exc_tb, report['tb_text'])
def test_handling(self):
"""Test ErrorReportingUtility.handling()."""
utility = ErrorReportingUtility()
- now = datetime.datetime(2006, 04, 01, 00, 30, 00, tzinfo=UTC)
try:
raise ArbitraryException('xyz')
except ArbitraryException:
- utility.handling(sys.exc_info(), now=now)
-
- errorfile = os.path.join(
- utility.log_namer.output_dir(now), '01800.T1')
- self.assertTrue(os.path.exists(errorfile))
- lines = open(errorfile, 'r').readlines()
-
- # the header
- self.assertEqual(lines[0], 'Oops-Id: OOPS-91T1\n')
- self.assertEqual(lines[1], 'Exception-Type: ArbitraryException\n')
- self.assertEqual(lines[2], 'Exception-Value: xyz\n')
- self.assertEqual(lines[3], 'Date: 2006-04-01T00:30:00+00:00\n')
- self.assertEqual(lines[4], 'Page-Id: \n')
- self.assertEqual(lines[5], 'Branch: %s\n' % versioninfo.branch_nick)
- self.assertEqual(lines[6], 'Revision: %s\n' % versioninfo.revno)
- self.assertEqual(lines[7], 'User: None\n')
- self.assertEqual(lines[8], 'URL: None\n')
- self.assertEqual(lines[9], 'Duration: -1\n')
- self.assertEqual(lines[10], 'Informational: True\n')
- self.assertEqual(lines[11], '\n')
-
- # no request vars
- self.assertEqual(lines[12], '\n')
-
- # no database statements
- self.assertEqual(lines[13], '\n')
-
- # traceback
- self.assertEqual(lines[14], 'Traceback (most recent call last):\n')
- # Module canonical.launchpad.webapp.ftests.test_errorlog, ...
- # raise ArbitraryException(\'xyz\')
- self.assertEqual(lines[17], 'ArbitraryException: xyz\n')
+ report = utility.handling(sys.exc_info())
+
+ self.assertEqual(report['informational'], True)
def test_oopsMessage(self):
"""oopsMessage pushes and pops the messages."""
@@ -790,7 +617,7 @@
raise ArbitraryException('foo')
except ArbitraryException:
info = sys.exc_info()
- oops, _ = utility._makeErrorReport(info)
+ oops = utility._makeReport(info)
self.assertEqual(
[('<oops-message-0>', "{'a': 'b', 'c': 'd'}")],
oops['req_vars'])
@@ -804,7 +631,7 @@
raise ArbitraryException('foo')
except ArbitraryException:
info = sys.exc_info()
- oops, _ = utility._makeErrorReport(info, request)
+ oops = utility._makeReport(info, request)
self.assertEqual(
[('<oops-message-0>', "{'a': 'b'}"), ('c', 'd')],
oops['req_vars'])
@@ -837,7 +664,7 @@
raise ArbitraryException('foo')
except ArbitraryException:
info = sys.exc_info()
- oops, _ = utility._makeErrorReport(info)
+ oops = utility._makeReport(info)
self.assertEqual("SELECT '%s'", oops['db_statements'][0][3])
finally:
clear_request_started()
@@ -897,9 +724,9 @@
self.assertTrue(
report.tb_text.startswith('Traceback (most recent call last):\n'),
report.tb_text)
- self.assertEqual('', report.pageid)
- self.assertEqual('None', report.username)
- self.assertEqual('None', report.url)
+ self.assertEqual(None, report.pageid)
+ self.assertEqual(None, report.username)
+ self.assertEqual(None, report.url)
self.assertEqual([], report.req_vars)
self.assertEqual([], report.db_statements)
@@ -943,37 +770,45 @@
# A request originating from another site that generates a NotFound
# (404) is ignored (i.e., no OOPS is logged).
utility = ErrorReportingUtility()
- request = dict(HTTP_REFERER='example.com')
- self.assertTrue(utility._isIgnoredException('NotFound', request))
+ report = {'type': 'NotFound',
+ 'url': 'http://example.com',
+ 'req_vars': [('HTTP_REFERER', 'example.com')]}
+ self.assertTrue(utility._filterReport(report))
def test_onsite_404_not_ignored(self):
# A request originating from a local site that generates a NotFound
# (404) produces an OOPS.
utility = ErrorReportingUtility()
- request = dict(HTTP_REFERER='canonical.com')
- self.assertTrue(utility._isIgnoredException('NotFound', request))
+ report = {'type': 'NotFound',
+ 'url': 'http://example.com',
+ 'req_vars': [('HTTP_REFERER', 'http://launchpad.dev/')]}
+ self.assertFalse(utility._filterReport(report))
def test_404_without_referer_is_ignored(self):
# If a 404 is generated and there is no HTTP referer, we don't produce
# an OOPS.
utility = ErrorReportingUtility()
- request = dict()
- self.assertTrue(utility._isIgnoredException('NotFound', request))
+ report = {'type': 'NotFound',
+ 'url': 'http://example.com',
+ 'req_vars': []}
+ self.assertTrue(utility._filterReport(report))
+
+ def test_ignored_report_filtered(self):
+ utility = ErrorReportingUtility()
+ report = {'ignore': True}
+ self.assertTrue(utility._filterReport(report))
def test_marked_exception_is_ignored(self):
- # If an exception has been marked as ignorable, then it is ignored.
- utility = ErrorReportingUtility()
- exception = Exception()
- directlyProvides(exception, IUnloggedException)
- self.assertTrue(
- utility._isIgnoredException('RuntimeError', exception=exception))
-
- def test_unmarked_exception_generates_oops(self):
- # If an exception has not been marked as ignorable, then it is not.
- utility = ErrorReportingUtility()
- exception = Exception()
- self.assertFalse(
- utility._isIgnoredException('RuntimeError', exception=exception))
+ # If an exception has been marked as ignorable, then it is ignored in
+ # the report.
+ utility = ErrorReportingUtility()
+ try:
+ raise ArbitraryException('xyz\nabc')
+ except ArbitraryException:
+ exc_info = sys.exc_info()
+ directlyProvides(exc_info[1], IUnloggedException)
+ report = utility._makeReport(exc_info)
+ self.assertTrue(report['ignore'])
class TestWrappedParameterConverter(testtools.TestCase):
=== modified file 'lib/lp/services/job/runner.py'
--- lib/lp/services/job/runner.py 2011-08-12 21:38:36 +0000
+++ lib/lp/services/job/runner.py 2011-08-15 04:48:24 +0000
@@ -612,9 +612,8 @@
def main(self):
section = self.config_section
if (getattr(section, 'error_dir', None) is not None
- and getattr(section, 'oops_prefix', None) is not None
- and getattr(section, 'copy_to_zlog', None) is not None):
- # If the three variables are not set, we will let the error
+ and getattr(section, 'oops_prefix', None) is not None):
+ # If the two variables are not set, we will let the error
# utility default to using the [error_reports] config.
errorlog.globalErrorUtility.configure(self.config_name)
job_source = getUtility(self.source_interface)
=== modified file 'lib/lp/services/scripts/base.py'
--- lib/lp/services/scripts/base.py 2011-08-10 09:23:35 +0000
+++ lib/lp/services/scripts/base.py 2011-08-15 04:48:24 +0000
@@ -395,9 +395,6 @@
# Scripts can override this if they want.
globalErrorUtility.configure()
- # Scripts do not have a zlog.
- globalErrorUtility.copy_to_zlog = False
-
# WARN and higher log messages should generate OOPS reports.
# self.name is used instead of the name argument, since it may have
# have been overridden by command-line parameters or by
=== modified file 'lib/lp/services/twistedsupport/tests/test_loggingsupport.py'
--- lib/lp/services/twistedsupport/tests/test_loggingsupport.py 2011-08-11 03:15:16 +0000
+++ lib/lp/services/twistedsupport/tests/test_loggingsupport.py 2011-08-15 04:48:24 +0000
@@ -51,7 +51,6 @@
[error_reports]
oops_prefix: O
error_dir: %s
- copy_to_zlog: False
""" % self.temp_dir))
globalErrorUtility.configure()
self.log_stream = StringIO.StringIO()
=== modified file 'versions.cfg'
--- versions.cfg 2011-08-12 06:40:54 +0000
+++ versions.cfg 2011-08-15 04:48:24 +0000
@@ -49,7 +49,7 @@
mocker = 0.10.1
mozrunner = 1.3.4
oauth = 1.0
-oops = 0.0.2
+oops = 0.0.3
paramiko = 1.7.4
Paste = 1.7.2
PasteDeploy = 1.3.3
Follow ups