Skip to content

Commit 1dedfee

Browse files
Added optional recording of requests in debug mode.
1 parent d9216be commit 1dedfee

File tree

2 files changed

+145
-2
lines changed

2 files changed

+145
-2
lines changed

docs/release-notes/version-4.4.1.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,6 @@ will run Apache when handling requests. This is necessary otherwise the
7474
Apache child worker process will not be able to connect to the listener
7575
socket for the mod_wsgi daemon process to proxy the request to the WSGI
7676
application.
77+
78+
6. Added a ``--enable-recorder`` option for enabling request recording when
79+
also using debug mode.

src/server/__init__.py

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
import pwd
1414
import grp
1515
import re
16+
import pprint
17+
import time
18+
import traceback
1619

1720
try:
1821
import Queue as queue
@@ -878,12 +881,115 @@ def close(self):
878881
self.debug_exception()
879882
raise
880883

884+
class RequestRecorder(object):
885+
886+
def __init__(self, application, savedir):
887+
self.application = application
888+
self.savedir = savedir
889+
self.lock = threading.Lock()
890+
self.pid = os.getpid()
891+
self.count = 0
892+
893+
def __call__(self, environ, start_response):
894+
with self.lock:
895+
self.count += 1
896+
count = self.count
897+
898+
key = "%s-%s-%s" % (int(time.time()*1000000), self.pid, count)
899+
900+
iheaders = os.path.join(self.savedir, key + ".iheaders")
901+
iheaders_fp = open(iheaders, 'w')
902+
903+
icontent = os.path.join(self.savedir, key + ".icontent")
904+
icontent_fp = open(icontent, 'w+b')
905+
906+
oheaders = os.path.join(self.savedir, key + ".oheaders")
907+
oheaders_fp = open(oheaders, 'w')
908+
909+
ocontent = os.path.join(self.savedir, key + ".ocontent")
910+
ocontent_fp = open(ocontent, 'w+b')
911+
912+
oaexcept = os.path.join(self.savedir, key + ".oaexcept")
913+
oaexcept_fp = open(oaexcept, 'w')
914+
915+
orexcept = os.path.join(self.savedir, key + ".orexcept")
916+
orexcept_fp = open(orexcept, 'w')
917+
918+
ofexcept = os.path.join(self.savedir, key + ".ofexcept")
919+
ofexcept_fp = open(ofexcept, 'w')
920+
921+
errors = environ['wsgi.errors']
922+
pprint.pprint(environ, stream=iheaders_fp)
923+
iheaders_fp.close()
924+
925+
input = environ['wsgi.input']
926+
927+
data = input.read(8192)
928+
929+
while data:
930+
icontent_fp.write(data)
931+
data = input.read(8192)
932+
933+
icontent_fp.flush()
934+
icontent_fp.seek(0, os.SEEK_SET)
935+
936+
environ['wsgi.input'] = icontent_fp
937+
938+
def _start_response(status, response_headers, *args):
939+
pprint.pprint(((status, response_headers)+args),
940+
stream=oheaders_fp)
941+
942+
_write = start_response(status, response_headers, *args)
943+
944+
def write(self, data):
945+
ocontent_fp.write(data)
946+
ocontent_fp.flush()
947+
return _write(data)
948+
949+
return write
950+
951+
try:
952+
try:
953+
result = self.application(environ, _start_response)
954+
955+
except:
956+
traceback.print_exception(*sys.exc_info(), file=oaexcept_fp)
957+
raise
958+
959+
try:
960+
for data in result:
961+
ocontent_fp.write(data)
962+
ocontent_fp.flush()
963+
yield data
964+
965+
except:
966+
traceback.print_exception(*sys.exc_info(), file=orexcept_fp)
967+
raise
968+
969+
finally:
970+
try:
971+
if hasattr(result, 'close'):
972+
result.close()
973+
974+
except:
975+
traceback.print_exception(*sys.exc_info(),
976+
file=ofexcept_fp)
977+
raise
978+
979+
finally:
980+
oheaders_fp.close()
981+
ocontent_fp.close()
982+
oaexcept_fp.close()
983+
orexcept_fp.close()
984+
ofexcept_fp.close()
985+
881986
class ApplicationHandler(object):
882987

