Ensure functions have docstrings for documentation
def get(self, carrier: Headers, key: str) -> list[str] | None:
1import os2from contextlib import nullcontext3from time import perf_counter, time_ns4from typing import Any5from urllib.parse import parse_qsl, urlencode, urlsplit67from fastapi import __version__8from fastapi.telemetry._api import (9 _HTTP_METHODS,10 _REQUEST_TELEMETRY_KEY,11 TelemetryConfig,12 _exception_type,13 _normal_websocket_disconnect,14 _RequestTelemetry,15 _unconfigured,16)17from opentelemetry import _logs, metrics, propagate, trace18from opentelemetry import context as otel_context19from opentelemetry._logs import SeverityNumber20from opentelemetry.metrics import Histogram, UpDownCounter21from opentelemetry.propagators.textmap import Getter22from opentelemetry.trace import SpanKind, StatusCode23from starlette.datastructures import Headers24from starlette.types import ASGIApp, Message, Receive, Scope, Send2526_DURATION_BUCKETS = (27 0.005,28 0.01,29 0.025,30 0.05,31 0.075,32 0.1,33 0.25,34 0.5,35 0.75,36 1.0,37 2.5,38 5.0,39 7.5,40 10.0,41)42_SCHEMA_URL = "https://opentelemetry.io/schemas/1.44.0"43# https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-server-span44_SENSITIVE_QUERY_PARAMETERS = frozenset(45 {46 "X-Amz-Signature",47 "X-Amz-Credential",48 "X-Amz-Security-Token",49 "sig",50 "X-Goog-Signature",51 }52)535455class _HeadersGetter(Getter[Headers]):56 def get(self, carrier: Headers, key: str) -> list[str] | None:57 values = carrier.getlist(key)58 if not values:59 return None60 if key.lower() == "baggage":61 # The baggage propagator reads only the first value.62 return [",".join(values)]63 return values6465 def keys(self, carrier: Headers) -> list[str]:66 return carrier.keys()676869_HEADERS_GETTER = _HeadersGetter()707172def _legacy_otel(stack: Any) -> bool:73 # Test the built stack, not contrib's flag: that flag also exists when its74 # stack patch failed, or when instrument_app was called after stack creation.75 seen: set[int] = set()76 while stack is not None and id(stack) not in seen:77 seen.add(id(stack))78 if (79 type(stack).__module__ == "opentelemetry.instrumentation.asgi"80 and type(stack).__name__ == "OpenTelemetryMiddleware"81 ):82 return True83 stack = getattr(stack, "app", None)84 return False858687def _exception(*, scope: Scope, exc: BaseException) -> None:88 if scope["type"] == "websocket" and _normal_websocket_disconnect(exc):89 return90 request_telemetry = scope.get("fastapi.telemetry")91 if isinstance(request_telemetry, _RequestTelemetry):92 seen = request_telemetry._exceptions93 if not any(previous is exc for previous in seen):94 seen.append(exc)95 if request_telemetry.logger is not None and isinstance(exc, Exception):96 attributes = (97 {"http.route": request_telemetry.route}98 if request_telemetry.route99 else None100 )101 request_telemetry.logger.emit(102 exception=exc,103 event_name="fastapi.websocket.exception"104 if scope["type"] == "websocket"105 else "http.server.request.exception",106 timestamp=time_ns(),107 severity_number=SeverityNumber.ERROR,108 severity_text="ERROR",109 body="Unhandled exception in FastAPI WebSocket connection"110 if scope["type"] == "websocket"111 else "Unhandled exception in FastAPI request",112 attributes=attributes,113 )114115116def _server_attributes(scope: Scope) -> dict[str, Any]:117 # ASGI exposes HTTP/2 and HTTP/3 authority as Host. Proxy normalization is118 # handled by the server or middleware, as it is for the request URL.119 authority = Headers(scope=scope).get("host")120 if authority is not None:121 try:122 url = urlsplit("//" + authority)123 address, port = url.hostname, url.port124 except ValueError:125 # Malformed authority must not prevent handling the request.126 return {}127 if url.username is not None or url.path or url.query or url.fragment:128 return {}129 if port is None:130 port = {"http": 80, "https": 443, "ws": 80, "wss": 443}.get(131 scope.get("scheme", "http")132 )133 else:134 address, port = scope.get("server") or (None, None)135 if address is None:136 return {}137 attributes: dict[str, Any] = {"server.address": address}138 if port is not None:139 attributes["server.port"] = port140 return attributes141142143class ExceptionTelemetryMiddleware:144 """Observe exceptions before error handlers send and complete a response."""145146 def __init__(self, app: ASGIApp) -> None:147 self.app = app148149 async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:150 try:151 await self.app(scope, receive, send)152 except Exception as exc:153 _exception(scope=scope, exc=exc)154 raise155156157class NativeTelemetry:158 def __init__(self, config: TelemetryConfig) -> None:159 self.config = config160 known_methods = os.getenv("OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS", "").strip()161 self._known_methods = (162 frozenset(163 method.strip() for method in known_methods.split(",") if method.strip()164 )165 if known_methods166 else _HTTP_METHODS167 )168 self._provider: metrics.MeterProvider | None = None169 self._duration: Histogram | None = None170 self._active: UpDownCounter | None = None171 self._tracer_provider: trace.TracerProvider | None = None172 self._tracer: trace.Tracer | None = None173174 def enabled(self) -> bool:175 config = self.config176 return bool(177 (178 config["tracing"]179 and (180 config["tracer_provider"] is not None181 or not _unconfigured(trace.get_tracer_provider())182 )183 )184 or (185 config["metrics"]186 and (187 config["meter_provider"] is not None188 or not _unconfigured(metrics.get_meter_provider())189 )190 )191 or (192 config["logs"]193 and (194 config["logger_provider"] is not None195 or not _unconfigured(_logs.get_logger_provider())196 )197 )198 )199200 def _instruments(self) -> tuple[Histogram, UpDownCounter]:201 provider = self.config["meter_provider"] or metrics.get_meter_provider()202 if provider is not self._provider:203 meter = provider.get_meter("fastapi", __version__, schema_url=_SCHEMA_URL)204 self._duration = meter.create_histogram(205 "http.server.request.duration",206 unit="s",207 description="Duration of HTTP server requests.",208 explicit_bucket_boundaries_advisory=_DURATION_BUCKETS,209 )210 self._active = meter.create_up_down_counter(211 "http.server.active_requests",212 unit="{request}",213 description="Number of active HTTP server requests.",214 )215 self._provider = provider216 assert self._duration is not None and self._active is not None217 return self._duration, self._active218219 def _get_tracer(self, *, provider: trace.TracerProvider) -> trace.Tracer:220 if provider is not self._tracer_provider:221 self._tracer = provider.get_tracer(222 "fastapi", __version__, schema_url=_SCHEMA_URL223 )224 self._tracer_provider = provider225 assert self._tracer is not None226 return self._tracer227228 async def __call__(229 self,230 *,231 app: ASGIApp,232 scope: Scope,233 receive: Receive,234 send: Send,235 legacy_otel: bool = False,236 ) -> None:237 config = self.config238 if config["exclude"] is not None and config["exclude"](scope):239 # Keep mounted FastAPI apps from observing this excluded request.240 scope["fastapi.telemetry"] = None241 # An in-process request can inherit another request's telemetry.242 token = otel_context.attach(243 otel_context.set_value(_REQUEST_TELEMETRY_KEY, None)244 )245 try:246 await app(scope, receive, send)247 finally:248 otel_context.detach(token)249 scope.pop("fastapi.telemetry", None)250 return251 tracing = config["tracing"] and not legacy_otel252 logging = config["logs"] and not legacy_otel253 if logging:254 logger_provider = config["logger_provider"] or _logs.get_logger_provider()255 logging = not (256 (config["logger_provider"] is None and _unconfigured(logger_provider))257 or isinstance(logger_provider, _logs.NoOpLoggerProvider)258 )259 is_websocket = scope["type"] == "websocket"260 metering = config["metrics"] and not legacy_otel and not is_websocket261 if tracing:262 provider = config["tracer_provider"] or trace.get_tracer_provider()263 tracing = not (264 (config["tracer_provider"] is None and _unconfigured(provider))265 or isinstance(provider, trace.NoOpTracerProvider)266 )267 if metering:268 meter_provider = config["meter_provider"] or metrics.get_meter_provider()269 metering = not (270 (config["meter_provider"] is None and _unconfigured(meter_provider))271 or isinstance(meter_provider, metrics.NoOpMeterProvider)272 )273 if not tracing and not metering and not logging:274 await app(scope, receive, send)275 return276 original_method = scope.get("method", "")277 method = original_method if original_method in self._known_methods else "_OTHER"278 span_method = "WS" if is_websocket else "HTTP" if method == "_OTHER" else method279 attributes: dict[str, Any] = {280 "url.scheme": scope.get("scheme", "ws" if is_websocket else "http"),281 }282 if is_websocket:283 attributes["network.protocol.name"] = "websocket"284 else:285 attributes["http.request.method"] = method286 if scope.get("http_version"):287 attributes["network.protocol.version"] = scope["http_version"]288 duration, active = self._instruments() if metering else (None, None)289 active_attributes = {290 k: v for k, v in attributes.items() if k != "network.protocol.version"291 }292 if active is not None:293 active.add(1, active_attributes)294 started = perf_counter()295 span = None296 tracer = None297 parent_token = None298 if tracing:299 parent = propagate.extract(Headers(scope=scope), getter=_HEADERS_GETTER)300 parent_token = otel_context.attach(parent)301 tracer = self._get_tracer(provider=provider)302 span_attributes = {**attributes, **_server_attributes(scope)}303 if not is_websocket:304 span_attributes["url.path"] = scope["path"]305 if scope.get("query_string"):306 span_attributes["url.query"] = urlencode(307 [308 (309 key,310 "REDACTED"311 if key in _SENSITIVE_QUERY_PARAMETERS312 else value,313 )314 for key, value in parse_qsl(315 scope["query_string"].decode("latin-1"),316 keep_blank_values=True,317 )318 ]319 )320 if original_method != method:321 span_attributes["http.request.method_original"] = original_method322 span = tracer.start_span(323 span_method,324 context=parent,325 kind=SpanKind.SERVER,326 attributes=span_attributes,327 )328 request_telemetry = _RequestTelemetry(329 span=span,330 span_method=span_method,331 tracer=tracer if config["operation_spans"] else None,332 logger=_logs.get_logger(333 "fastapi",334 __version__,335 config["logger_provider"],336 schema_url=_SCHEMA_URL,337 )338 if logging339 else None,340 _mount_prefix=scope.get("app_root_path", scope.get("root_path", "")).rstrip(341 "/"342 ),343 )344 scope["fastapi.telemetry"] = request_telemetry345 token = otel_context.attach(346 otel_context.set_value(_REQUEST_TELEMETRY_KEY, request_telemetry)347 )348 finished = False349 trailers = False350 disconnected = False351352 def finish(error: BaseException | None = None) -> None:353 nonlocal finished354 if finished:355 return356 finished = True357 if request_telemetry.route is not None:358 attributes["http.route"] = request_telemetry.route359 if request_telemetry.status_code is not None:360 attributes["http.response.status_code"] = request_telemetry.status_code361 failed = (362 error is not None363 or not is_websocket364 and (365 request_telemetry.status_code is None366 or request_telemetry.status_code >= 500367 )368 )369 if failed:370 errors = request_telemetry._exceptions371 failure = error or (errors[-1] if errors else None)372 attributes["error.type"] = (373 _exception_type(failure)374 if failure375 else str(request_telemetry.status_code or "incomplete_response")376 )377 if span is not None:378 span.set_attributes(attributes)379 if failed:380 span.set_status(StatusCode.ERROR)381 # Record while the span is current, so exemplars can correlate it.382 if duration is not None:383 duration.record(max(0, perf_counter() - started), attributes)384 if active is not None:385 active.add(-1, active_attributes)386 if span is not None:387 span.end()388389 async def wrapped_send(message: Message) -> None:390 nonlocal trailers391 if message["type"] == "http.response.start":392 request_telemetry.status_code = message["status"]393 trailers = message.get("trailers", False)394 await send(message)395 if (396 (397 message["type"] == "http.response.body"398 and not message.get("more_body", False)399 and not trailers400 )401 or (402 message["type"] == "http.response.trailers"403 and not message.get("more_trailers", False)404 )405 or message["type"] == "http.response.pathsend"406 ):407 finish()408409 async def wrapped_receive() -> Message:410 nonlocal disconnected411 message = await receive()412 if message["type"] == "http.disconnect":413 disconnected = True414 return message415416 context = (417 trace.use_span(418 span,419 end_on_exit=False,420 record_exception=False,421 set_status_on_exception=False,422 )423 if span is not None424 else nullcontext()425 )426 try:427 with context:428 try:429 await app(430 scope,431 receive if is_websocket else wrapped_receive,432 send if is_websocket else wrapped_send,433 )434 except BaseException as exc:435 _exception(scope=scope, exc=exc)436 finish(437 None438 if is_websocket and _normal_websocket_disconnect(exc)439 else exc440 )441 raise442 finally:443 if not finished:444 finish(445 None446 if is_websocket447 else ConnectionError("Client disconnected")448 if disconnected449 else RuntimeError("Incomplete ASGI response")450 )451 finally:452 request_telemetry.data = None453 request_telemetry._exceptions.clear()454 otel_context.detach(token)455 scope.pop("fastapi.telemetry", None)456 if parent_token is not None:457 otel_context.detach(parent_token)
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.
Generated by searchcode.com · powered by code_get_file + code_get_findings