通过使用Python的GraphQL库Strawberry来禁止输出日志和堆栈跟踪
在FastAPI的官方文档中介绍了GraphQL的库。
推荐的GraphQL库是Strawberry。
这次我使用Strawberry库来实现GraphQL的API时遇到了一些困难,主要是关于日志输出的问题。下面我将阐述具体问题及其解决方法。
关于草莓的事情
改写后的句子:改一改,Strawberry就是Python用的GraphQL库。Python用的GraphQL库中,Graphene是一个文档全面而且历史悠久的库。相比之下,Strawberry是一个相对较新的库。
FastAPI的文档推荐使用Strawberry。
听说它的设计理念与FastAPI很相似。
草莓仍在积极开发中,在GitHub上有频繁的提交记录。因此,作为GraphQL的功能还不够充分。就我个人而言,日志输出方面,我遇到了一些麻烦,因为草莓官方文档没有详细介绍日志输出。
输出堆栈跟踪日志
在应用程序的运维阶段,日志输出非常重要。
然而,如果按照Strawberry的官方文档来实施,那么在进行生产部署时,日志输出会被堆满堆栈跟踪信息…
这是因为FastAPI使用的ASGI服务器与Strawberry的规范存在问题。
例如,假设有一段代码来获取用户列表,如果无法获取到列表,就可以通过raise语句引发异常,然后Strawberry会捕获该异常并返回结果到GraphQL页面,而无需进行输入到模式的处理,这样处理起来简单。但是,控制台却充斥着堆栈跟踪信息。
Traceback (most recent call last):
File "/root/.cache/pypoetry/virtualenvs/app-9TtSrW0h-py3.9/lib/python3.9/site-packages/graphql/execution/execute.py", line 625, in await_result
return_type, field_nodes, info, path, await result
File "/root/.cache/pypoetry/virtualenvs/app-9TtSrW0h-py3.9/lib/python3.9/site-packages/strawberry/extensions/directives.py", line 19, in resolve
result = await await_maybe(_next(root, info, *args, **kwargs))
File "/root/.cache/pypoetry/virtualenvs/app-9TtSrW0h-py3.9/lib/python3.9/site-packages/strawberry/schema/schema_converter.py", line 392, in _resolver
return _get_result(_source, strawberry_info, **kwargs)
File "/root/.cache/pypoetry/virtualenvs/app-9TtSrW0h-py3.9/lib/python3.9/site-packages/strawberry/schema/schema_converter.py", line 384, in _get_result
由于这个问题,raises也被ASGI服务器捕捉到了。Strawberry认为这是正常处理,但在ASGI服务器看来却成了异常处理,产生了不一致的问题。
不产生堆栈跟踪信息的方法。
要想避免输出堆栈跟踪信息,需要继承Extension和GraphQLRouter来进行定制。
完成的代码如下所示。
import strawberry
from fastapi import FastAPI, Request
from strawberry.fastapi import GraphQLRouter
from strawberry.http import GraphQLHTTPResponse, process_result
from strawberry.types import ExecutionResult
from graphql.error.graphql_error import GraphQLError
from strawberry.extensions import Extension
class MyExtension(Extension):
def get_results(self):
# resolver, mutationの処理が終わるとGraphQLRouterの処理の前にここの処理が実行される
# エラーが格納されているか判定
errors = self.execution_context.result.errors
if errors is not None and len(errors) > 0:
# errosにGraphQLErrorが格納されているとGraphQLRouterの処理に移る時にトレースバックが出力されるためdataに移し替え
self.execution_context.result.data = self.execution_context.result.errors
self.execution_context.result.errors = []
class MyGraphQLRouter(GraphQLRouter):
async def process_result(
self, request: Request, result: ExecutionResult
) -> GraphQLHTTPResponse:
if type(result.data[0]) is GraphQLError:
# dataに移し替えたGraphQLErrorを再度errorsに移し替え
result.errors = result.data
result.errors[0].message = result.data
result.data = None
# GrphQLのレスポンスフォーマットに変換する
data: GraphQLHTTPResponse = process_result(result)
return data
@strawberry.type
class Query:
@strawberry.field
def users(self) -> str:
users = get_users()
if len(users) == 0:
Exception("ユーザー一覧を取得できませんでした")
return "ユーザー一覧取得しました"
schema = strawberry.Schema(query=Query, extensions=[MyExtension])
graphql_app = MyGraphQLRouter(schema)
app = FastAPI()
app.include_router(graphql_app, prefix="/graphql")
在 Extension 上,您可以在 resolver 和 mutation 处理结束后插入其他处理逻辑。
在 GraphQLRouter 上,您可以在创建响应时插入处理逻辑。
处理的顺序是先 Extension,然后是 GraphQLRouter。
同时,在 Extension 和 GraphQLRouter 之间会输出堆栈跟踪。
因此,您需要在 Extension 中处理,以避免输出堆栈跟踪。
堆栈跟踪会在 self.execution_context.result.errors 中存在错误时输出,因此您需要将这些错误转移到 self.execution_context.result.data 中。
因此,在此情况下,为了将其视为正常处理并返回响应,我们通过GraphQLRouter再次执行self.execution_context.result.errors的操作。
总结
使用FastAPI推荐的GraphQL库Strawberry时,我在日志输出方面遇到了困难,因为不断输出了堆栈跟踪信息,所以总结了一些避免方法。
希望这些技巧对你有所帮助,因为官方文档中没有提到。
随着Strawberry的开发进展,这个问题应该会得到解决…?