Skip to content

[PROPOSAL] new signal on_request #3787

@hh-h

Description

@hh-h

Long story short

We want to attach context from contextvars to logger message

Problem

When user tries to put some information to logs (i.e. request_id) there's no access to contextvars, because user can only put something to contextvars in middleware

I propose to add new signal, like response.on_prepare, but for request, where I can put something to contextvars, for example mark this request with uuid.

see example (python 3.7 only):

import logging
import uuid
from logging.config import dictConfig

import contextvars
from aiohttp import web
from aiohttp.abc import AbstractAccessLogger

REQUEST_ID = contextvars.ContextVar("request_id")

class AsyncAccessLogger(AbstractAccessLogger):
    def log(self, request, response, time):
        print(("AsyncAccessLogger", REQUEST_ID.get()))
        self.logger.info("test")

class ContextFilter(logging.Filter):
    def filter(self, record: logging.LogRecord) -> bool:
        record.__dict__.update({"request_id": REQUEST_ID.get()})
        return True

def setup_logs():
    dictConfig(
        config={
            "version": 1,
            "disable_existing_loggers": False,
            "filters": {"request_context": {"()": "start_log_test.ContextFilter"}},
            "handlers": {"stdout": {"level": "DEBUG", "class": "logging.StreamHandler"}},
            "loggers": {
                "aiohttp.access": {"handlers": ["stdout"], "level": "DEBUG"},
                "aiohttp.server": {"handlers": ["stdout"], "level": "DEBUG"},
            },
        }
    )

@web.middleware
async def request_id(request, handler):
    REQUEST_ID.set(str(uuid.uuid4()))
    return await handler(request)

async def get_(request: web.Request):
    print(("REQUEST", REQUEST_ID.get()))
    # for exception
    1 / 0
    return web.json_response()

def main():
    app = web.Application(middlewares=[request_id])
    app.add_routes([web.get("/", get_)])
    setup_logs()
    web.run_app(app, access_log_class=AsyncAccessLogger, port=8088)

if __name__ == "__main__":
    main()

after request

======== Running on http://0.0.0.0:8088 ========
(Press CTRL+C to quit)
('REQUEST', '3bd497ec-1df1-4deb-842d-d12197a9c9a1')
Error handling request
Traceback (most recent call last):
  File ".../code/venvs/aiohttp_logs/lib/python3.7/site-packages/aiohttp/web_protocol.py", line 418, in start
    resp = await task
  File ".../code/venvs/aiohttp_logs/lib/python3.7/site-packages/aiohttp/web_app.py", line 458, in _handle
    resp = await handler(request)
  File ".../code/venvs/aiohttp_logs/lib/python3.7/site-packages/aiohttp/web_middlewares.py", line 119, in impl
    return await handler(request)
  File ".../code/python/aiohttp_logs/start_log_test.py", line 42, in request_id
    return await handler(request)
  File ".../code/python/aiohttp_logs/start_log_test.py", line 48, in get_
    1 / 0
ZeroDivisionError: division by zero
Unhandled exception
Traceback (most recent call last):
  File ".../code/venvs/aiohttp_logs/lib/python3.7/site-packages/aiohttp/web_protocol.py", line 455, in start
    self.log_access(request, resp, loop.time() - now)
  File ".../code/venvs/aiohttp_logs/lib/python3.7/site-packages/aiohttp/web_protocol.py", line 348, in log_access
    self.access_logger.log(request, response, time)
  File ".../code/python/aiohttp_logs/start_log_test.py", line 14, in log
    print(("AsyncAccessLogger", REQUEST_ID.get()))
LookupError: <ContextVar name='request_id' at 0x10aee7d00>

If we agree on design/architecture, I can work on pull request or maybe someone can give us a hint how to achieve that :)

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions