[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