883988
def __init__(self, entry_point, application_type='script',
884989
callable_object='application', mount_point='/',
885990
with_newrelic_agent=False, debug_mode=False,
886-
enable_debugger=False, debugger_startup=False):
991+
enable_debugger=False, debugger_startup=False,
992+
enable_recorder=False, recorder_directory=None):
887993

888994
self.entry_point = entry_point
889995
self.application_type = application_type
@@ -931,6 +1037,9 @@ def __init__(self, entry_point, application_type='script',
9311037
if enable_debugger:
9321038
self.setup_debugger(debugger_startup)
9331039

1040+
if enable_recorder:
1041+
self.setup_recorder(recorder_directory)
1042+
9341043
def setup_newrelic_agent(self):
9351044
import newrelic.agent
9361045

@@ -950,6 +1059,9 @@ def setup_newrelic_agent(self):
9501059
def setup_debugger(self, startup):
9511060
self.application = PostMortemDebugger(self.application, startup)
9521061

1062+
def setup_recorder(self, savedir):
1063+
self.application = RequestRecorder(self.application, savedir)
1064+
9531065
def reload_required(self, environ):
9541066
if self.debug_mode:
9551067
return False
@@ -1049,6 +1161,8 @@ def __call__(self, environ, start_response):
10491161
coverage_directory = '%(coverage_directory)s'
10501162
enable_profiler = %(enable_profiler)s
10511163
profiler_directory = '%(profiler_directory)s'
1164+
enable_recorder = %(enable_recorder)s
1165+
recorder_directory = '%(recorder_directory)s'
10521166
10531167
if python_paths:
10541168
sys.path.extend(python_paths)
@@ -1094,7 +1208,8 @@ def output_profiler_data():
10941208
application_type=application_type, callable_object=callable_object,
10951209
mount_point=mount_point, with_newrelic_agent=with_newrelic_agent,
10961210
debug_mode=debug_mode, enable_debugger=enable_debugger,
1097-
debugger_startup=debugger_startup)
1211+
debugger_startup=debugger_startup, enable_recorder=enable_recorder,
1212+
recorder_directory=recorder_directory)
10981213
10991214
reload_required = handler.reload_required
11001215
handle_request = handler.handle_request
@@ -1792,6 +1907,14 @@ def check_percentage(option, opt_str, value, parser):
17921907
'which profiler data will be written when enabled under debug '
17931908
'mode.'),
17941909

1910+
optparse.make_option('--enable-recorder', action='store_true',
1911+
default=False, help='Flag indicating whether recording of '
1912+
'requests is enabled when running in debug mode.'),
1913+
optparse.make_option('--recorder-directory', metavar='DIRECTORY-PATH',
1914+
default='', help='Override the path to the directory into '
1915+
'which recorder data will be written when enabled under debug '
1916+
'mode.'),
1917+
17951918
optparse.make_option('--setup-only', action='store_true', default=False,
17961919
help='Flag indicating that after the configuration files have '
17971920
'been setup, that the command should then exit and not go on '
@@ -2179,10 +2302,24 @@ def _cmd_setup_server(command, args, options):
21792302
except Exception:
21802303
pass
21812304

2305+
if options['enable_recorder']:
2306+
if not options['recorder_directory']:
2307+
options['recorder_directory'] = os.path.join(
2308+
options['server_root'], 'archive')
2309+
else:
2310+
options['recorder_directory'] = os.path.abspath(
2311+
options['recorder_directory'])
2312+
2313+
try:
2314+
os.mkdir(options['recorder_directory'])
2315+
except Exception:
2316+
pass
2317+
21822318
else:
21832319
options['enable_debugger'] = False
21842320
options['enable_coverage'] = False
21852321
options['enable_profiler'] = False
2322+
options['enable_recorder'] = False
21862323

21872324
options['parent_domain'] = 'unspecified'
21882325

@@ -2282,6 +2419,9 @@ def _cmd_setup_server(command, args, options):
22822419
if options['enable_profiler']:
22832420
print('Profiler Output :', options['profiler_directory'])
22842421

2422+
if options['enable_recorder']:
2423+
print('Recorder Output :', options['recorder_directory'])
2424+
22852425
if options['envvars_script']:
22862426
print('Environ Variables :', options['envvars_script'])
22872427

0 commit comments

Comments
 (0)