Skip to content

[PROPOSAL] new signal on_request #3787

Closed
@hh-h

Description

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

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