使用OpenCensus追踪FastAPI失败请求时的响应对象问题
解决FastAPI OpenCensus中间件捕获5xx异常后返回有效响应的问题
你的核心问题是:当请求处理逻辑抛出未处理异常时,中间件捕获后返回None,违反了Starlette/FastAPI对HTTP中间件必须返回合法Response对象的要求,导致后续出现"None不可调用"的错误。
解决方案步骤
- 从FastAPI导入
Response和status模块,用于构造标准的500错误响应 - 在异常捕获块中,替换返回
None为构造一个包含500状态码的Response对象 - 确保追踪Span正确结束,保证数据能正常导出到AppInsights
修改后的中间件代码
import logging from fastapi import Request, Response, status # 新增导入 from opencensus.trace import ( attributes_helper, execution_context, samplers, ) from opencensus.ext.azure.trace_exporter import AzureExporter from opencensus.trace import span as span_module from opencensus.trace import tracer as tracer_module from opencensus.trace import utils from opencensus.trace.propagation import trace_context_http_header_format from opencensus.ext.azure.log_exporter import AzureLogHandler from starlette.types import ASGIApp from src.settings import settings HTTP_HOST = attributes_helper.COMMON_ATTRIBUTES["HTTP_HOST"] HTTP_METHOD = attributes_helper.COMMON_ATTRIBUTES["HTTP_METHOD"] HTTP_PATH = attributes_helper.COMMON_ATTRIBUTES["HTTP_PATH"] HTTP_ROUTE = attributes_helper.COMMON_ATTRIBUTES["HTTP_ROUTE"] HTTP_URL = attributes_helper.COMMON_ATTRIBUTES["HTTP_URL"] HTTP_STATUS_CODE = attributes_helper.COMMON_ATTRIBUTES["HTTP_STATUS_CODE"] module_logger = logging.getLogger(__name__) module_logger.addHandler(AzureLogHandler( connection_string=settings.appinsights_connection_string )) class AppInsightsMiddleware: """ Middleware class to handle tracing of fastapi requests and exporting the data to AppInsights. Most of the code here is copied from a github issue: https://github.com/census-instrumentation/opencensus-python/issues/1020 """ def __init__( self, app: ASGIApp, excludelist_paths=None, excludelist_hostnames=None, sampler=None, exporter=None, propagator=None, ) -> None: self.app = app self.excludelist_paths = excludelist_paths self.excludelist_hostnames = excludelist_hostnames self.sampler = sampler or samplers.AlwaysOnSampler() self.propagator = ( propagator or trace_context_http_header_format.TraceContextPropagator() ) self.exporter = exporter or AzureExporter( connection_string=settings.appinsights_connection_string ) async def __call__(self, request: Request, call_next): # Do not trace if the url is in the exclude list if utils.disable_tracing_url(str(request.url), self.excludelist_paths): return await call_next(request) try: span_context = self.propagator.from_headers(request.headers) tracer = tracer_module.Tracer( span_context=span_context, sampler=self.sampler, exporter=self.exporter, propagator=self.propagator, ) except Exception: module_logger.error("Failed to trace request", exc_info=True) return await call_next(request) try: span = tracer.start_span() span.span_kind = span_module.SpanKind.SERVER span.name = "[{}]{}".format(request.method, request.url) tracer.add_attribute_to_current_span(HTTP_HOST, request.url.hostname) tracer.add_attribute_to_current_span(HTTP_METHOD, request.method) tracer.add_attribute_to_current_span(HTTP_PATH, request.url.path) tracer.add_attribute_to_current_span(HTTP_URL, str(request.url)) execution_context.set_opencensus_attr( "excludelist_hostnames", self.excludelist_hostnames ) except Exception: # pragma: NO COVER module_logger.error("Failed to trace request", exc_info=True) try: response = await call_next(request) tracer.add_attribute_to_current_span(HTTP_STATUS_CODE, response.status_code) tracer.end_span() return response # Explicitly handle any internal exception here, and set status code to 500 except Exception as exception: module_logger.exception(exception) tracer.add_attribute_to_current_span(HTTP_STATUS_CODE, 500) tracer.end_span() # 构造合法的500响应对象返回,替换原来的return None return Response( content="Internal Server Error", status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, media_type="text/plain" )
关键说明
- 必须返回
Response实例:Starlette的HTTP中间件要求__call__方法必须返回一个实现ASGI接口的响应对象,None不符合要求,因此会触发错误 - 自定义响应内容:你可以根据需求修改
content和media_type,比如返回JSON格式的错误信息:return Response( content='{"detail": "Internal Server Error"}', status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, media_type="application/json" ) - 确保追踪数据导出:在返回响应前已经调用
tracer.end_span(),保证异常请求的追踪数据能正常发送到AppInsights
内容的提问来源于stack exchange,提问作者mchristos
相关产品推荐
相关产品推荐

