10

I have a fastapi application and I want to log every request made on it. I'm trying to use loguru and uvicorn for this, but I don't know how to print the headers and request params (if have one) associated with each request.

I want something like this:

INFO 2020-08-13 13:36:33.494 uvicorn.protocols.http.h11_impl:send - 127.0.0.1:52660 - "GET 
/url1/url2/ HTTP/1.1" 400 params={"some": value, "some1":value}

Is there a way ? thanks for your help.

Here some links:

loguru uvicorn fastapi

1 Answer 1

19

A dependency on a router level could be used (thanks to @lsabi in the comments below):

import sys

import uvicorn

from fastapi import FastAPI, Request, APIRouter, Depends
from loguru import logger
from starlette.routing import Match

logger.remove()
logger.add(sys.stdout, colorize=True, format="<green>{time:HH:mm:ss}</green> | {level} | <level>{message}</level>")
app = FastAPI()

router = APIRouter()


async def logging_dependency(request: Request):
    logger.debug(f"{request.method} {request.url}")
    logger.debug("Params:")
    for name, value in request.path_params.items():
        logger.debug(f"\t{name}: {value}")
    logger.debug("Headers:")
    for name, value in request.headers.items():
        logger.debug(f"\t{name}: {value}")


@router.get("/{param1}/{param2}")
async def path_operation(param1: str, param2: str):
    return {'param1': param1, 'param2': param2}

app.include_router(router, dependencies=[Depends(logging_dependency)])

if __name__ == "__main__":
    uvicorn.run("app:app", host="localhost", port=8001)

More sophisticated approach is using a middleware for logging every request and doing matching manually:

import sys

import uvicorn

from fastapi import FastAPI, Request
from loguru import logger
from starlette.routing import Match

logger.remove()
logger.add(sys.stdout, colorize=True, format="<green>{time:HH:mm:ss}</green> | {level} | <level>{message}</level>")
app = FastAPI()


@app.middleware("http")
async def log_middle(request: Request, call_next):
    logger.debug(f"{request.method} {request.url}")
    routes = request.app.router.routes
    logger.debug("Params:")
    for route in routes:
        match, scope = route.matches(request)
        if match == Match.FULL:
            for name, value in scope["path_params"].items():
                logger.debug(f"\t{name}: {value}")
    logger.debug("Headers:")
    for name, value in request.headers.items():
        logger.debug(f"\t{name}: {value}")

    response = await call_next(request)
    return response


@app.get("/{param1}/{param2}")
async def path_operation(param1: str, param2: str):
    return {'param1': param1, 'param2': param2}


if __name__ == "__main__":
    uvicorn.run("app:app", host="localhost", port=8001)

curl http://localhost:8001/admin/home

Output:

16:06:43 | DEBUG | GET http://localhost:8001/admin/home
16:06:43 | DEBUG | Params:
16:06:43 | DEBUG |  param1: admin
16:06:43 | DEBUG |  param2: home
16:06:43 | DEBUG | Headers:
16:06:43 | DEBUG |  host: localhost:8001
16:06:43 | DEBUG |  user-agent: curl/7.64.0
16:06:43 | DEBUG |  accept: */*
Sign up to request clarification or add additional context in comments.

6 Comments

A dependency could also be used, so that URL paths can be logged independently or together
Thank you!! this really help me to solve the problem!!...I just have one question...I replace sys.stdout for "log/access.log" to save into a file...but the format looks really bad...any idea ? thank you again !!
@AlexNoname I know...this is what I mean...character error prnt.sc/tzqx95
Try to disable colorize=False
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.