[Zope-CVS] CVS: Packages/tcpwatch - tcpwatch.py:1.1.2.1

Tres Seaver tseaver@zope.com
Sat, 24 May 2003 11:56:10 -0400


Update of /cvs-repository/Packages/tcpwatch
In directory cvs.zope.org:/tmp/cvs-serv31867

Modified Files:
      Tag: tseaver-logging-branch
	tcpwatch.py 
Log Message:


  - Add optional per-request logging:

    o Enable logging using the '-R' or '--record-directory' options, which
      take a path argument;  this argument is the directory in which the
      request, response, and error files will be written.  For each request,
      write one file containing the request headers and payload, one file
      containing response headers and payload, and (if tcpwatch detects an
      error), a file containing error messages.  The filenames use a
      prefix (see '--record-prefix'), and mangle the connection and
      transaction numbers.  E.g., the second transaction of the third
      connection would have its request headers and payload written to
      the file, 'watch_000000302.request';  the corresponding response data
      would be written to the file, 'watch_000000302.request'.

    o '--record-prefix' changes the prefix of generated files (defaults
      to 'watch').

    0 '--no-record-reponses' and '--no-record-errors' turn off response
      and error file creation.


=== Packages/tcpwatch/tcpwatch.py 1.1 => 1.1.2.1 ===
--- Packages/tcpwatch/tcpwatch.py:1.1	Sat Mar  2 16:18:21 2002
+++ Packages/tcpwatch/tcpwatch.py	Sat May 24 11:56:09 2003
@@ -79,6 +79,7 @@
     % VERSION)
 
 import sys
+import os
 import socket
 import asyncore
 import getopt
@@ -384,6 +385,73 @@
     def flush(self):
         sys.stdout.flush()
 
+class RecordingObserver (BasicObserver):
+    """Log request to a file.
+
+    o Filenames mangle connection and transaction numbers from the
+      ForwardedConnectionInfo passed as 'fci'.
+
+    o Decorates an underlying observer, created via the passed 'sub_factory'.
+
+    o Files are created in the supplied 'record_directory'.
+
+    o Unless suppressed, log response and error to corresponding files.
+    """
+    _ERROR_SOURCES = ('Server', 'Client')
+
+    # __implements__ = IConnectionObserver
+
+    def __init__(self, fci, sub_factory, record_directory,
+                 record_prefix='watch', record_responses=1, record_errors=1):
+        self._connection_number = fci.connection_number
+        self._transaction = fci.transaction
+        self._decorated = sub_factory(fci)
+        self._directory = record_directory
+        self._prefix = record_prefix
+        self._response = record_responses
+        self._errors = record_errors
+
+    def connected(self, from_client):
+        """See IConnectionObserver.
+        """
+        self._decorated.connected(from_client)
+
+    def received(self, data, from_client):
+        """See IConnectionObserver.
+        """
+        if from_client or self._response:
+            extension = from_client and 'request' or 'response'
+            file = self._openForAppend(extension=extension)
+            file.write(data)
+            file.close()
+        self._decorated.received(data, from_client)
+
+    def closed(self, from_client):
+        """See IConnectionObserver.
+        """
+        self._decorated.closed(from_client)
+
+    def error(self, from_client, type, value):
+        """See IConnectionObserver.
+        """
+        if self._errors:
+            file = self._openForAppend(extension='errors')
+            file.write('(%s) %s: %s\n' % (self._ERROR_SOURCES[from_client],
+                                          type, value))
+        self._decorated.error(from_client, type, value)
+
+    def _openForAppend(self, extension):
+        """Open a file with the given extension for appending.
+
+        o File should be in the directory indicated by self._directory.
+
+        o File should have a filename '<prefix>_<conn #>.<extension>'.
+        """
+        filename = '%s_%07d%02d.%s' % (self._prefix, self._connection_number, 
+                                       self._transaction, extension)
+        fqpath = os.path.join(self._directory, filename)
+        return open(fqpath, 'a')
+
 
 #############################################################################
 #
@@ -1198,6 +1266,20 @@
   -c Extra color (colorizes escaped characters)
   -r Show carriage returns (ASCII 13)
   -s Output to stdout instead of a Tkinter window
+
+Recording options:
+  -R (or --record-directory) <path>
+    Write recorded data to <path>.  By default, creates request and
+    response files for each request, and writes a corresponding error file
+    for any error detected by tcpwatch.  Requires either running as an
+    HTTP proxy ('-p'), or with splitting turned on ('-h').
+  --record-prefix=<prefix>
+    Use <prefix> as the file prefix for logged request / response / error
+    files (defaults to 'watch').
+  --no-record-responses
+    Suppress writing '.response' files.
+  --no-record-errors
+    Suppress writing '.error' files.
 """
     sys.exit()
 
@@ -1210,7 +1292,15 @@
 def main(args):
     global show_cr
 
-    optlist, extra = getopt.getopt(args, 'chL:np:rs', ['help', 'http'])
+    try:
+        optlist, extra = getopt.getopt(args, 'chL:np:rsR:',
+                                       ['help', 'http',
+                                        'record-directory=', 'record-prefix='
+                                        'no-record-responses',
+                                        'no-record-errors',
+                                       ])
+    except getopt.GetoptError, msg:
+        usageError(msg)
 
     fwd_params = []
     proxy_params = []
@@ -1218,6 +1308,11 @@
     show_config = 0
     split_http = 0
     colorized = 1
+    record_directory = None
+    record_prefix = 'watch'
+    record_responses = 1
+    record_errors = 1
+    recording = {}
 
     for option, value in optlist:
         if option == '--help':
@@ -1266,10 +1361,21 @@
                 usageError('-L requires 2, 3, or 4 colon-separated parameters')
             fwd_params.append(
                 (listen_host, listen_port, dest_host, dest_port))
+        elif option == '-R' or option == '--record-directory':
+            record_directory = value
+        elif option == '--record-prefix':
+            record_prefix = value
+        elif option == '--no-record-responses':
+            record_responses = 0
+        elif option == '--no-record-errors':
+            record_errors = 0
 
     if not fwd_params and not proxy_params:
         usage()
 
+    if record_directory and not split_http and not proxy_params:
+        usageError( 'Recording requires enabling either proxy or splitting.' )
+
     # Prepare the configuration display.
     config_info_lines = []
     title_lst = []
@@ -1291,6 +1397,17 @@
     if obs_factory is None:
         # If no observer factory has been specified, use Tkinter.
         obs_factory = setupTk(titlepart, config_info, colorized)
+
+    if record_directory:
+        def _decorateRecorder(fci, sub_factory=obs_factory,
+                              record_directory=record_directory,
+                              record_prefix=record_prefix,
+                              record_responses=record_responses,
+                              record_errors=record_errors):
+            return RecordingObserver(fci, sub_factory, record_directory,
+                                     record_prefix, record_responses,
+                                     record_errors)
+        obs_factory = _decorateRecorder
 
     chosen_factory = obs_factory
     if split_http: