Merge "Support swift.proxy_logging_status in request env"
This commit is contained in:
commit
4eda676e2e
@ -198,6 +198,13 @@ filter factory::
|
|||||||
return webhook_filter
|
return webhook_filter
|
||||||
|
|
||||||
|
|
||||||
|
Middlewares can override the status integer that is logged by proxy_logging
|
||||||
|
middleware by setting ``swift.proxy_logging_status`` in the request WSGI
|
||||||
|
environment. The value should be an integer. The value will replace the default
|
||||||
|
status integer in the log message, unless the proxy_logging middleware detects
|
||||||
|
a client disconnect or exception while handling the request, in which case
|
||||||
|
``swift.proxy_logging_status`` is overridden by a 499 or 500 respectively.
|
||||||
|
|
||||||
--------------
|
--------------
|
||||||
Swift Metadata
|
Swift Metadata
|
||||||
--------------
|
--------------
|
||||||
|
@ -27,19 +27,28 @@ The logging format implemented below is as follows::
|
|||||||
start_time end_time policy_index
|
start_time end_time policy_index
|
||||||
|
|
||||||
These values are space-separated, and each is url-encoded, so that they can
|
These values are space-separated, and each is url-encoded, so that they can
|
||||||
be separated with a simple .split()
|
be separated with a simple ``.split()``.
|
||||||
|
|
||||||
* remote_addr is the contents of the REMOTE_ADDR environment variable, while
|
* ``remote_addr`` is the contents of the REMOTE_ADDR environment variable,
|
||||||
client_ip is swift's best guess at the end-user IP, extracted variously
|
while ``client_ip`` is swift's best guess at the end-user IP, extracted
|
||||||
from the X-Forwarded-For header, X-Cluster-Ip header, or the REMOTE_ADDR
|
variously from the X-Forwarded-For header, X-Cluster-Ip header, or the
|
||||||
environment variable.
|
REMOTE_ADDR environment variable.
|
||||||
|
|
||||||
* source (swift.source in the WSGI environment) indicates the code
|
* ``status_int`` is the integer part of the ``status`` string passed to this
|
||||||
|
middleware's start_response function, unless the WSGI environment has an item
|
||||||
|
with key ``swift.proxy_logging_status``, in which case the value of that item
|
||||||
|
is used. Other middleware's may set ``swift.proxy_logging_status`` to
|
||||||
|
override the logging of ``status_int``. In either case, the logged
|
||||||
|
``status_int`` value is forced to 499 if a client disconnect is detected
|
||||||
|
while this middleware is handling a request, or 500 if an exception is caught
|
||||||
|
while handling a request.
|
||||||
|
|
||||||
|
* ``source`` (``swift.source`` in the WSGI environment) indicates the code
|
||||||
that generated the request, such as most middleware. (See below for
|
that generated the request, such as most middleware. (See below for
|
||||||
more detail.)
|
more detail.)
|
||||||
|
|
||||||
* log_info (swift.log_info in the WSGI environment) is for additional
|
* ``log_info`` (``swift.log_info`` in the WSGI environment) is for additional
|
||||||
information that could prove quite useful, such as any x-delete-at
|
information that could prove quite useful, such as any ``x-delete-at``
|
||||||
value or other "behind the scenes" activity that might not
|
value or other "behind the scenes" activity that might not
|
||||||
otherwise be detectable from the plain log information. Code that
|
otherwise be detectable from the plain log information. Code that
|
||||||
wishes to add additional log information should use code like
|
wishes to add additional log information should use code like
|
||||||
@ -62,18 +71,18 @@ For example, with staticweb, the middleware might intercept a request to
|
|||||||
/v1/AUTH_acc/cont/, make a subrequest to the proxy to retrieve
|
/v1/AUTH_acc/cont/, make a subrequest to the proxy to retrieve
|
||||||
/v1/AUTH_acc/cont/index.html and, in effect, respond to the client's original
|
/v1/AUTH_acc/cont/index.html and, in effect, respond to the client's original
|
||||||
request using the 2nd request's body. In this instance the subrequest will be
|
request using the 2nd request's body. In this instance the subrequest will be
|
||||||
logged by the rightmost middleware (with a swift.source set) and the outgoing
|
logged by the rightmost middleware (with a ``swift.source`` set) and the
|
||||||
request (with body overridden) will be logged by leftmost middleware.
|
outgoing request (with body overridden) will be logged by leftmost middleware.
|
||||||
|
|
||||||
Requests that follow the normal pipeline (use the same wsgi environment
|
Requests that follow the normal pipeline (use the same wsgi environment
|
||||||
throughout) will not be double logged because an environment variable
|
throughout) will not be double logged because an environment variable
|
||||||
(swift.proxy_access_log_made) is checked/set when a log is made.
|
(``swift.proxy_access_log_made``) is checked/set when a log is made.
|
||||||
|
|
||||||
All middleware making subrequests should take care to set swift.source when
|
All middleware making subrequests should take care to set ``swift.source`` when
|
||||||
needed. With the doubled proxy logs, any consumer/processor of swift's proxy
|
needed. With the doubled proxy logs, any consumer/processor of swift's proxy
|
||||||
logs should look at the swift.source field, the rightmost log value, to decide
|
logs should look at the ``swift.source`` field, the rightmost log value, to
|
||||||
if this is a middleware subrequest or not. A log processor calculating
|
decide if this is a middleware subrequest or not. A log processor calculating
|
||||||
bandwidth usage will want to only sum up logs with no swift.source.
|
bandwidth usage will want to only sum up logs with no ``swift.source``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@ -389,11 +398,11 @@ class ProxyLoggingMiddleware(object):
|
|||||||
def my_start_response(status, headers, exc_info=None):
|
def my_start_response(status, headers, exc_info=None):
|
||||||
start_response_args[0] = (status, list(headers), exc_info)
|
start_response_args[0] = (status, list(headers), exc_info)
|
||||||
|
|
||||||
def status_int_for_logging(start_status, client_disconnect=False):
|
def status_int_for_logging():
|
||||||
# log disconnected clients as '499' status code
|
# log disconnected clients as '499' status code
|
||||||
if client_disconnect or input_proxy.client_disconnect:
|
if input_proxy.client_disconnect:
|
||||||
return 499
|
return 499
|
||||||
return start_status
|
return env.get('swift.proxy_logging_status')
|
||||||
|
|
||||||
def iter_response(iterable):
|
def iter_response(iterable):
|
||||||
iterator = reiterate(iterable)
|
iterator = reiterate(iterable)
|
||||||
@ -438,21 +447,19 @@ class ProxyLoggingMiddleware(object):
|
|||||||
metric_name_policy + '.first-byte.timing', ttfb * 1000)
|
metric_name_policy + '.first-byte.timing', ttfb * 1000)
|
||||||
|
|
||||||
bytes_sent = 0
|
bytes_sent = 0
|
||||||
client_disconnect = False
|
|
||||||
start_status = wire_status_int
|
|
||||||
try:
|
try:
|
||||||
for chunk in iterator:
|
for chunk in iterator:
|
||||||
bytes_sent += len(chunk)
|
bytes_sent += len(chunk)
|
||||||
yield chunk
|
yield chunk
|
||||||
except GeneratorExit: # generator was closed before we finished
|
except GeneratorExit: # generator was closed before we finished
|
||||||
client_disconnect = True
|
env['swift.proxy_logging_status'] = 499
|
||||||
raise
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
start_status = 500
|
env['swift.proxy_logging_status'] = 500
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
status_int = status_int_for_logging(
|
env.setdefault('swift.proxy_logging_status', wire_status_int)
|
||||||
start_status, client_disconnect)
|
status_int = status_int_for_logging()
|
||||||
self.log_request(
|
self.log_request(
|
||||||
req, status_int, input_proxy.bytes_received, bytes_sent,
|
req, status_int, input_proxy.bytes_received, bytes_sent,
|
||||||
start_time, time.time(), resp_headers=resp_headers,
|
start_time, time.time(), resp_headers=resp_headers,
|
||||||
@ -463,7 +470,8 @@ class ProxyLoggingMiddleware(object):
|
|||||||
iterable = self.app(env, my_start_response)
|
iterable = self.app(env, my_start_response)
|
||||||
except Exception:
|
except Exception:
|
||||||
req = Request(env)
|
req = Request(env)
|
||||||
status_int = status_int_for_logging(500)
|
env['swift.proxy_logging_status'] = 500
|
||||||
|
status_int = status_int_for_logging()
|
||||||
self.log_request(
|
self.log_request(
|
||||||
req, status_int, input_proxy.bytes_received, 0, start_time,
|
req, status_int, input_proxy.bytes_received, 0, start_time,
|
||||||
time.time())
|
time.time())
|
||||||
|
@ -36,7 +36,7 @@ from test.unit.common.middleware.helpers import FakeAppThatExcepts
|
|||||||
class FakeApp(object):
|
class FakeApp(object):
|
||||||
|
|
||||||
def __init__(self, body=None, response_str='200 OK', policy_idx='0',
|
def __init__(self, body=None, response_str='200 OK', policy_idx='0',
|
||||||
chunked=False):
|
chunked=False, environ_updates=None):
|
||||||
if body is None:
|
if body is None:
|
||||||
body = [b'FAKE APP']
|
body = [b'FAKE APP']
|
||||||
elif isinstance(body, six.binary_type):
|
elif isinstance(body, six.binary_type):
|
||||||
@ -46,6 +46,7 @@ class FakeApp(object):
|
|||||||
self.response_str = response_str
|
self.response_str = response_str
|
||||||
self.policy_idx = policy_idx
|
self.policy_idx = policy_idx
|
||||||
self.chunked = chunked
|
self.chunked = chunked
|
||||||
|
self.environ_updates = environ_updates or {}
|
||||||
|
|
||||||
def __call__(self, env, start_response):
|
def __call__(self, env, start_response):
|
||||||
try:
|
try:
|
||||||
@ -65,7 +66,7 @@ class FakeApp(object):
|
|||||||
if is_container_or_object_req and self.policy_idx is not None:
|
if is_container_or_object_req and self.policy_idx is not None:
|
||||||
headers.append(('X-Backend-Storage-Policy-Index',
|
headers.append(('X-Backend-Storage-Policy-Index',
|
||||||
str(self.policy_idx)))
|
str(self.policy_idx)))
|
||||||
|
env.update(self.environ_updates)
|
||||||
start_response(self.response_str, headers)
|
start_response(self.response_str, headers)
|
||||||
while env['wsgi.input'].read(5):
|
while env['wsgi.input'].read(5):
|
||||||
pass
|
pass
|
||||||
@ -791,6 +792,136 @@ class TestProxyLogging(unittest.TestCase):
|
|||||||
self.assertEqual(log_parts[6], '499')
|
self.assertEqual(log_parts[6], '499')
|
||||||
self.assertEqual(log_parts[10], '-') # read length
|
self.assertEqual(log_parts[10], '-') # read length
|
||||||
|
|
||||||
|
def test_environ_has_proxy_logging_status(self):
|
||||||
|
conf = {'log_msg_template':
|
||||||
|
'{method} {path} {status_int} {wire_status_int}'}
|
||||||
|
|
||||||
|
def do_test(environ_updates):
|
||||||
|
fake_app = FakeApp(body=[b'Slow Down'],
|
||||||
|
response_str='503 Slow Down',
|
||||||
|
environ_updates=environ_updates)
|
||||||
|
app = proxy_logging.ProxyLoggingMiddleware(fake_app, conf)
|
||||||
|
app.access_logger = debug_logger()
|
||||||
|
req = Request.blank('/v1/a/c')
|
||||||
|
captured_start_resp = mock.MagicMock()
|
||||||
|
try:
|
||||||
|
resp = app(req.environ, captured_start_resp)
|
||||||
|
b''.join(resp) # read body
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
captured_start_resp.assert_called_once_with(
|
||||||
|
'503 Slow Down', mock.ANY, None)
|
||||||
|
return self._log_parts(app)
|
||||||
|
|
||||||
|
# control case, logged status == wire status
|
||||||
|
environ_updates = {}
|
||||||
|
self.assertEqual(do_test(environ_updates),
|
||||||
|
['GET', '/v1/a/c', '503', '503'])
|
||||||
|
|
||||||
|
# logged status is forced to other value
|
||||||
|
environ_updates = {'swift.proxy_logging_status': 429}
|
||||||
|
self.assertEqual(do_test(environ_updates),
|
||||||
|
['GET', '/v1/a/c', '429', '503'])
|
||||||
|
|
||||||
|
environ_updates = {'swift.proxy_logging_status': '429'}
|
||||||
|
self.assertEqual(do_test(environ_updates),
|
||||||
|
['GET', '/v1/a/c', '429', '503'])
|
||||||
|
|
||||||
|
environ_updates = {'swift.proxy_logging_status': None}
|
||||||
|
self.assertEqual(do_test(environ_updates),
|
||||||
|
['GET', '/v1/a/c', '-', '503'])
|
||||||
|
|
||||||
|
environ_updates = {'swift.proxy_logging_status': ''}
|
||||||
|
self.assertEqual(do_test(environ_updates),
|
||||||
|
['GET', '/v1/a/c', '-', '503'])
|
||||||
|
|
||||||
|
def test_environ_has_proxy_logging_status_unread_body(self):
|
||||||
|
conf = {'log_msg_template':
|
||||||
|
'{method} {path} {status_int} {wire_status_int}'}
|
||||||
|
|
||||||
|
def do_test(environ_updates):
|
||||||
|
fake_app = FakeApp(body=[b'Slow Down'],
|
||||||
|
response_str='503 Slow Down',
|
||||||
|
environ_updates=environ_updates)
|
||||||
|
app = proxy_logging.ProxyLoggingMiddleware(fake_app, conf)
|
||||||
|
app.access_logger = debug_logger()
|
||||||
|
req = Request.blank('/v1/a/c')
|
||||||
|
captured_start_resp = mock.MagicMock()
|
||||||
|
resp = app(req.environ, captured_start_resp)
|
||||||
|
# read first chunk
|
||||||
|
next(resp)
|
||||||
|
resp.close() # raise a GeneratorExit in middleware app_iter loop
|
||||||
|
captured_start_resp.assert_called_once_with(
|
||||||
|
'503 Slow Down', mock.ANY, None)
|
||||||
|
return self._log_parts(app)
|
||||||
|
|
||||||
|
# control case, logged status is 499
|
||||||
|
environ_updates = {}
|
||||||
|
self.assertEqual(do_test(environ_updates),
|
||||||
|
['GET', '/v1/a/c', '499', '503'])
|
||||||
|
|
||||||
|
# logged status is forced to 499 despite swift.proxy_logging_status
|
||||||
|
environ_updates = {'swift.proxy_logging_status': '429'}
|
||||||
|
self.assertEqual(do_test(environ_updates),
|
||||||
|
['GET', '/v1/a/c', '499', '503'])
|
||||||
|
|
||||||
|
def test_environ_has_proxy_logging_status_and_app_explodes(self):
|
||||||
|
# verify exception overrides proxy_logging_status
|
||||||
|
conf = {'log_msg_template':
|
||||||
|
'{method} {path} {status_int} {wire_status_int}'}
|
||||||
|
|
||||||
|
class ExplodingFakeApp(object):
|
||||||
|
|
||||||
|
def __call__(self, env, start_response):
|
||||||
|
# this is going to be so great!
|
||||||
|
env['swift.proxy_logging_status'] = '456'
|
||||||
|
start_response('568 Bespoke', [('X-Special', 'fun')])
|
||||||
|
raise Exception('oops!')
|
||||||
|
|
||||||
|
fake_app = ExplodingFakeApp()
|
||||||
|
app = proxy_logging.ProxyLoggingMiddleware(fake_app, conf)
|
||||||
|
app.access_logger = debug_logger()
|
||||||
|
req = Request.blank('/v1/a/c')
|
||||||
|
captured_start_resp = mock.MagicMock()
|
||||||
|
with self.assertRaises(Exception) as cm:
|
||||||
|
app(req.environ, captured_start_resp)
|
||||||
|
captured_start_resp.assert_not_called()
|
||||||
|
self.assertEqual('oops!', str(cm.exception))
|
||||||
|
self.assertEqual(self._log_parts(app),
|
||||||
|
['GET', '/v1/a/c', '500', '500'])
|
||||||
|
|
||||||
|
def test_environ_has_proxy_logging_status_and_body_explodes(self):
|
||||||
|
# verify exception overrides proxy_logging_status
|
||||||
|
conf = {'log_msg_template':
|
||||||
|
'{method} {path} {status_int} {wire_status_int}'}
|
||||||
|
|
||||||
|
def exploding_body():
|
||||||
|
yield 'some'
|
||||||
|
yield 'stuff'
|
||||||
|
raise Exception('oops!')
|
||||||
|
|
||||||
|
class ExplodingFakeApp(object):
|
||||||
|
|
||||||
|
def __call__(self, env, start_response):
|
||||||
|
# this is going to be so great!
|
||||||
|
env['swift.proxy_logging_status'] = '456'
|
||||||
|
start_response('568 Bespoke', [('X-Special', 'fun')])
|
||||||
|
return exploding_body()
|
||||||
|
|
||||||
|
fake_app = ExplodingFakeApp()
|
||||||
|
app = proxy_logging.ProxyLoggingMiddleware(fake_app, conf)
|
||||||
|
app.access_logger = debug_logger()
|
||||||
|
req = Request.blank('/v1/a/c')
|
||||||
|
captured_start_resp = mock.MagicMock()
|
||||||
|
app_iter = app(req.environ, captured_start_resp)
|
||||||
|
with self.assertRaises(Exception) as cm:
|
||||||
|
b''.join(app_iter)
|
||||||
|
captured_start_resp.assert_called_once_with(
|
||||||
|
'568 Bespoke', [('X-Special', 'fun')], None)
|
||||||
|
self.assertEqual('oops!', str(cm.exception))
|
||||||
|
self.assertEqual(self._log_parts(app),
|
||||||
|
['GET', '/v1/a/c', '500', '568'])
|
||||||
|
|
||||||
def test_app_exception(self):
|
def test_app_exception(self):
|
||||||
app = proxy_logging.ProxyLoggingMiddleware(
|
app = proxy_logging.ProxyLoggingMiddleware(
|
||||||
FakeAppThatExcepts(), {})
|
FakeAppThatExcepts(), {})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user