Skip to content

Commit 2c3b885

Browse files
[PR #10713/8d74e26 backport][3.11] Avoid fetching loop time on each request unless logging is enabled (#10738)
Co-authored-by: Sam Bull <[email protected]>
1 parent eadcd28 commit 2c3b885

File tree

4 files changed

+52
-4
lines changed

4 files changed

+52
-4
lines changed

CHANGES/10713.misc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Optimized web server performance when access logging is disabled by reducing time syscalls -- by :user:`bdraco`.

aiohttp/web_protocol.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ class RequestHandler(BaseProtocol):
170170
"_current_request",
171171
"_timeout_ceil_threshold",
172172
"_request_in_progress",
173+
"_logging_enabled",
173174
"_cache",
174175
)
175176

@@ -244,8 +245,10 @@ def __init__(
244245
self.access_logger: Optional[AbstractAccessLogger] = access_log_class(
245246
access_log, access_log_format
246247
)
248+
self._logging_enabled = self.access_logger.enabled
247249
else:
248250
self.access_logger = None
251+
self._logging_enabled = False
249252

250253
self._close = False
251254
self._force_close = False
@@ -463,9 +466,11 @@ def force_close(self) -> None:
463466
self.transport = None
464467

465468
def log_access(
466-
self, request: BaseRequest, response: StreamResponse, time: float
469+
self, request: BaseRequest, response: StreamResponse, time: Optional[float]
467470
) -> None:
468471
if self.access_logger is not None and self.access_logger.enabled:
472+
if TYPE_CHECKING:
473+
assert time is not None
469474
self.access_logger.log(request, response, self._loop.time() - time)
470475

471476
def log_debug(self, *args: Any, **kw: Any) -> None:
@@ -495,7 +500,7 @@ def _process_keepalive(self) -> None:
495500
async def _handle_request(
496501
self,
497502
request: BaseRequest,
498-
start_time: float,
503+
start_time: Optional[float],
499504
request_handler: Callable[[BaseRequest], Awaitable[StreamResponse]],
500505
) -> Tuple[StreamResponse, bool]:
501506
self._request_in_progress = True
@@ -563,7 +568,9 @@ async def start(self) -> None:
563568

564569
message, payload = self._messages.popleft()
565570

566-
start = loop.time()
571+
# time is only fetched if logging is enabled as otherwise
572+
# its thrown away and never used.
573+
start = loop.time() if self._logging_enabled else None
567574

568575
manager.requests_count += 1
569576
writer = StreamWriter(self, loop)
@@ -671,7 +678,7 @@ async def start(self) -> None:
671678
self.transport.close()
672679

673680
async def finish_response(
674-
self, request: BaseRequest, resp: StreamResponse, start_time: float
681+
self, request: BaseRequest, resp: StreamResponse, start_time: Optional[float]
675682
) -> Tuple[StreamResponse, bool]:
676683
"""Prepare the response and write_eof, then log access.
677684

tests/test_web_app.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,20 @@ def log(self, request, response, time):
144144
)
145145

146146

147+
async def test_app_make_handler_no_access_log_class(mocker) -> None:
148+
srv = mocker.patch("aiohttp.web_app.Server")
149+
app = web.Application(handler_args={"access_log": None})
150+
app._make_handler(access_log=None)
151+
srv.assert_called_with(
152+
app._handle,
153+
request_factory=app._make_request,
154+
loop=asyncio.get_event_loop(),
155+
access_log=None,
156+
debug=mock.ANY,
157+
access_log_class=mock.ANY,
158+
)
159+
160+
147161
async def test_app_make_handler_raises_deprecation_warning() -> None:
148162
app = web.Application()
149163

tests/test_web_log.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,3 +255,29 @@ def enabled(self) -> bool:
255255
resp = await client.get("/")
256256
assert 200 == resp.status
257257
assert "This should not be logged" not in caplog.text
258+
259+
260+
async def test_logger_set_to_none(
261+
aiohttp_server: AiohttpServer,
262+
aiohttp_client: AiohttpClient,
263+
caplog: pytest.LogCaptureFixture,
264+
) -> None:
265+
"""Test logger does nothing when access_log is set to None."""
266+
267+
async def handler(request: web.Request) -> web.Response:
268+
return web.Response()
269+
270+
class Logger(AbstractAccessLogger):
271+
272+
def log(
273+
self, request: web.BaseRequest, response: web.StreamResponse, time: float
274+
) -> None:
275+
self.logger.critical("This should not be logged") # pragma: no cover
276+
277+
app = web.Application()
278+
app.router.add_get("/", handler)
279+
server = await aiohttp_server(app, access_log=None, access_log_class=Logger)
280+
client = await aiohttp_client(server)
281+
resp = await client.get("/")
282+
assert 200 == resp.status
283+
assert "This should not be logged" not in caplog.text

0 commit comments

Comments
 (0)