[Erp5-report] r37974 leonardo - in /erp5/trunk/utils/Products.LongRequestLogger: ./ Product...
nobody at svn.erp5.org
nobody at svn.erp5.org
Tue Aug 24 13:45:35 CEST 2010
Author: leonardo
Date: Tue Aug 24 13:45:34 2010
New Revision: 37974
URL: http://svn.erp5.org?rev=37974&view=rev
Log:
Initial import of the long zope request logging product
Added:
erp5/trunk/utils/Products.LongRequestLogger/
erp5/trunk/utils/Products.LongRequestLogger/Products/
erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/
erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/__init__.py
erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/dumper.py
erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/monitor.py
erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/patch.py
erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/
erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/__init__.py
erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/common.py
erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/testLongRequestLogger.py
erp5/trunk/utils/Products.LongRequestLogger/Products/__init__.py
erp5/trunk/utils/Products.LongRequestLogger/README.txt
erp5/trunk/utils/Products.LongRequestLogger/docs/
erp5/trunk/utils/Products.LongRequestLogger/docs/HISTORY.txt
erp5/trunk/utils/Products.LongRequestLogger/setup.py
Added: erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/__init__.py
URL: http://svn.erp5.org/erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/__init__.py?rev=37974&view=auto
==============================================================================
--- erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/__init__.py (added)
+++ erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/__init__.py [utf8] Tue Aug 24 13:45:34 2010
@@ -0,0 +1,33 @@
+##############################################################################
+#
+# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
+# Leonardo Rochael Almeida <leonardo at nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly advised to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+
+def initialize(context):
+ from Products.LongRequestLogger.patch import do_patch
+ do_patch()
+
Added: erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/dumper.py
URL: http://svn.erp5.org/erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/dumper.py?rev=37974&view=auto
==============================================================================
--- erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/dumper.py (added)
+++ erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/dumper.py [utf8] Tue Aug 24 13:45:34 2010
@@ -0,0 +1,113 @@
+##############################################################################
+#
+# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
+# Leonardo Rochael Almeida <leonardo at nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly advised to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import traceback
+from cStringIO import StringIO
+from logging import getLogger
+from thread import get_ident
+import time
+from pprint import pformat
+
+try:
+ from sys import _current_frames
+except ImportError:
+ # Python 2.4 and older
+ from threadframe import dict as _current_frames
+
+# we might want to change this later to something more specific
+logger_name = __name__
+
+log = getLogger(logger_name)
+
+REQUEST_FORMAT = """
+request:
+URL: %(method)s %(url)s
+form: %(form)s
+other: %(other)s
+""".strip()
+
+class Dumper(object):
+
+ def __init__(self, thread_id=None):
+ if thread_id is None:
+ # assume we're being called by the thread that wants to be
+ # monitored
+ thread_id = get_ident()
+ self.thread_id = thread_id
+ self.start = time.time()
+
+ def format_request(self, request):
+ if request is None:
+ return "[No request]"
+ url = request.getURL()
+ if request.get('QUERY_STRING'):
+ url += '?' + request['QUERY_STRING']
+ retries = request.retry_count
+ method = request['REQUEST_METHOD']
+ form = pformat(request.form)
+ other = pformat(request.other)
+ return REQUEST_FORMAT % locals()
+
+ def extract_request(self, frame):
+ # import locally to get even monkey-patched results
+ from ZPublisher.Publish import call_object
+ func_code = call_object.func_code #@UndefinedVariable
+ while frame is not None:
+ code = frame.f_code
+ if (code is func_code):
+ request = frame.f_locals.get('request')
+ return request
+ frame = frame.f_back
+
+ def extract_request_info(self, frame):
+ request = self.extract_request(frame)
+ return self.format_request(request)
+
+ def get_top_thread_frame(self):
+ return _current_frames()[self.thread_id]
+
+ def get_thread_info(self, frame):
+ request_info = self.extract_request_info(frame)
+ now = time.time()
+ runtime = now - self.start
+ info = ("Thread %s: Running for %.1f secs; %s" %
+ (self.thread_id, runtime, request_info))
+ return info
+
+ def format_thread(self):
+ frame = self.get_top_thread_frame()
+ output = StringIO()
+ thread_info = self.get_thread_info(frame)
+ print >> output, thread_info
+ print >> output, "Traceback:"
+ traceback.print_stack(frame, file=output)
+ del frame
+ return output.getvalue()
+
+ def __call__(self):
+ log.warning(self.format_thread())
Added: erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/monitor.py
URL: http://svn.erp5.org/erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/monitor.py?rev=37974&view=auto
==============================================================================
--- erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/monitor.py (added)
+++ erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/monitor.py [utf8] Tue Aug 24 13:45:34 2010
@@ -0,0 +1,83 @@
+##############################################################################
+#
+# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
+# Leonardo Rochael Almeida <leonardo at nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly advised to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+from threading import Thread
+from threading import Condition
+from Products.LongRequestLogger.dumper import Dumper
+
+DEFAULT_TIMEOUT = 2
+DEFAULT_INTERVAL = 1
+
+class Monitor(Thread):
+ """Logs the stack-trace of a thread until it's stopped
+
+ m = Monitor(thread.get_ident(), timeout=5, interval=2)
+
+ Wait 5 seconds before dumping the stack-trace of the identified thread
+ every 2 seconds.
+
+ m.stop()
+
+ Stop the monitoring, whether timed-out or not
+ """
+
+ def __init__(self,
+ thread_id=None,
+ timeout=DEFAULT_TIMEOUT,
+ interval=DEFAULT_INTERVAL):
+ Thread.__init__(self)
+ self.timeout = timeout
+ self.interval = interval
+ self.dumper = Dumper(thread_id)
+
+ self.running = True
+ self.running_condition = Condition()
+ self.start()
+
+ def stop(self):
+ """Stop monitoring the other thread"""
+ # this function is called by the other thread, when it wants to stop
+ # being monitored
+ self.running_condition.acquire()
+ try:
+ self.running = False
+ self.running_condition.notify()
+ finally:
+ self.running_condition.release()
+ self.join()
+
+ def run(self):
+ self.running_condition.acquire()
+ self.running_condition.wait(self.timeout)
+ # If the other thread is still running by now, it's time to monitor it
+ try:
+ while self.running:
+ self.dumper()
+ self.running_condition.wait(self.interval)
+ finally:
+ self.running_condition.release()
Added: erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/patch.py
URL: http://svn.erp5.org/erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/patch.py?rev=37974&view=auto
==============================================================================
--- erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/patch.py (added)
+++ erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/patch.py [utf8] Tue Aug 24 13:45:34 2010
@@ -0,0 +1,55 @@
+##############################################################################
+#
+# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
+# Leonardo Rochael Almeida <leonardo at nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly advised to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import sys
+from logging import getLogger
+from Products.LongRequestLogger.monitor import Monitor
+
+log = getLogger(__name__)
+
+def wrapper(*args, **kw):
+ m = Monitor()
+ try:
+ result = wrapper.original(*args, **kw)
+ return result
+ finally:
+ m.stop()
+
+def do_patch():
+ from ZPublisher.Publish import publish_module_standard as original
+ wrapper.original = original
+ log.info('patching %s.%s' % (wrapper.original.__module__,
+ wrapper.original.__name__))
+ setattr(sys.modules[wrapper.original.__module__],
+ wrapper.original.__name__,
+ wrapper)
+
+def do_unpatch():
+ setattr(sys.modules[wrapper.original.__module__],
+ wrapper.original.__name__,
+ wrapper.original)
Added: erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/__init__.py
URL: http://svn.erp5.org/erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/__init__.py?rev=37974&view=auto
==============================================================================
--- erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/__init__.py (added)
+++ erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/__init__.py [utf8] Tue Aug 24 13:45:34 2010
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
+# Leonardo Rochael Almeida <leonardo at nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly advised to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
Added: erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/common.py
URL: http://svn.erp5.org/erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/common.py?rev=37974&view=auto
==============================================================================
--- erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/common.py (added)
+++ erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/common.py [utf8] Tue Aug 24 13:45:34 2010
@@ -0,0 +1,56 @@
+##############################################################################
+#
+# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
+# Leonardo Rochael Almeida <leonardo at nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly advised to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import time
+
+class Sleeper(object):
+ """This class exists solely to inflate the stack trace, and to be in a
+ file where the stack trace won't be affected editing of the test file that
+ uses it
+ """
+
+ def __init__(self, interval):
+ self.interval = interval
+
+ def sleep(self):
+ self._sleep1()
+
+ def _sleep1(self):
+ self._sleep2()
+
+ def _sleep2(self):
+ time.sleep(self.interval)
+
+class App(object):
+
+ def __call__(self, interval):
+ Sleeper(interval).sleep()
+ return "OK"
+
+# Enable this module to be published with ZPublisher.Publish.publish_module()
+bobo_application = App()
Added: erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/testLongRequestLogger.py
URL: http://svn.erp5.org/erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/testLongRequestLogger.py?rev=37974&view=auto
==============================================================================
--- erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/testLongRequestLogger.py (added)
+++ erp5/trunk/utils/Products.LongRequestLogger/Products/LongRequestLogger/tests/testLongRequestLogger.py [utf8] Tue Aug 24 13:45:34 2010
@@ -0,0 +1,313 @@
+##############################################################################
+#
+# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved.
+# Leonardo Rochael Almeida <leonardo at nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly advised to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+##############################################################################
+
+import unittest
+from cStringIO import StringIO
+from doctest import OutputChecker
+from doctest import REPORT_UDIFF, NORMALIZE_WHITESPACE, ELLIPSIS
+
+from Products.LongRequestLogger.tests.common import Sleeper
+
+class SimpleOutputChecker(OutputChecker):
+ # for certain inputs the doctest output checker is much more convenient
+ # than manually munging assertions
+
+ optionflags = REPORT_UDIFF | NORMALIZE_WHITESPACE | ELLIPSIS
+
+ def __init__(self, want, optionflags=None):
+ self.want = want
+ if optionflags is not None:
+ self.optionflags = optionflags
+
+ def __call__(self, got):
+ assert self.check_output(self.want, got, self.optionflags), \
+ self.output_difference(self, # pretend we're a doctest.Example
+ got, self.optionflags)
+
+check_dump = SimpleOutputChecker('''
+Thread ...: Running for 0.0 secs; [No request]
+<BLANKLINE>
+Traceback:
+...
+ File ".../LongRequestLogger/dumper.py", line ..., in format_thread
+ traceback.print_stack(frame, file=output)
+ File ".../LongRequestLogger/dumper.py", line ..., in get_top_thread_frame
+ return _current_frames()[self.thread_id]
+''')
+
+check_log = SimpleOutputChecker('''
+Products.LongRequestLogger.dumper WARNING
+ Thread ...: Running for 0.0 secs; [No request]
+Traceback:
+...
+ File ".../LongRequestLogger/dumper.py", line ..., in __call__
+ log.warning(self.format_thread())
+ File ".../LongRequestLogger/dumper.py", line ..., in format_thread
+ traceback.print_stack(frame, file=output)
+ File ".../LongRequestLogger/dumper.py", line ..., in get_top_thread_frame
+ return _current_frames()[self.thread_id]
+''')
+
+check_monitor_log = SimpleOutputChecker('''
+Products.LongRequestLogger.dumper WARNING
+ Thread ...: Running for 2.0 secs; [No request]
+Traceback:
+...
+ File ".../LongRequestLogger/tests/common.py", line 41, in sleep
+ self._sleep1()
+ File ".../LongRequestLogger/tests/common.py", line 44, in _sleep1
+ self._sleep2()
+ File ".../LongRequestLogger/tests/common.py", line 47, in _sleep2
+ time.sleep(self.interval)
+''')
+
+check_monitor_2_intervals_log = SimpleOutputChecker('''
+Products.LongRequestLogger.dumper WARNING
+ Thread ...: Running for 2.0 secs; [No request]
+Traceback:
+...
+ File ".../LongRequestLogger/tests/common.py", line 41, in sleep
+ self._sleep1()
+ File ".../LongRequestLogger/tests/common.py", line 44, in _sleep1
+ self._sleep2()
+ File ".../LongRequestLogger/tests/common.py", line 47, in _sleep2
+ time.sleep(self.interval)
+Products.LongRequestLogger.dumper WARNING
+ Thread ...: Running for 3.0 secs; [No request]
+Traceback:
+...
+ File ".../LongRequestLogger/tests/common.py", line 41, in sleep
+ self._sleep1()
+ File ".../LongRequestLogger/tests/common.py", line 44, in _sleep1
+ self._sleep2()
+ File ".../LongRequestLogger/tests/common.py", line 47, in _sleep2
+ time.sleep(self.interval)
+Products.LongRequestLogger.dumper WARNING
+ Thread ...: Running for 4.0 secs; [No request]
+Traceback:
+...
+ File ".../LongRequestLogger/tests/common.py", line 41, in sleep
+ self._sleep1()
+ File ".../LongRequestLogger/tests/common.py", line 44, in _sleep1
+ self._sleep2()
+ File ".../LongRequestLogger/tests/common.py", line 47, in _sleep2
+ time.sleep(self.interval)
+''')
+
+check_publishing_1_interval_log = SimpleOutputChecker('''
+Products.LongRequestLogger.dumper WARNING
+ Thread ...: Running for 2.0 secs; request:
+URL: GET http://localhost
+form: {}
+other: {'ACTUAL_URL': 'http://localhost',
+ 'PARENTS': [],
+ 'PUBLISHED': <Products.LongRequestLogger.tests.common.App object at 0x...>,
+ 'RESPONSE': HTTPResponse(''),
+ 'SERVER_URL': 'http://localhost',
+ 'TraversalRequestNameStack': [],
+ 'URL': 'http://localhost',
+ 'interval': 3.5,
+ 'method': 'GET'}
+Traceback:
+...
+ File ".../LongRequestLogger/patch.py", line ..., in wrapper
+ result = wrapper.original(*args, **kw)
+ File ".../ZPublisher/Publish.py", line ..., in publish_module_standard
+ response = publish(request, module_name, after_list, debug=debug)
+...
+ File ".../LongRequestLogger/tests/common.py", line 52, in __call__
+ Sleeper(interval).sleep()
+ File ".../LongRequestLogger/tests/common.py", line 41, in sleep
+ self._sleep1()
+ File ".../LongRequestLogger/tests/common.py", line 44, in _sleep1
+ self._sleep2()
+ File ".../LongRequestLogger/tests/common.py", line 47, in _sleep2
+ time.sleep(self.interval)
+Products.LongRequestLogger.dumper WARNING
+ Thread ...: Running for 3.0 secs; request:
+URL: GET http://localhost
+form: {}
+other: {'ACTUAL_URL': 'http://localhost',
+ 'PARENTS': [],
+ 'PUBLISHED': <Products.LongRequestLogger.tests.common.App object at 0x...>,
+ 'RESPONSE': HTTPResponse(''),
+ 'SERVER_URL': 'http://localhost',
+ 'TraversalRequestNameStack': [],
+ 'URL': 'http://localhost',
+ 'interval': 3.5,
+ 'method': 'GET'}
+Traceback:
+...
+ File ".../LongRequestLogger/patch.py", line ..., in wrapper
+ result = wrapper.original(*args, **kw)
+ File ".../ZPublisher/Publish.py", line ..., in publish_module_standard
+ response = publish(request, module_name, after_list, debug=debug)
+...
+ File ".../LongRequestLogger/tests/common.py", line 52, in __call__
+ Sleeper(interval).sleep()
+ File ".../LongRequestLogger/tests/common.py", line 41, in sleep
+ self._sleep1()
+ File ".../LongRequestLogger/tests/common.py", line 44, in _sleep1
+ self._sleep2()
+ File ".../LongRequestLogger/tests/common.py", line 47, in _sleep2
+ time.sleep(self.interval)
+''')
+
+check_request_formating = SimpleOutputChecker('''
+request:
+URL: GET http://localhost/foo/bar
+form: {}
+other: {'RESPONSE': HTTPResponse(''),
+ 'SERVER_URL': 'http://localhost',
+ 'URL': 'http://localhost/foo/bar',
+ 'method': 'GET'}
+''')
+
+class TestLongRequestLogger(unittest.TestCase):
+
+ def setUp(self):
+ from Products.LongRequestLogger.patch import do_patch
+ from Products.LongRequestLogger.dumper import logger_name
+ from zope.testing.loggingsupport import InstalledHandler
+ do_patch()
+ self.loghandler = InstalledHandler(logger_name)
+ self.requests = []
+
+ def makeRequest(self, path='/', **kw):
+ # create fake request and response for convenience
+ from ZPublisher.HTTPRequest import HTTPRequest
+ from ZPublisher.HTTPResponse import HTTPResponse
+ stdin = StringIO()
+ stdout = StringIO()
+ # minimal environment needed
+ env = dict(SERVER_NAME='localhost',
+ SERVER_PORT='80',
+ REQUEST_METHOD='GET',
+ SCRIPT_NAME=path)
+ response = HTTPResponse(stdout=stdout)
+ request = HTTPRequest(stdin, env, response)
+ self.requests.append(request)
+ return request
+
+ def tearDown(self):
+ from Products.LongRequestLogger.patch import do_unpatch
+ do_unpatch()
+ self.loghandler.uninstall()
+ for request in self.requests:
+ request.response.stdout.close()
+ request.clear()
+
+ def testDumperFormat(self):
+ from Products.LongRequestLogger.dumper import Dumper
+ dumper = Dumper()
+ check_dump(dumper.format_thread())
+
+ def testDumperRequestExtraction(self):
+ # The dumper extract requests by looking for the frame that contains
+ # call_object and then looking for the 'request' variable inside it
+ from ZPublisher.Publish import call_object
+ from Products.LongRequestLogger.dumper import Dumper
+ def callable():
+ dumper = Dumper()
+ return dumper.extract_request(dumper.get_top_thread_frame())
+
+ request = self.makeRequest('/foo')
+ retrieved_request = call_object(callable, (), request)
+ self.assertTrue(request is retrieved_request)
+
+ def testRequestFormating(self):
+ from Products.LongRequestLogger.dumper import Dumper
+ dumper = Dumper()
+ request = self.makeRequest('/foo/bar')
+ check_request_formating(dumper.format_request(request))
+
+ def testDumperLog(self):
+ from Products.LongRequestLogger.dumper import Dumper
+ dumper = Dumper()
+ # check the dumper will log what we expect when called
+ dumper()
+ check_log(str(self.loghandler))
+
+ def testMonitorStopBeforeTimeout(self):
+ from Products.LongRequestLogger.monitor import Monitor
+ m = Monitor()
+ self.assertTrue(m.isAlive())
+ self.assertTrue(m.running)
+ m.stop()
+ self.assertFalse(m.running)
+ self.assertFalse(m.isAlive())
+ # unless this test is so slow that there were 2 seconds interval
+ # between starting the monitor and stopping it, there should be no
+ # logged messages
+ self.assertFalse(self.loghandler.records)
+
+ def testMonitorStopAfterTimeout(self):
+ from Products.LongRequestLogger.monitor import Monitor
+ m = Monitor()
+ s = Sleeper(m.timeout + 0.5)
+ # sleep a little more than the timeout to be on the safe side
+ s.sleep()
+ m.stop()
+ check_monitor_log(str(self.loghandler))
+
+ def testMonitorStopAfterTimeoutAndTwoIntervals(self):
+ from Products.LongRequestLogger.monitor import Monitor
+ m = Monitor()
+ s = Sleeper(m.timeout + 2 * m.interval + 0.5)
+ # sleep a little more than the timeout + intervals to be on the safe
+ # side
+ s.sleep()
+ m.stop()
+ check_monitor_2_intervals_log(str(self.loghandler))
+
+ def testIsPatched(self):
+ import ZPublisher.Publish
+ import Products.LongRequestLogger
+ self.assertEquals(
+ ZPublisher.Publish.publish_module_standard,
+ Products.LongRequestLogger.patch.wrapper
+ )
+
+ def testPublish(self):
+ from ZPublisher.Publish import publish_module_standard
+ # Before publishing, there should be no slow query records.
+ self.assertFalse(self.loghandler.records)
+ # Request taking (timeout + interval + margin) 3.5 seconds...
+ request = self.makeRequest('/', interval=3.5)
+ request['interval'] = 3.5
+ publish_module_standard('Products.LongRequestLogger.tests.common',
+ request=request,
+ response=request.response,
+ debug=True)
+ # ...should generate query log records like these
+ check_publishing_1_interval_log(str(self.loghandler))
+
+def test_suite():
+ return unittest.TestSuite((
+ unittest.makeSuite(TestLongRequestLogger),
+ ))
Added: erp5/trunk/utils/Products.LongRequestLogger/Products/__init__.py
URL: http://svn.erp5.org/erp5/trunk/utils/Products.LongRequestLogger/Products/__init__.py?rev=37974&view=auto
==============================================================================
--- erp5/trunk/utils/Products.LongRequestLogger/Products/__init__.py (added)
+++ erp5/trunk/utils/Products.LongRequestLogger/Products/__init__.py [utf8] Tue Aug 24 13:45:34 2010
@@ -0,0 +1,6 @@
+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+try:
+ __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+ from pkgutil import extend_path
+ __path__ = extend_path(__path__, __name__)
Added: erp5/trunk/utils/Products.LongRequestLogger/README.txt
URL: http://svn.erp5.org/erp5/trunk/utils/Products.LongRequestLogger/README.txt?rev=37974&view=auto
==============================================================================
--- erp5/trunk/utils/Products.LongRequestLogger/README.txt (added)
+++ erp5/trunk/utils/Products.LongRequestLogger/README.txt [utf8] Tue Aug 24 13:45:34 2010
@@ -0,0 +1,4 @@
+Introduction
+============
+
+
Added: erp5/trunk/utils/Products.LongRequestLogger/docs/HISTORY.txt
URL: http://svn.erp5.org/erp5/trunk/utils/Products.LongRequestLogger/docs/HISTORY.txt?rev=37974&view=auto
==============================================================================
--- erp5/trunk/utils/Products.LongRequestLogger/docs/HISTORY.txt (added)
+++ erp5/trunk/utils/Products.LongRequestLogger/docs/HISTORY.txt [utf8] Tue Aug 24 13:45:34 2010
@@ -0,0 +1,7 @@
+Changelog
+=========
+
+0.1dev (unreleased)
+-------------------
+
+- Initial release
Added: erp5/trunk/utils/Products.LongRequestLogger/setup.py
URL: http://svn.erp5.org/erp5/trunk/utils/Products.LongRequestLogger/setup.py?rev=37974&view=auto
==============================================================================
--- erp5/trunk/utils/Products.LongRequestLogger/setup.py (added)
+++ erp5/trunk/utils/Products.LongRequestLogger/setup.py [utf8] Tue Aug 24 13:45:34 2010
@@ -0,0 +1,37 @@
+from setuptools import setup
+from setuptools import find_packages
+import os
+
+version = '0.1'
+
+setup(name='Products.LongRequestLogger',
+ version=version,
+ description="Dumps stack trace of long running Zope2 requests",
+ long_description=open("README.txt").read() + "\n" +
+ open(os.path.join("docs", "HISTORY.txt")).read(),
+ classifiers=[
+ "Programming Language :: Python",
+ "Framework :: Zope2",
+ "Intended Audience :: System Administrators",
+ ],
+ keywords='',
+ author='Nexedi SA',
+ author_email='erp5-dev at erp5.org',
+ url='http://www.erp5.org/',
+ license='GPL',
+ packages=find_packages(exclude=['ez_setup']),
+ namespace_packages=['Products'],
+ include_package_data=True,
+ zip_safe=False,
+ install_requires=[
+ 'setuptools',
+ ],
+ extras_require=dict(
+ # With Python versions < 2.5, we need the threadframe module
+ python24=['threadframe'],
+ # we need to work with Zope 2.8 buildouts that might not provide a
+ # Zope2 egg (fake or otherwise). So we declare the full dependency
+ # here.
+ standalone=['Zope2'],
+ ),
+)
More information about the Erp5-report
mailing list