Skip to content

Commit 961dd21

Browse files
committed
Fix access_log_format in GunicornWebWorker
- Replace default gunicorn's access log format with the default aiohttp's one. - Use regular expression to check if the log format passed as a gunicorn's option `access_logformat` uses Gunicorn's formatting style and in this case raise a `ValueError`. - Add a note describing this behavior to the `Logging` section of the documentation.
1 parent 18fae34 commit 961dd21

File tree

4 files changed

+59
-3
lines changed

4 files changed

+59
-3
lines changed

aiohttp/worker.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,24 @@
22

33
import asyncio
44
import os
5+
import re
56
import signal
67
import ssl
78
import sys
89

910
import gunicorn.workers.base as base
1011

11-
from aiohttp.helpers import ensure_future
12+
from gunicorn.config import AccessLogFormat as GunicornAccessLogFormat
13+
from aiohttp.helpers import AccessLogger, ensure_future
1214

1315
__all__ = ('GunicornWebWorker', 'GunicornUVLoopWebWorker')
1416

1517

1618
class GunicornWebWorker(base.Worker):
1719

20+
DEFAULT_AIOHTTP_LOG_FORMAT = AccessLogger.LOG_FORMAT
21+
DEFAULT_GUNICORN_LOG_FORMAT = GunicornAccessLogFormat.default
22+
1823
def __init__(self, *args, **kw): # pragma: no cover
1924
super().__init__(*args, **kw)
2025

@@ -48,7 +53,8 @@ def make_handler(self, app):
4853
timeout=self.cfg.timeout,
4954
keep_alive=self.cfg.keepalive,
5055
access_log=self.log.access_log,
51-
access_log_format=self.cfg.access_log_format)
56+
access_log_format=self._get_valid_log_format(
57+
self.cfg.access_log_format))
5258

5359
@asyncio.coroutine
5460
def close(self):
@@ -158,6 +164,15 @@ def _create_ssl_context(cfg):
158164
ctx.set_ciphers(cfg.ciphers)
159165
return ctx
160166

167+
def _get_valid_log_format(self, source_format):
168+
if source_format == self.DEFAULT_GUNICORN_LOG_FORMAT:
169+
return self.DEFAULT_AIOHTTP_LOG_FORMAT
170+
elif re.search(r'%\([^\)]+\)', source_format):
171+
raise ValueError('Gunicorn style using `%(name)s` '
172+
'is not supported for the log formatting.')
173+
else:
174+
return source_format
175+
161176

162177
class GunicornUVLoopWebWorker(GunicornWebWorker):
163178

docs/gunicorn.rst

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.. _deployment-using-gunicorn:
2+
13
Deployment using Gunicorn
24
=========================
35

docs/logging.rst

+14
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,16 @@ Default access log format is::
8989
'%a %l %u %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i"'
9090

9191

92+
.. note::
93+
94+
When `Gunicorn <http://docs.gunicorn.org/en/latest/index.html>`_ is used for
95+
:ref:`deployment <deployment-using-gunicorn>` its default access log format
96+
will be automatically replaced with the default aiohttp's access log format.
97+
98+
If Gunicorn's option access_logformat_ is
99+
specified explicitly it should use aiohttp's format specification.
100+
101+
92102
Error logs
93103
----------
94104

@@ -100,3 +110,7 @@ The log is enabled by default.
100110
To use different logger name please specify *logger* parameter
101111
(:class:`logging.Logger` instance) on performing
102112
:meth:`aiohttp.web.Application.make_handler` call.
113+
114+
115+
.. _access_logformat:
116+
http://docs.gunicorn.org/en/stable/settings.html#access-log-format

tests/test_worker.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
uvloop = None
1818

1919

20+
WRONG_LOG_FORMAT = '%a "%{Referrer}i" %(h)s %(l)s %s'
21+
ACCEPTABLE_LOG_FORMAT = '%a "%{Referrer}i" %s'
22+
23+
2024
class BaseTestWorker:
2125

2226
def __init__(self):
@@ -89,14 +93,32 @@ def test_init_signals(worker):
8993
assert worker.loop.add_signal_handler.called
9094

9195

92-
def test_make_handler(worker):
96+
def test_make_handler(worker, mocker):
9397
worker.wsgi = mock.Mock()
9498
worker.loop = mock.Mock()
9599
worker.log = mock.Mock()
96100
worker.cfg = mock.Mock()
101+
worker.cfg.access_log_format = ACCEPTABLE_LOG_FORMAT
102+
mocker.spy(worker, '_get_valid_log_format')
97103

98104
f = worker.make_handler(worker.wsgi)
99105
assert f is worker.wsgi.make_handler.return_value
106+
assert worker._get_valid_log_format.called
107+
108+
109+
@pytest.mark.parametrize('source,result', [
110+
(ACCEPTABLE_LOG_FORMAT, ACCEPTABLE_LOG_FORMAT),
111+
(AsyncioWorker.DEFAULT_GUNICORN_LOG_FORMAT,
112+
AsyncioWorker.DEFAULT_AIOHTTP_LOG_FORMAT),
113+
])
114+
def test__get_valid_log_format_ok(worker, source, result):
115+
assert result == worker._get_valid_log_format(source)
116+
117+
118+
def test__get_valid_log_format_exc(worker):
119+
with pytest.raises(ValueError) as exc:
120+
worker._get_valid_log_format(WRONG_LOG_FORMAT)
121+
assert '%(name)s' in str(exc)
100122

101123

102124
@asyncio.coroutine
@@ -115,6 +137,7 @@ def test__run_ok(worker, loop):
115137
worker.wsgi.make_handler.return_value.requests_count = 1
116138
worker.cfg.max_requests = 100
117139
worker.cfg.is_ssl = True
140+
worker.cfg.access_log_format = ACCEPTABLE_LOG_FORMAT
118141

119142
ssl_context = mock.Mock()
120143
with mock.patch('ssl.SSLContext', return_value=ssl_context):
@@ -205,6 +228,7 @@ def test__run_ok_no_max_requests(worker, loop):
205228
worker.loop = loop
206229
loop.create_server = make_mocked_coro(sock)
207230
worker.wsgi.make_handler.return_value.requests_count = 1
231+
worker.cfg.access_log_format = ACCEPTABLE_LOG_FORMAT
208232
worker.cfg.max_requests = 0
209233
worker.cfg.is_ssl = True
210234

@@ -239,6 +263,7 @@ def test__run_ok_max_requests_exceeded(worker, loop):
239263
worker.loop = loop
240264
loop.create_server = make_mocked_coro(sock)
241265
worker.wsgi.make_handler.return_value.requests_count = 15
266+
worker.cfg.access_log_format = ACCEPTABLE_LOG_FORMAT
242267
worker.cfg.max_requests = 10
243268
worker.cfg.is_ssl = True
244269

0 commit comments

Comments
 (0)