diff --git a/eventlet/wsgi.py b/eventlet/wsgi.py index ef2c62f..cbca82b 100644 --- a/eventlet/wsgi.py +++ b/eventlet/wsgi.py @@ -15,6 +15,7 @@ DEFAULT_MAX_SIMULTANEOUS_REQUESTS = 1024 DEFAULT_MAX_HTTP_VERSION = 'HTTP/1.1' MAX_REQUEST_LINE = 8192 MINIMUM_CHUNK_SIZE = 4096 +DEFAULT_LOG_FORMAT='%(client_ip)s - - [%(date_time)s] "%(request_line)s" %(status_code)s %(body_length)s %(wall_seconds).6f' __all__ = ['server', 'format_date_time'] @@ -303,13 +304,13 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): pass finish = time.time() - self.server.log_message('%s - - [%s] "%s" %s %s %.6f' % ( - self.get_client_ip(), - self.log_date_time_string(), - self.requestline, - status_code[0], - length[0], - finish - start)) + self.server.log_message(self.server.log_format % dict( + client_ip=self.get_client_ip(), + date_time=self.log_date_time_string(), + request_line=self.requestline, + status_code=status_code[0], + body_length=length[0], + wall_seconds=finish - start)) def get_client_ip(self): client_ip = self.client_address[0] @@ -388,8 +389,9 @@ class Server(BaseHTTPServer.HTTPServer): max_http_version=None, protocol=HttpProtocol, minimum_chunk_size=None, - log_x_forwarded_for=True): - + log_x_forwarded_for=True, + log_format=DEFAULT_LOG_FORMAT): + self.outstanding_requests = 0 self.socket = socket self.address = address @@ -405,6 +407,7 @@ class Server(BaseHTTPServer.HTTPServer): if minimum_chunk_size is not None: protocol.minimum_chunk_size = minimum_chunk_size self.log_x_forwarded_for = log_x_forwarded_for + self.log_format = log_format def get_environ(self): socket = self.socket @@ -437,7 +440,8 @@ def server(sock, site, server_event=None, minimum_chunk_size=None, log_x_forwarded_for=True, - custom_pool=None): + custom_pool=None, + log_format=DEFAULT_LOG_FORMAT): """ Start up a `WSGI `_ server handling requests from the supplied server socket. This function loops forever. @@ -449,17 +453,18 @@ def server(sock, site, :param protocol: Protocol class. Deprecated. :param server_event: Used to collect the Server object. Deprecated. :param minimum_chunk_size: Minimum size in bytes for http chunks. This can be used to improve performance of applications which yield many small strings, though using it technically violates the WSGI spec. - :param log_x_forwarded_for: If True (the default), logs the contents of the x-forwarded-for header in addition to the actual client ip address. + :param log_x_forwarded_for: If True (the default), logs the contents of the x-forwarded-for header in addition to the actual client ip address in the 'client_ip' field of the log line. :param custom_pool: A custom Pool instance which is used to spawn client green threads. If this is supplied, max_size is ignored. + :param log_formar: A python format string that is used as the template to generate log lines. The following values can be formatted into it: client_ip, date_time, request_line, status_code, body_length, wall_seconds. Look at DEFAULT_LOG_FORMAT for an example of how to use this. """ - serv = Server(sock, sock.getsockname(), site, log, environ=None, max_http_version=max_http_version, protocol=protocol, minimum_chunk_size=minimum_chunk_size, - log_x_forwarded_for=log_x_forwarded_for) + log_x_forwarded_for=log_x_forwarded_for, + log_format=log_format) if server_event is not None: server_event.send(serv) if max_size is None: diff --git a/tests/wsgi_test.py b/tests/wsgi_test.py index 2b3d65d..0fb8515 100644 --- a/tests/wsgi_test.py +++ b/tests/wsgi_test.py @@ -124,20 +124,36 @@ class TestHttpd(LimitedTestCase): super(TestHttpd, self).setUp() self.logfile = StringIO() self.site = Site() - listener = api.tcp_listener(('localhost', 0)) - self.port = listener.getsockname()[1] - self.killer = api.spawn( - wsgi.server, - listener, - self.site, - max_size=128, - log=self.logfile) - + self.killer = None + self.spawn_server() + def tearDown(self): super(TestHttpd, self).tearDown() api.kill(self.killer) api.sleep(0) + def spawn_server(self, **kwargs): + """Spawns a new wsgi server with the given arguments. + Sets self.port to the port of the server, and self.killer is the greenlet + running it. + + Kills any previously-running server.""" + if self.killer: + api.kill(self.killer) + + new_kwargs = dict(max_size=128, + log=self.logfile, + site=self.site) + new_kwargs.update(kwargs) + + if 'sock' not in new_kwargs: + new_kwargs['sock'] = api.tcp_listener(('localhost', 0)) + + self.port = new_kwargs['sock'].getsockname()[1] + self.killer = api.spawn( + wsgi.server, + **new_kwargs) + def test_001_server(self): sock = api.connect_tcp( ('localhost', self.port)) @@ -317,10 +333,9 @@ class TestHttpd(LimitedTestCase): private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key') server_sock = api.ssl_listener(('localhost', 0), certificate_file, private_key_file) - - api.spawn(wsgi.server, server_sock, wsgi_app, log=StringIO()) + self.spawn_server(sock=server_sock, site=wsgi_app) - sock = api.connect_tcp(('localhost', server_sock.getsockname()[1])) + sock = api.connect_tcp(('localhost', self.port)) sock = util.wrap_ssl(sock) sock.write('POST /foo HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\nContent-length:3\r\n\r\nabc') result = sock.read(8192) @@ -334,7 +349,7 @@ class TestHttpd(LimitedTestCase): certificate_file = os.path.join(os.path.dirname(__file__), 'test_server.crt') private_key_file = os.path.join(os.path.dirname(__file__), 'test_server.key') server_sock = api.ssl_listener(('localhost', 0), certificate_file, private_key_file) - api.spawn(wsgi.server, server_sock, wsgi_app, log=StringIO()) + self.spawn_server(sock=server_sock, site=wsgi_app) sock = api.connect_tcp(('localhost', server_sock.getsockname()[1])) sock = util.wrap_ssl(sock) @@ -505,16 +520,7 @@ class TestHttpd(LimitedTestCase): # turning off the option should work too self.logfile = StringIO() - api.kill(self.killer) - listener = api.tcp_listener(('localhost', 0)) - self.port = listener.getsockname()[1] - self.killer = api.spawn( - wsgi.server, - listener, - self.site, - max_size=128, - log=self.logfile, - log_x_forwarded_for=False) + self.spawn_server(log_x_forwarded_for=False) sock = api.connect_tcp(('localhost', self.port)) sock.sendall('GET / HTTP/1.1\r\nHost: localhost\r\nX-Forwarded-For: 1.2.3.4, 5.6.7.8\r\n\r\n') @@ -551,16 +557,7 @@ class TestHttpd(LimitedTestCase): # ensure that all clients finished from eventlet import pool p = pool.Pool(max_size=5) - api.kill(self.killer) - listener = api.tcp_listener(('localhost', 0)) - self.port = listener.getsockname()[1] - self.killer = api.spawn( - wsgi.server, - listener, - self.site, - max_size=128, - log=self.logfile, - custom_pool=p) + self.spawn_server(custom_pool=p) # this stuff is copied from test_001_server, could be better factored sock = api.connect_tcp( @@ -623,6 +620,14 @@ class TestHttpd(LimitedTestCase): self.assertEquals(fd.read(7), 'testing') fd.close() + def test_025_log_format(self): + self.spawn_server(log_format="HI %(request_line)s HI") + sock = api.connect_tcp(('localhost', self.port)) + sock.sendall('GET /yo! HTTP/1.1\r\nHost: localhost\r\n\r\n') + sock.recv(1024) + sock.close() + self.assert_('\nHI GET /yo! HTTP/1.1 HI\n' in self.logfile.getvalue(), self.logfile.getvalue()) + if __name__ == '__main__': main()