Я заметил, что любой logging.info('stuff happened in a route')
на маршруте не попадал в мой журнал Application Insights. Экземпляры logging.info('stuff happened in entrypoint')
были. Я нашел этот небольшой совет, но решение немного расплывчатое и, похоже, специфичное для запуска uvicorn, и я не знаю, как применить его к Azure.
Для справки вот фрагменты моего __init__.py
import logging
import azure.functions as func
from fastapi import FastAPI
from .routes import route1
app = FastAPI()
app.include_router(route1.router)
@app.get("/api/test"):
def test():
logging.info("entry test")
return "test"
async def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
return await func.AsgiMiddleware(app).handle_async(req, context)
Эти записи попадают в мои журналы
Тогда на маршруте 1 у меня есть
from fastapi import APIRouter
import logging
router=APIRouter(prefix = "/api/route1")
@router.get("/test")
def test():
logging.info("resp route test")
return "route test"
Сам маршрут работает, то есть, если я перейду к «api/route1/test», я получаю «тест маршрута ответа» в своем браузере, но затем, когда я проверяю журналы, у меня нет записи «тест маршрута».
Я попробовал добавить logging.config.dictConfig({"disable_existing_loggers": False, "version": 1})
в начало файла Route1.py, но это не помогло.
Я внес несколько изменений в ваш код, чтобы получить журнал, а также вывод браузера.
__init__.py
import logging
import azure.functions as func
from fastapi import FastAPI
from .route import route1
app = FastAPI()
app.include_router(route1.router)
@app.get("/api/test")
async def test():
logging.info('stuff happened in entrypoint')
logging.info('entry test')
return "test"
async def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
logging.info('Received request from main')
return await func.AsgiMiddleware(app).handle_async(req, context)
route1.py
from fastapi import APIRouter
import logging
router=APIRouter(prefix = "/api/route1")
@router.get("/test")
async def test():
logging.info('stuff happened in a route')
logging.info('resp route test')
return "route test"
Я могу получить ожидаемый ответ при переходе к /api/route1/test
на местном уровне.
Затем я опубликовал его в функциональном приложении.
Ниже приведены результаты в приложении-функции и в аналитике приложения.
Добавьте async
в def test() как для Route1.py, так и для init.py.
Я только что решил эту проблему и обнаружил несколько особенностей входа в приложения Azure Function Apps/Application Insights.
logging.info()
или logger = logging.getLogger()
. print()
утверждения не работают. Созданные логгеры (customer_logger = logging.getLogger(__name__)
) не работают.logging.config.dictConfig(logging_config)
выдал исключение при локальном запуске с использованием func host start
.)logging.info('stuff happened in entrypoint')
сработало, но logging.info('stuff happened in route')
.async def function_name
) запускать в основном потоке в цикле событий.
Синхронные конечные точки (def function_name
) получают каждый свой собственный
нить. Таким образом, если ваш API получает 5 запросов одновременно, они не блокируют друг друга.invocation_id
(концепция Azure). Существует объект, который Azure передает с каждым запросом, который называется «контекст». «Контекст» содержит атрибут thread_local_storage
. thread_local_storage
имеет атрибут invocation_id
, и вам нужно установить его из созданного потока. Каким-то образом, как только это установлено, журналы работают.Чтобы исправить это, вам нужно сначала передать 1) объект Azure контекста и 2) родительский поток invocation_id
до вашей конечной точки, а затем из созданного потока/конечной точки вам нужно установить thread_local_storage.invocation_id
в родительский invocation_id
.
Для каждой конечной точки существует неявный параметр, называемый request
. Если вы просто определите его как параметр, вы можете использовать его в своей конечной точке и проверять в отладчике.
from fastapi import Request
@app.get("/api/test")
def hello_world(request: Request):
logging.info("hello world!") # remember print() doesn't work
Внутри этого объекта request
есть словарь scope
.
AsgiFunctionApp
/ AsgiMiddleware
Если вы используете azure.functions.AsgiFunctionApp
или azure.function.AsgiMiddleware
(определенной версии, не совсем точно, но, я думаю, более поздней, чем 1.8), контекст thread_local_storage
и родительский поток invocation_id
фактически включены в область действия для вас.
В этом случае вы должны сделать следующее в своей синхронной конечной точке (помните, что нет необходимости делать это в конечной точке async
):
from fastapi import Request
@app.get("/api/test")
def hello_world(request: Request):
thread_local_storage = request.scope.get("azure_functions.thread_local_storage")
if thread_local_storage is not None:
parent_invocation_id = request.scope.get("azure_functions.invocation_id")
thread_local_storage.invocation_id = parent_invocation_id
logging.info("hello world!") # remember print() doesn't work
Если вы используете собственное промежуточное программное обеспечение, как в этом примере здесь, вам нужно будет добавить родительский элемент invocation_id
и контекст (или объект thread_local_storage
) в область видимости.
Что-то вроде этого:
Затем вы должны установить его в своей конечной точке следующим образом:
from fastapi import Request
@app.get("/api/test")
def hello_world(request: Request):
context_obj = request.scope.get("azure_context", {}).get("context_obj")
if context_obj and hasattr(context_obj, "thread_local_storage"):
invocation_id = request.scope.get("azure_context", {}).get("invocation_id")
if invocation_id:
context_obj.thread_local_storage.invocation_id = request.scope.get("azure_context", {}
).get("invocation_id")
logging.info("hello world!") # remember print() doesn't work
Раздражает включать этот код в начало всех ваших функций, поэтому вы можете создать для него функцию-обертку (х/т Евгений Николаев)
from fastapi import Request
from functools import wraps
def logging_context_wrapper(func):
"""Passes invocation_id to the thread local context to enable proper Azure Functions logging.
Can be applied to a sync handler which has request: Request parameter - to get the context from.
"""
request_param = next(
(param for (param, annotation) in func.__annotations__.items() if annotation is Request),
None,
)
if not request_param:
raise Exception("Function must have a request parameter of type Request.")
@wraps(func)
def wrapper(*args, **kwargs):
request = kwargs[request_param]
# if you were 2b from above, sub whatever code you used
thread_local_storage = request.scope.get("azure_functions.thread_local_storage")
if thread_local_storage is not None:
thread_local_storage.invocation_id = request.scope.get("azure_functions.invocation_id")
return func(*args, **kwargs)
return wrapper
@app.get("/api/test")
@logging_context_wrapper
def hello_world(request: Request):
logging.info("hello world!") # remember print() doesn't work
print()
, просто logging.info()
, logging.warning()
и т. д.logging
, по крайней мере, пока она не заработает.def foo
, а не async def foo
) по умолчанию не работает, поскольку синхронные конечные точки выполняются в отдельных потоках.thread_local_storage.invocation_id
в созданном потоке/конечной точке на контекст invocation_id
родительского потока.print()
или созданные средства ведения журнала?PYTHON_THREADPOOL_THREAD_COUNT
. Определяет ли это, сколько потоков FastAPI ему доступно?
Единственное отличие, которое я вижу, — это событие регистрации под
def main
у вас. В этом и есть разница или я что-то еще упускаю?