Commit b2ea75a2 authored by Chris Withers's avatar Chris Withers

further changes to ConflictError logging:

- all conflict errors are counted and logged at info, as they were before Florent's change
- logging makes it clear where the conflict has been resolved and where it hasn't
- errors seen by the user are rendered with standard_error_message and are sent to the error_log which will likely copy them to the event log, depending on the users setup.
- also checking in a functional test for generating write conflict errors.

(I'll be committing to CHANGES.TXT shortly, I just wanted to keep the merges simpler)
parent 1eae46f4
## This script requires:
## - python2.4
## - Zope 3's zope.testbrowser package:
## http://www.zope.org/Members/benji_york/ZopeTestbrowser-0.9.0.tgz
##
## The just run:
## $python2.4 generate_conflicts.py
import base64
import string
import threading
import urllib2
from zope.testbrowser.browser import Browser
# create our browser
class AuthBrowser(Browser):
def addBasicAuth(self,username,password):
self.addHeader(
'Authorization',
'Basic '+base64.encodestring(username+':'+password).strip()
)
def open(self,uri,include_server=True):
if include_server:
uri = server+uri
return Browser.open(self,uri)
browser = AuthBrowser()
# constants
server = 'http://localhost:8080'
# the following user must be able to view the management screens
# and create file objects
username = 'username'
password = 'password'
browser.addBasicAuth(username,password)
threads = 10
filename = 'conflict.txt'
filesize = 10000
hits = 5
# delete the file if it's already there
browser.open('/manage_main')
if filename in [c.optionValue
for c in browser.getControl(name='ids:list').controls]:
browser.open('/manage_delObjects?ids:list='+filename)
# create it
browser.open('/manage_addFile?id='+filename)
# edit it, hopefully causing conflicts
data = 'X'*filesize
class EditThread(threading.Thread):
def __init__(self,i):
self.conflicts = 0
self.browser = AuthBrowser()
self.browser.handleErrors = False
self.browser.addBasicAuth(username,password)
threading.Thread.__init__(self,name=str(i))
def run(self):
for i in range(1,hits+1):
self.browser.open('/conflict.txt/manage_main')
self.browser.getControl(name='title').value='Test Title'
self.browser.getControl(name='filedata:text').value = data
try:
self.browser.getControl(name='manage_edit:method').click()
except urllib2.HTTPError,e:
# print e.read()
self.conflicts += 1
print "Thread %s - CONFLICT" % self.getName()
else:
print "Thread %s - EDIT" % self.getName()
thread_objects = []
for i in range(1,threads+1):
t = EditThread(i)
thread_objects.append(t)
t.start()
for t in thread_objects:
t.join()
total = 0
print
for t in thread_objects:
print "Thread %s - %i conflicts seen" % (t.getName(),t.conflicts)
total += t.conflicts
print
print "%i conflicts seen by browsers" % total
...@@ -18,9 +18,9 @@ from AccessControl.SecurityManagement import newSecurityManager ...@@ -18,9 +18,9 @@ from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.SecurityManagement import noSecurityManager from AccessControl.SecurityManagement import noSecurityManager
from Acquisition import aq_acquire from Acquisition import aq_acquire
from App.config import getConfiguration from App.config import getConfiguration
from time import asctime
from types import StringType, ListType from types import StringType, ListType
from zExceptions import Unauthorized from zExceptions import Unauthorized
from zLOG import LOG, ERROR, WARNING, INFO, BLATHER, log_time
from ZODB.POSException import ConflictError from ZODB.POSException import ConflictError
import transaction import transaction
import AccessControl.User import AccessControl.User
...@@ -28,6 +28,7 @@ import App.FindHomes ...@@ -28,6 +28,7 @@ import App.FindHomes
import ExtensionClass import ExtensionClass
import Globals import Globals
import imp import imp
import logging
import OFS.Application import OFS.Application
import os import os
import sys import sys
...@@ -103,7 +104,7 @@ def startup(): ...@@ -103,7 +104,7 @@ def startup():
noSecurityManager() noSecurityManager()
global startup_time global startup_time
startup_time = log_time() startup_time = asctime()
Zope2.zpublisher_transactions_manager = TransactionsManager() Zope2.zpublisher_transactions_manager = TransactionsManager()
Zope2.zpublisher_exception_hook = zpublisher_exception_hook Zope2.zpublisher_exception_hook = zpublisher_exception_hook
...@@ -132,8 +133,13 @@ class RequestContainer(ExtensionClass.Base): ...@@ -132,8 +133,13 @@ class RequestContainer(ExtensionClass.Base):
def __init__(self,r): self.REQUEST=r def __init__(self,r): self.REQUEST=r
conflict_errors = 0 conflict_errors = 0
unresolved_conflict_errors = 0
conflict_logger = logging.getLogger('ZODB.Conflict')
def zpublisher_exception_hook(published, REQUEST, t, v, traceback): def zpublisher_exception_hook(published, REQUEST, t, v, traceback):
global unresolved_conflict_errors
global conflict_errors
try: try:
if isinstance(t, StringType): if isinstance(t, StringType):
if t.lower() in ('unauthorized', 'redirect'): if t.lower() in ('unauthorized', 'redirect'):
...@@ -142,25 +148,31 @@ def zpublisher_exception_hook(published, REQUEST, t, v, traceback): ...@@ -142,25 +148,31 @@ def zpublisher_exception_hook(published, REQUEST, t, v, traceback):
if t is SystemExit: if t is SystemExit:
raise raise
if issubclass(t, ConflictError): if issubclass(t, ConflictError):
global conflict_errors
conflict_errors = conflict_errors + 1 conflict_errors = conflict_errors + 1
method_name = REQUEST.get('PATH_INFO', '') # This logs _all_ conflict errors
LOG('ZODB', BLATHER, "%s at %s: %s" conflict_logger.info(
" (%s conflicts since startup at %s)" '%s at %s (%i conflicts, of which %i'
% (v.__class__.__name__, method_name, v, ' were unresolved, since startup at %s)',
conflict_errors, startup_time), v,
error=(t, v, traceback)) REQUEST.get('PATH_INFO', '<unknown>'),
conflict_errors,
unresolved_conflict_errors,
startup_time
)
# This debug logging really doesn't help a lot...
conflict_logger.debug('Conflict traceback',exc_info=True)
raise ZPublisher.Retry(t, v, traceback) raise ZPublisher.Retry(t, v, traceback)
if t is ZPublisher.Retry: if t is ZPublisher.Retry:
# An exception that can't be retried anymore try:
# Retrieve the original exception v.reraise()
try: v.reraise() except:
except: t, v, traceback = sys.exc_info() # we catch the re-raised exception so that it gets
# Log it as ERROR # stored in the error log and gets rendered with
method_name = REQUEST.get('PATH_INFO', '') # standard_error_message
LOG('Publisher', ERROR, "Unhandled %s at %s: %s" t, v, traceback = sys.exc_info()
% (v.__class__.__name__, method_name, v)) if issubclass(t, ConflictError):
# Then fall through to display the error to the user # ouch, a user saw this conflict error :-(
unresolved_conflict_errors += 1
try: try:
log = aq_acquire(published, '__error_log__', containment=1) log = aq_acquire(published, '__error_log__', containment=1)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment