[{"data":1,"prerenderedAt":1984},["ShallowReactive",2],{"nav":3,"page-\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi\u002F":580,"surround-\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi\u002F":1983},[4,186,386],{"title":5,"path":6,"stem":7,"children":8},"Advanced Pydantic Validation Serialization","\u002Fadvanced-pydantic-validation-serialization","advanced-pydantic-validation-serialization",[9,12,42,66,90,114,150,174],{"title":10,"path":6,"stem":11},"Advanced Pydantic Validation and Serialization","advanced-pydantic-validation-serialization\u002Findex",{"title":13,"path":14,"stem":15,"children":16},"Custom Validators and Field Constraints in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Findex",[17,18,24,30,36],{"title":13,"path":14,"stem":15},{"title":19,"path":20,"stem":21,"children":22},"Before, After and Wrap Validators in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fbefore-after-and-wrap-validators","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fbefore-after-and-wrap-validators\u002Findex",[23],{"title":19,"path":20,"stem":21},{"title":25,"path":26,"stem":27,"children":28},"Creating Reusable Custom Validators in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcreating-reusable-custom-validators-in-pydantic\u002Findex",[29],{"title":25,"path":26,"stem":27},{"title":31,"path":32,"stem":33,"children":34},"Cross-Field Validation Patterns in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fcross-field-validation-patterns\u002Findex",[35],{"title":31,"path":32,"stem":33},{"title":37,"path":38,"stem":39,"children":40},"Pydantic v2 Async Custom Validator: What to Do Instead","\u002Fadvanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator","advanced-pydantic-validation-serialization\u002Fcustom-validators-field-constraints\u002Fpydantic-v2-async-custom-validator\u002Findex",[41],{"title":37,"path":38,"stem":39},{"title":43,"path":44,"stem":45,"children":46},"JSON Schema Customization in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Findex",[47,48,54,60],{"title":43,"path":44,"stem":45},{"title":49,"path":50,"stem":51,"children":52},"Customizing OpenAPI Schema Generation in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fcustomizing-openapi-schema-generation-in-fastapi\u002Findex",[53],{"title":49,"path":50,"stem":51},{"title":55,"path":56,"stem":57,"children":58},"Discriminated Unions in OpenAPI with Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fdiscriminated-unions-in-openapi","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fdiscriminated-unions-in-openapi\u002Findex",[59],{"title":55,"path":56,"stem":57},{"title":61,"path":62,"stem":63,"children":64},"Examples in the OpenAPI Schema with FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fexamples-in-openapi-schema","advanced-pydantic-validation-serialization\u002Fjson-schema-customization\u002Fexamples-in-openapi-schema\u002Findex",[65],{"title":61,"path":62,"stem":63},{"title":67,"path":68,"stem":69,"children":70},"Nested Model Serialization in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Findex",[71,72,78,84],{"title":67,"path":68,"stem":69},{"title":73,"path":74,"stem":75,"children":76},"Excluding Fields Per Endpoint in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fexcluding-fields-per-endpoint","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fexcluding-fields-per-endpoint\u002Findex",[77],{"title":73,"path":74,"stem":75},{"title":79,"path":80,"stem":81,"children":82},"Handling Deeply Nested JSON Models Efficiently","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fhandling-deeply-nested-json-models-efficiently\u002Findex",[83],{"title":79,"path":80,"stem":81},{"title":85,"path":86,"stem":87,"children":88},"Self-Referencing and Recursive Models in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fself-referencing-and-recursive-models","advanced-pydantic-validation-serialization\u002Fnested-model-serialization\u002Fself-referencing-and-recursive-models\u002Findex",[89],{"title":85,"path":86,"stem":87},{"title":91,"path":92,"stem":93,"children":94},"Performance Optimization for Pydantic Models in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Findex",[95,96,102,108],{"title":91,"path":92,"stem":93},{"title":97,"path":98,"stem":99,"children":100},"model_construct and When to Skip Validation in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fmodel-construct-when-to-skip-validation","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fmodel-construct-when-to-skip-validation\u002Findex",[101],{"title":97,"path":98,"stem":99},{"title":103,"path":104,"stem":105,"children":106},"Pydantic Model Serialization Performance in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Fpydantic-model-serialization-performance\u002Findex",[107],{"title":103,"path":104,"stem":105},{"title":109,"path":110,"stem":111,"children":112},"TypeAdapter for Non-Model Types in Pydantic","\u002Fadvanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Ftypeadapter-for-non-model-types","advanced-pydantic-validation-serialization\u002Fperformance-optimization-for-models\u002Ftypeadapter-for-non-model-types\u002Findex",[113],{"title":109,"path":110,"stem":111},{"title":115,"path":116,"stem":117,"children":118},"Pydantic V2 Migration Guide for FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Findex",[119,120,126,132,138,144],{"title":115,"path":116,"stem":117},{"title":121,"path":122,"stem":123,"children":124},"Migrate @validator to @field_validator in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrate-validator-to-field-validator\u002Findex",[125],{"title":121,"path":122,"stem":123},{"title":127,"path":128,"stem":129,"children":130},"Migrating from Pydantic v1 to v2 Without Breaking APIs","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmigrating-from-pydantic-v1-to-v2-without-breaking-apis\u002Findex",[131],{"title":127,"path":128,"stem":129},{"title":133,"path":134,"stem":135,"children":136},"model_config vs class Config in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Fmodel-config-vs-class-config\u002Findex",[137],{"title":133,"path":134,"stem":135},{"title":139,"path":140,"stem":141,"children":142},"Replacing json_encoders with field_serializer in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Freplacing-json-encoders-with-field-serializer\u002Findex",[143],{"title":139,"path":140,"stem":141},{"title":145,"path":146,"stem":147,"children":148},"Migrating @root_validator to @model_validator in Pydantic v2","\u002Fadvanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator","advanced-pydantic-validation-serialization\u002Fpydantic-v2-migration-guide\u002Froot-validator-to-model-validator\u002Findex",[149],{"title":145,"path":146,"stem":147},{"title":151,"path":152,"stem":153,"children":154},"Request Validation Patterns in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Findex",[155,156,162,168],{"title":151,"path":152,"stem":153},{"title":157,"path":158,"stem":159,"children":160},"Optional vs Nullable Fields in Pydantic and FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Foptional-vs-nullable-fields\u002Findex",[161],{"title":157,"path":158,"stem":159},{"title":163,"path":164,"stem":165,"children":166},"Query, Path and Body Parameter Validation in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fquery-path-and-body-parameter-validation","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fquery-path-and-body-parameter-validation\u002Findex",[167],{"title":163,"path":164,"stem":165},{"title":169,"path":170,"stem":171,"children":172},"Validating File Uploads and Forms in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fvalidating-file-uploads-and-forms","advanced-pydantic-validation-serialization\u002Frequest-validation-patterns\u002Fvalidating-file-uploads-and-forms\u002Findex",[173],{"title":169,"path":170,"stem":171},{"title":175,"path":176,"stem":177,"children":178},"Type Hinting and IDE Integration in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Findex",[179,180],{"title":175,"path":176,"stem":177},{"title":181,"path":182,"stem":183,"children":184},"Annotated Dependencies and Reusable Types in FastAPI","\u002Fadvanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Fannotated-dependencies-and-reusable-types","advanced-pydantic-validation-serialization\u002Ftype-hinting-ide-integration\u002Fannotated-dependencies-and-reusable-types\u002Findex",[185],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189,"children":190},"Async Background Tasks Observability","\u002Fasync-background-tasks-observability","async-background-tasks-observability",[191,194,224,254,284,308,338,362],{"title":192,"path":188,"stem":193},"Async, Background Tasks, and Observability in FastAPI","async-background-tasks-observability\u002Findex",{"title":195,"path":196,"stem":197,"children":198},"Async Correctness and Concurrency in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Findex",[199,200,206,212,218],{"title":195,"path":196,"stem":197},{"title":201,"path":202,"stem":203,"children":204},"Concurrent Requests with asyncio.gather in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Fconcurrent-requests-with-asyncio-gather","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Fconcurrent-requests-with-asyncio-gather\u002Findex",[205],{"title":201,"path":202,"stem":203},{"title":207,"path":208,"stem":209,"children":210},"FastAPI async def vs def: Performance and When to Use Each","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffastapi-async-def-vs-def-performance\u002Findex",[211],{"title":207,"path":208,"stem":209},{"title":213,"path":214,"stem":215,"children":216},"Fixing Blocking Calls in Async FastAPI Routes","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Ffixing-blocking-calls-in-async-routes\u002Findex",[217],{"title":213,"path":214,"stem":215},{"title":219,"path":220,"stem":221,"children":222},"Running Sync Code in a Threadpool in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Frunning-sync-code-in-a-threadpool","async-background-tasks-observability\u002Fasync-correctness-concurrency\u002Frunning-sync-code-in-a-threadpool\u002Findex",[223],{"title":219,"path":220,"stem":221},{"title":225,"path":226,"stem":227,"children":228},"Async Database Sessions in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions","async-background-tasks-observability\u002Fasync-database-sessions\u002Findex",[229,230,236,242,248],{"title":225,"path":226,"stem":227},{"title":231,"path":232,"stem":233,"children":234},"Async SQLAlchemy Session per Request in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request","async-background-tasks-observability\u002Fasync-database-sessions\u002Fasync-sqlalchemy-session-per-request\u002Findex",[235],{"title":231,"path":232,"stem":233},{"title":237,"path":238,"stem":239,"children":240},"Fixing asyncpg Connection Pool Exhaustion in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion","async-background-tasks-observability\u002Fasync-database-sessions\u002Ffixing-asyncpg-pool-exhaustion\u002Findex",[241],{"title":237,"path":238,"stem":239},{"title":243,"path":244,"stem":245,"children":246},"Testing with Async Database Fixtures in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures","async-background-tasks-observability\u002Fasync-database-sessions\u002Ftesting-with-async-database-fixtures\u002Findex",[247],{"title":243,"path":244,"stem":245},{"title":249,"path":250,"stem":251,"children":252},"Transaction Management and Rollback in FastAPI","\u002Fasync-background-tasks-observability\u002Fasync-database-sessions\u002Ftransaction-management-and-rollback","async-background-tasks-observability\u002Fasync-database-sessions\u002Ftransaction-management-and-rollback\u002Findex",[253],{"title":249,"path":250,"stem":251},{"title":255,"path":256,"stem":257,"children":258},"Background Task Processing in FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing","async-background-tasks-observability\u002Fbackground-task-processing\u002Findex",[259,260,266,272,278],{"title":255,"path":256,"stem":257},{"title":261,"path":262,"stem":263,"children":264},"FastAPI BackgroundTasks vs Celery vs ARQ","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq","async-background-tasks-observability\u002Fbackground-task-processing\u002Ffastapi-backgroundtasks-vs-celery-vs-arq\u002Findex",[265],{"title":261,"path":262,"stem":263},{"title":267,"path":268,"stem":269,"children":270},"Retry and Idempotency for FastAPI Background Tasks","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fretry-and-idempotency-for-tasks","async-background-tasks-observability\u002Fbackground-task-processing\u002Fretry-and-idempotency-for-tasks\u002Findex",[271],{"title":267,"path":268,"stem":269},{"title":273,"path":274,"stem":275,"children":276},"Running ARQ Workers with FastAPI","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi","async-background-tasks-observability\u002Fbackground-task-processing\u002Frunning-arq-workers-with-fastapi\u002Findex",[277],{"title":273,"path":274,"stem":275},{"title":279,"path":280,"stem":281,"children":282},"When FastAPI BackgroundTasks Silently Fails","\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails","async-background-tasks-observability\u002Fbackground-task-processing\u002Fwhen-backgroundtasks-silently-fails\u002Findex",[283],{"title":279,"path":280,"stem":281},{"title":285,"path":286,"stem":287,"children":288},"Caching Strategies in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies","async-background-tasks-observability\u002Fcaching-strategies\u002Findex",[289,290,296,302],{"title":285,"path":286,"stem":287},{"title":291,"path":292,"stem":293,"children":294},"Cache Invalidation Patterns in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fcache-invalidation-patterns-in-fastapi\u002Findex",[295],{"title":291,"path":292,"stem":293},{"title":297,"path":298,"stem":299,"children":300},"Caching Dependency Results in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results","async-background-tasks-observability\u002Fcaching-strategies\u002Fcaching-dependency-results\u002Findex",[301],{"title":297,"path":298,"stem":299},{"title":303,"path":304,"stem":305,"children":306},"Redis Response Caching in FastAPI","\u002Fasync-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi","async-background-tasks-observability\u002Fcaching-strategies\u002Fredis-response-caching-in-fastapi\u002Findex",[307],{"title":303,"path":304,"stem":305},{"title":309,"path":310,"stem":311,"children":312},"Observability and Tracing in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing","async-background-tasks-observability\u002Fobservability-and-tracing\u002Findex",[313,314,320,326,332],{"title":309,"path":310,"stem":311},{"title":315,"path":316,"stem":317,"children":318},"Correlating Logs, Traces and Errors in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors\u002Findex",[319],{"title":315,"path":316,"stem":317},{"title":321,"path":322,"stem":323,"children":324},"Instrumenting FastAPI with OpenTelemetry","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry","async-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry\u002Findex",[325],{"title":321,"path":322,"stem":323},{"title":327,"path":328,"stem":329,"children":330},"Prometheus Metrics for FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi\u002Findex",[331],{"title":327,"path":328,"stem":329},{"title":333,"path":334,"stem":335,"children":336},"Structured JSON Logging with Request IDs in FastAPI","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids","async-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids\u002Findex",[337],{"title":333,"path":334,"stem":335},{"title":339,"path":340,"stem":341,"children":342},"Rate Limiting and Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Findex",[343,344,350,356],{"title":339,"path":340,"stem":341},{"title":345,"path":346,"stem":347,"children":348},"FastAPI Rate Limiting with Redis and SlowAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi","async-background-tasks-observability\u002Frate-limiting-throttling\u002Ffastapi-rate-limiting-with-redis-slowapi\u002Findex",[349],{"title":345,"path":346,"stem":347},{"title":351,"path":352,"stem":353,"children":354},"Per-User Token Bucket Throttling in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling","async-background-tasks-observability\u002Frate-limiting-throttling\u002Fper-user-token-bucket-throttling\u002Findex",[355],{"title":351,"path":352,"stem":353},{"title":357,"path":358,"stem":359,"children":360},"Rate Limit Headers and 429 Responses in FastAPI","\u002Fasync-background-tasks-observability\u002Frate-limiting-throttling\u002Frate-limit-headers-and-429-responses","async-background-tasks-observability\u002Frate-limiting-throttling\u002Frate-limit-headers-and-429-responses\u002Findex",[361],{"title":357,"path":358,"stem":359},{"title":363,"path":364,"stem":365,"children":366},"Testing FastAPI Applications","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Findex",[367,368,374,380],{"title":363,"path":364,"stem":365},{"title":369,"path":370,"stem":371,"children":372},"Mocking External Services in FastAPI Tests","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Fmocking-external-services-in-tests\u002Findex",[373],{"title":369,"path":370,"stem":371},{"title":375,"path":376,"stem":377,"children":378},"TestClient vs httpx AsyncClient in FastAPI","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftestclient-vs-httpx-asyncclient","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftestclient-vs-httpx-asyncclient\u002Findex",[379],{"title":375,"path":376,"stem":377},{"title":381,"path":382,"stem":383,"children":384},"Testing Async FastAPI Endpoints with pytest-asyncio","\u002Fasync-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftesting-async-endpoints-with-pytest-asyncio","async-background-tasks-observability\u002Ftesting-fastapi-applications\u002Ftesting-async-endpoints-with-pytest-asyncio\u002Findex",[385],{"title":381,"path":382,"stem":383},{"title":387,"path":388,"stem":389,"children":390},"Core Architecture Routing Patterns","\u002Fcore-architecture-routing-patterns","core-architecture-routing-patterns",[391,394,412,436,472,496,526,556],{"title":392,"path":388,"stem":393},"FastAPI Core Architecture and Routing Patterns","core-architecture-routing-patterns\u002Findex",{"title":395,"path":396,"stem":397,"children":398},"Application Factory Patterns in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Findex",[399,400,406],{"title":395,"path":396,"stem":397},{"title":401,"path":402,"stem":403,"children":404},"FastAPI App Factory Pattern for Testing and Deployment","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Ffastapi-app-factory-pattern-for-testing-and-deployment\u002Findex",[405],{"title":401,"path":402,"stem":403},{"title":407,"path":408,"stem":409,"children":410},"Lifespan Events vs Startup and Shutdown in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Flifespan-events-vs-startup-shutdown","core-architecture-routing-patterns\u002Fapplication-factory-patterns\u002Flifespan-events-vs-startup-shutdown\u002Findex",[411],{"title":407,"path":408,"stem":409},{"title":413,"path":414,"stem":415,"children":416},"Configuration Management in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management","core-architecture-routing-patterns\u002Fconfiguration-management\u002Findex",[417,418,424,430],{"title":413,"path":414,"stem":415},{"title":419,"path":420,"stem":421,"children":422},"Managing Environment Variables with Pydantic Settings","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fmanaging-environment-variables-with-pydantic-settings\u002Findex",[423],{"title":419,"path":420,"stem":421},{"title":425,"path":426,"stem":427,"children":428},"Pydantic Settings vs Dynaconf vs python-decouple","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fpydantic-settings-vs-dynaconf-vs-python-decouple\u002Findex",[429],{"title":425,"path":426,"stem":427},{"title":431,"path":432,"stem":433,"children":434},"Secrets and .env Files Per Environment in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fconfiguration-management\u002Fsecrets-and-env-files-per-environment","core-architecture-routing-patterns\u002Fconfiguration-management\u002Fsecrets-and-env-files-per-environment\u002Findex",[435],{"title":431,"path":432,"stem":433},{"title":437,"path":438,"stem":439,"children":440},"Dependency Injection Strategies in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Findex",[441,442,448,454,460,466],{"title":437,"path":438,"stem":439},{"title":443,"path":444,"stem":445,"children":446},"Best Practices for FastAPI Dependency Injection","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fbest-practices-for-fastapi-dependency-injection\u002Findex",[447],{"title":443,"path":444,"stem":445},{"title":449,"path":450,"stem":451,"children":452},"Dependency Caching and use_cache in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fdependency-caching-and-use-cache","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fdependency-caching-and-use-cache\u002Findex",[453],{"title":449,"path":450,"stem":451},{"title":455,"path":456,"stem":457,"children":458},"Fixing FastAPI Dependency Injection Circular Imports","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Ffastapi-dependency-injection-circular-import-fix\u002Findex",[459],{"title":455,"path":456,"stem":457},{"title":461,"path":462,"stem":463,"children":464},"Overriding Dependencies in FastAPI Tests","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Foverriding-dependencies-in-tests","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Foverriding-dependencies-in-tests\u002Findex",[465],{"title":461,"path":462,"stem":463},{"title":467,"path":468,"stem":469,"children":470},"Yield Dependencies and Cleanup Order in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fyield-dependencies-and-cleanup-order","core-architecture-routing-patterns\u002Fdependency-injection-strategies\u002Fyield-dependencies-and-cleanup-order\u002Findex",[471],{"title":467,"path":468,"stem":469},{"title":473,"path":474,"stem":475,"children":476},"Error Handling and Global Exceptions in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Findex",[477,478,484,490],{"title":473,"path":474,"stem":475},{"title":479,"path":480,"stem":481,"children":482},"Customising Validation Error Responses in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fcustomising-validation-error-responses\u002Findex",[483],{"title":479,"path":480,"stem":481},{"title":485,"path":486,"stem":487,"children":488},"Global Exception Handlers for Consistent API Responses","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fglobal-exception-handlers-for-consistent-api-responses\u002Findex",[489],{"title":485,"path":486,"stem":487},{"title":491,"path":492,"stem":493,"children":494},"HTTPException vs Custom Exception Classes in FastAPI","\u002Fcore-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fhttpexception-vs-custom-exception-classes","core-architecture-routing-patterns\u002Ferror-handling-global-exceptions\u002Fhttpexception-vs-custom-exception-classes\u002Findex",[495],{"title":491,"path":492,"stem":493},{"title":497,"path":498,"stem":499,"children":500},"Middleware Implementation in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Findex",[501,502,508,514,520],{"title":497,"path":498,"stem":499},{"title":503,"path":504,"stem":505,"children":506},"CORS Middleware Configuration in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fcors-middleware-configuration","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fcors-middleware-configuration\u002Findex",[507],{"title":503,"path":504,"stem":505},{"title":509,"path":510,"stem":511,"children":512},"Implementing Custom Middleware for Request Tracing","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing\u002Findex",[513],{"title":509,"path":510,"stem":511},{"title":515,"path":516,"stem":517,"children":518},"Middleware Execution Order in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-execution-order\u002Findex",[519],{"title":515,"path":516,"stem":517},{"title":521,"path":522,"stem":523,"children":524},"Middleware vs Dependencies: When to Use Which","\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-vs-dependencies-when-to-use-which","core-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fmiddleware-vs-dependencies-when-to-use-which\u002Findex",[525],{"title":521,"path":522,"stem":523},{"title":527,"path":528,"stem":529,"children":530},"Modular Router Organization in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Findex",[531,532,538,544,550],{"title":527,"path":528,"stem":529},{"title":533,"path":534,"stem":535,"children":536},"APIRouter Prefix vs Sub-Application Mounting in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fapirouter-prefix-vs-sub-application-mounting\u002Findex",[537],{"title":533,"path":534,"stem":535},{"title":539,"path":540,"stem":541,"children":542},"How to Structure Large FastAPI Projects for Scale","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fhow-to-structure-large-fastapi-projects-for-scale\u002Findex",[543],{"title":539,"path":540,"stem":541},{"title":545,"path":546,"stem":547,"children":548},"Router Tags and OpenAPI Grouping in FastAPI","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Frouter-tags-and-openapi-grouping","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Frouter-tags-and-openapi-grouping\u002Findex",[549],{"title":545,"path":546,"stem":547},{"title":551,"path":552,"stem":553,"children":554},"Versioning APIs with FastAPI Routers","\u002Fcore-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers","core-architecture-routing-patterns\u002Fmodular-router-organization\u002Fversioning-apis-with-routers\u002Findex",[555],{"title":551,"path":552,"stem":553},{"title":557,"path":558,"stem":559,"children":560},"The FastAPI Request\u002FResponse Lifecycle","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Findex",[561,562,568,574],{"title":557,"path":558,"stem":559},{"title":563,"path":564,"stem":565,"children":566},"How a Request Flows Through FastAPI","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fhow-a-request-flows-through-fastapi","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fhow-a-request-flows-through-fastapi\u002Findex",[567],{"title":563,"path":564,"stem":565},{"title":569,"path":570,"stem":571,"children":572},"Response Model and Serialization Order","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fresponse-model-and-serialization-order","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fresponse-model-and-serialization-order\u002Findex",[573],{"title":569,"path":570,"stem":571},{"title":575,"path":576,"stem":577,"children":578},"Streaming and File Responses in FastAPI","\u002Fcore-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses","core-architecture-routing-patterns\u002Frequest-response-lifecycle\u002Fstreaming-and-file-responses\u002Findex",[579],{"title":575,"path":576,"stem":577},{"id":581,"title":327,"body":582,"dateModified":1954,"datePublished":1954,"description":1955,"extension":1956,"faq":1957,"howto":1968,"meta":1969,"navigation":821,"path":328,"seo":1980,"stem":329,"type":1981,"__hash__":1982},"content\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi\u002Findex.md",{"type":583,"value":584,"toc":1943},"minimark",[585,589,596,632,641,742,747,762,769,776,780,786,1140,1147,1150,1521,1542,1546,1549,1556,1559,1578,1582,1589,1592,1636,1639,1643,1646,1656,1660,1663,1794,1801,1822,1826,1833,1850,1858,1865,1869,1875,1885,1891,1897,1903,1907,1939],[586,587,327],"h1",{"id":588},"prometheus-metrics-for-fastapi",[590,591,592],"p",{},[593,594,595],"strong",{},"Key takeaways:",[597,598,599,603,611,626,629],"ul",{},[600,601,602],"li",{},"RED — Rate, Errors, Duration — is three metrics: a request counter, the status label on that counter, and a latency histogram.",[600,604,605,606,610],{},"Label with the route template (",[607,608,609],"code",{},"\u002Fusers\u002F{user_id}","), never the raw path. This is the mistake that takes Prometheus down.",[600,612,613,614,617,618,617,622,625],{},"Read ",[607,615,616],{},"request.scope[\"route\"]"," ",[619,620,621],"em",{},"after",[607,623,624],{},"call_next",", or the router has not matched yet and you get the raw path anyway.",[600,627,628],{},"Pick histogram buckets around your SLO; the defaults are not tuned for your service.",[600,630,631],{},"With multiple workers, an in-process registry only ever reports one worker's numbers.",[590,633,634,635,640],{},"You can see that the service is slow but not which endpoint, or you have an error budget and nothing that measures it. This page — part of ",[636,637,639],"a",{"href":638},"\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002F","Observability and Tracing"," — covers exporting Prometheus metrics from FastAPI, and spends most of its length on the label design, because that is where production incidents come from.",[642,643,644,738],"figure",{},[645,646,654,655,654,659,654,663,654,672,654,679,654,687,654,692,654,695,654,699,654,702,654,706,654,711,654,716,654,720,654,724,654,728,654,732,654,735],"svg",{"viewBox":647,"role":648,"ariaLabelledBy":649,"xmlns":652,"style":653},"0 0 720 280","img",[650,651],"prom-title","prom-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0","\n  ",[656,657,658],"title",{"id":650},"Label cardinality: raw path versus route template",[660,661,662],"desc",{"id":651},"A comparison showing raw path labels producing one time series per user id, against a templated path label producing a single bounded series.",[664,665],"rect",{"x":666,"y":667,"width":668,"height":669,"rx":670,"style":671},"20","24","320","230","10","fill:none;stroke:currentColor;stroke-width:1.4px",[673,674,678],"text",{"x":675,"y":676,"style":677},"180","50","text-anchor:middle;fill:currentColor;font:600 14px sans-serif","path = raw URL",[664,680],{"x":681,"y":682,"width":683,"height":684,"rx":685,"style":686},"44","70","272","34","6","fill:none;stroke:currentColor;stroke-width:1.2px",[673,688,691],{"x":675,"y":689,"style":690},"92","text-anchor:middle;fill:currentColor;font:400 12px monospace","path=\"\u002Fusers\u002F8342\"",[664,693],{"x":681,"y":694,"width":683,"height":684,"rx":685,"style":686},"112",[673,696,698],{"x":675,"y":697,"style":690},"134","path=\"\u002Fusers\u002F8343\"",[664,700],{"x":681,"y":701,"width":683,"height":684,"rx":685,"style":686},"154",[673,703,705],{"x":675,"y":704,"style":690},"176","path=\"\u002Fusers\u002F8344\"",[673,707,710],{"x":675,"y":708,"style":709},"212","text-anchor:middle;fill:currentColor;font:600 13px sans-serif","one series per user",[673,712,715],{"x":675,"y":713,"style":714},"232","text-anchor:middle;fill:currentColor;font:400 12px sans-serif","unbounded growth",[664,717],{"x":718,"y":667,"width":668,"height":669,"rx":670,"style":719},"380","fill:none;stroke:#00796B;stroke-width:1.8px",[673,721,723],{"x":722,"y":676,"style":677},"540","path = route template",[664,725],{"x":726,"y":694,"width":683,"height":684,"rx":685,"style":727},"404","fill:#00796B;stroke:#00796B;stroke-width:1.2px",[673,729,731],{"x":722,"y":697,"style":730},"text-anchor:middle;fill:#ffffff;font:400 12px monospace","path=\"\u002Fusers\u002F{user_id}\"",[673,733,734],{"x":722,"y":708,"style":709},"one series per route",[673,736,737],{"x":722,"y":713,"style":714},"bounded by your code",[739,740,741],"figcaption",{},"Cardinality is decided by the label value you choose, and only the template is bounded by something you control.",[743,744,746],"h2",{"id":745},"what-to-measure","What to Measure",[590,748,749,750,753,754,757,758,761],{},"The RED method gives you three signals per endpoint, and they answer nearly every question you have during an incident. ",[593,751,752],{},"Rate"," is how many requests per second an endpoint is serving. ",[593,755,756],{},"Errors"," is how many of them failed. ",[593,759,760],{},"Duration"," is the latency distribution.",[590,763,764,765,768],{},"In Prometheus terms that is two metrics, not three. A counter with a ",[607,766,767],{},"status"," label gives you rate (its increase) and errors (the increase filtered to 5xx), and a histogram gives you duration. Everything else you might add — in-flight gauges, response size summaries, dependency call counters — is refinement on top of those two.",[590,770,771,772,775],{},"Histograms rather than summaries, for one specific reason: Prometheus can aggregate histogram buckets across instances, and cannot aggregate summary quantiles. A p99 computed per pod and then averaged is a meaningless number. ",[607,773,774],{},"histogram_quantile()"," over summed buckets is a real one.",[743,777,779],{"id":778},"the-instrumentation","The Instrumentation",[590,781,782,785],{},[607,783,784],{},"prometheus-client"," provides the metric types and the exposition format. Everything below is verified against version 0.25.0 with FastAPI 0.139.2.",[787,788,793],"pre",{"className":789,"code":790,"language":791,"meta":792,"style":792},"language-python shiki shiki-themes github-light-high-contrast","import os\nimport time\n\n# Off before prometheus_client is imported: _created series carry wall-clock timestamps, which\n# would make every scrape differ. Real deployments usually turn them off too.\nos.environ.setdefault(\"PROMETHEUS_DISABLE_CREATED_SERIES\", \"true\")\n\nfrom fastapi import FastAPI, HTTPException, Request, Response\nfrom prometheus_client import (\n    CONTENT_TYPE_LATEST,\n    CollectorRegistry,\n    Counter,\n    Histogram,\n    generate_latest,\n)\n\nREGISTRY = CollectorRegistry()\n\nREQUESTS_TOTAL = Counter(\n    \"http_requests_total\",\n    \"Total HTTP requests.\",\n    [\"method\", \"path\", \"status\"],\n    registry=REGISTRY,\n)\nREQUEST_DURATION = Histogram(\n    \"http_request_duration_seconds\",\n    \"Request latency.\",\n    [\"method\", \"path\"],\n    # Buckets chosen for a web API: dense where your SLO lives, not the library default.\n    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0),\n    registry=REGISTRY,\n)\n","python","",[607,794,795,808,816,823,830,836,855,860,874,887,897,903,909,915,921,926,931,943,948,959,967,975,997,1011,1016,1027,1035,1043,1056,1062,1124,1135],{"__ignoreMap":792},[796,797,800,804],"span",{"class":798,"line":799},"line",1,[796,801,803],{"class":802},"sTJeM","import",[796,805,807],{"class":806},"sigWx"," os\n",[796,809,811,813],{"class":798,"line":810},2,[796,812,803],{"class":802},[796,814,815],{"class":806}," time\n",[796,817,819],{"class":798,"line":818},3,[796,820,822],{"emptyLinePlaceholder":821},true,"\n",[796,824,826],{"class":798,"line":825},4,[796,827,829],{"class":828},"sFeEa","# Off before prometheus_client is imported: _created series carry wall-clock timestamps, which\n",[796,831,833],{"class":798,"line":832},5,[796,834,835],{"class":828},"# would make every scrape differ. Real deployments usually turn them off too.\n",[796,837,839,842,846,849,852],{"class":798,"line":838},6,[796,840,841],{"class":806},"os.environ.setdefault(",[796,843,845],{"class":844},"sYEJz","\"PROMETHEUS_DISABLE_CREATED_SERIES\"",[796,847,848],{"class":806},", ",[796,850,851],{"class":844},"\"true\"",[796,853,854],{"class":806},")\n",[796,856,858],{"class":798,"line":857},7,[796,859,822],{"emptyLinePlaceholder":821},[796,861,863,866,869,871],{"class":798,"line":862},8,[796,864,865],{"class":802},"from",[796,867,868],{"class":806}," fastapi ",[796,870,803],{"class":802},[796,872,873],{"class":806}," FastAPI, HTTPException, Request, Response\n",[796,875,877,879,882,884],{"class":798,"line":876},9,[796,878,865],{"class":802},[796,880,881],{"class":806}," prometheus_client ",[796,883,803],{"class":802},[796,885,886],{"class":806}," (\n",[796,888,890,894],{"class":798,"line":889},10,[796,891,893],{"class":892},"sacAq","    CONTENT_TYPE_LATEST",[796,895,896],{"class":806},",\n",[796,898,900],{"class":798,"line":899},11,[796,901,902],{"class":806},"    CollectorRegistry,\n",[796,904,906],{"class":798,"line":905},12,[796,907,908],{"class":806},"    Counter,\n",[796,910,912],{"class":798,"line":911},13,[796,913,914],{"class":806},"    Histogram,\n",[796,916,918],{"class":798,"line":917},14,[796,919,920],{"class":806},"    generate_latest,\n",[796,922,924],{"class":798,"line":923},15,[796,925,854],{"class":806},[796,927,929],{"class":798,"line":928},16,[796,930,822],{"emptyLinePlaceholder":821},[796,932,934,937,940],{"class":798,"line":933},17,[796,935,936],{"class":892},"REGISTRY",[796,938,939],{"class":802}," =",[796,941,942],{"class":806}," CollectorRegistry()\n",[796,944,946],{"class":798,"line":945},18,[796,947,822],{"emptyLinePlaceholder":821},[796,949,951,954,956],{"class":798,"line":950},19,[796,952,953],{"class":892},"REQUESTS_TOTAL",[796,955,939],{"class":802},[796,957,958],{"class":806}," Counter(\n",[796,960,962,965],{"class":798,"line":961},20,[796,963,964],{"class":844},"    \"http_requests_total\"",[796,966,896],{"class":806},[796,968,970,973],{"class":798,"line":969},21,[796,971,972],{"class":844},"    \"Total HTTP requests.\"",[796,974,896],{"class":806},[796,976,978,981,984,986,989,991,994],{"class":798,"line":977},22,[796,979,980],{"class":806},"    [",[796,982,983],{"class":844},"\"method\"",[796,985,848],{"class":806},[796,987,988],{"class":844},"\"path\"",[796,990,848],{"class":806},[796,992,993],{"class":844},"\"status\"",[796,995,996],{"class":806},"],\n",[796,998,1000,1004,1007,1009],{"class":798,"line":999},23,[796,1001,1003],{"class":1002},"sV4o_","    registry",[796,1005,1006],{"class":802},"=",[796,1008,936],{"class":892},[796,1010,896],{"class":806},[796,1012,1014],{"class":798,"line":1013},24,[796,1015,854],{"class":806},[796,1017,1019,1022,1024],{"class":798,"line":1018},25,[796,1020,1021],{"class":892},"REQUEST_DURATION",[796,1023,939],{"class":802},[796,1025,1026],{"class":806}," Histogram(\n",[796,1028,1030,1033],{"class":798,"line":1029},26,[796,1031,1032],{"class":844},"    \"http_request_duration_seconds\"",[796,1034,896],{"class":806},[796,1036,1038,1041],{"class":798,"line":1037},27,[796,1039,1040],{"class":844},"    \"Request latency.\"",[796,1042,896],{"class":806},[796,1044,1046,1048,1050,1052,1054],{"class":798,"line":1045},28,[796,1047,980],{"class":806},[796,1049,983],{"class":844},[796,1051,848],{"class":806},[796,1053,988],{"class":844},[796,1055,996],{"class":806},[796,1057,1059],{"class":798,"line":1058},29,[796,1060,1061],{"class":828},"    # Buckets chosen for a web API: dense where your SLO lives, not the library default.\n",[796,1063,1065,1068,1070,1073,1076,1078,1081,1083,1086,1088,1091,1093,1096,1098,1101,1103,1106,1108,1111,1113,1116,1118,1121],{"class":798,"line":1064},30,[796,1066,1067],{"class":1002},"    buckets",[796,1069,1006],{"class":802},[796,1071,1072],{"class":806},"(",[796,1074,1075],{"class":892},"0.005",[796,1077,848],{"class":806},[796,1079,1080],{"class":892},"0.01",[796,1082,848],{"class":806},[796,1084,1085],{"class":892},"0.025",[796,1087,848],{"class":806},[796,1089,1090],{"class":892},"0.05",[796,1092,848],{"class":806},[796,1094,1095],{"class":892},"0.1",[796,1097,848],{"class":806},[796,1099,1100],{"class":892},"0.25",[796,1102,848],{"class":806},[796,1104,1105],{"class":892},"0.5",[796,1107,848],{"class":806},[796,1109,1110],{"class":892},"1.0",[796,1112,848],{"class":806},[796,1114,1115],{"class":892},"2.5",[796,1117,848],{"class":806},[796,1119,1120],{"class":892},"5.0",[796,1122,1123],{"class":806},"),\n",[796,1125,1127,1129,1131,1133],{"class":798,"line":1126},31,[796,1128,1003],{"class":1002},[796,1130,1006],{"class":802},[796,1132,936],{"class":892},[796,1134,896],{"class":806},[796,1136,1138],{"class":798,"line":1137},32,[796,1139,854],{"class":806},[590,1141,1142,1143,1146],{},"An explicit ",[607,1144,1145],{},"CollectorRegistry"," rather than the module-level default keeps the process's own Python and platform collectors out of your app metrics, and — more practically — makes the registry something you can throw away between tests instead of fighting duplicate-timeseries errors on re-import.",[590,1148,1149],{},"The middleware records both metrics and resolves the label:",[787,1151,1153],{"className":789,"code":1152,"language":791,"meta":792,"style":792},"# Fixed latencies so the published scrape is byte-stable; production uses the real elapsed time.\n_TICKS = iter([0.004, 0.030, 0.220, 0.700])\n\napp = FastAPI()\n\n\ndef route_template(request: Request) -> str:\n    \"\"\"The label that keeps cardinality bounded: '\u002Fusers\u002F{user_id}', never '\u002Fusers\u002F8342'.\"\"\"\n    route = request.scope.get(\"route\")\n    return getattr(route, \"path\", request.url.path)\n\n\n@app.middleware(\"http\")\nasync def record_metrics(request: Request, call_next):\n    start = time.perf_counter()\n    try:\n        response = await call_next(request)\n        status = response.status_code\n    except Exception:\n        REQUESTS_TOTAL.labels(request.method, route_template(request), \"500\").inc()\n        raise\n    elapsed = time.perf_counter() - start\n    elapsed = next(_TICKS, 0.012)      # Delete this line outside the docs: it fakes the clock.\n    path = route_template(request)\n    if path != \"\u002Fmetrics\":\n        REQUESTS_TOTAL.labels(request.method, path, str(status)).inc()\n        REQUEST_DURATION.labels(request.method, path).observe(elapsed)\n    return response\n\n\n@app.get(\"\u002Fmetrics\")\nasync def metrics() -> Response:\n    return Response(generate_latest(REGISTRY), media_type=CONTENT_TYPE_LATEST)\n",[607,1154,1155,1160,1194,1198,1208,1212,1216,1234,1239,1254,1270,1274,1278,1290,1304,1314,1321,1334,1344,1354,1368,1373,1389,1413,1423,1439,1451,1459,1466,1470,1474,1486,1498],{"__ignoreMap":792},[796,1156,1157],{"class":798,"line":799},[796,1158,1159],{"class":828},"# Fixed latencies so the published scrape is byte-stable; production uses the real elapsed time.\n",[796,1161,1162,1165,1167,1170,1173,1176,1178,1181,1183,1186,1188,1191],{"class":798,"line":810},[796,1163,1164],{"class":892},"_TICKS",[796,1166,939],{"class":802},[796,1168,1169],{"class":892}," iter",[796,1171,1172],{"class":806},"([",[796,1174,1175],{"class":892},"0.004",[796,1177,848],{"class":806},[796,1179,1180],{"class":892},"0.030",[796,1182,848],{"class":806},[796,1184,1185],{"class":892},"0.220",[796,1187,848],{"class":806},[796,1189,1190],{"class":892},"0.700",[796,1192,1193],{"class":806},"])\n",[796,1195,1196],{"class":798,"line":818},[796,1197,822],{"emptyLinePlaceholder":821},[796,1199,1200,1203,1205],{"class":798,"line":825},[796,1201,1202],{"class":806},"app ",[796,1204,1006],{"class":802},[796,1206,1207],{"class":806}," FastAPI()\n",[796,1209,1210],{"class":798,"line":832},[796,1211,822],{"emptyLinePlaceholder":821},[796,1213,1214],{"class":798,"line":838},[796,1215,822],{"emptyLinePlaceholder":821},[796,1217,1218,1221,1225,1228,1231],{"class":798,"line":857},[796,1219,1220],{"class":802},"def",[796,1222,1224],{"class":1223},"s3dhs"," route_template",[796,1226,1227],{"class":806},"(request: Request) -> ",[796,1229,1230],{"class":892},"str",[796,1232,1233],{"class":806},":\n",[796,1235,1236],{"class":798,"line":862},[796,1237,1238],{"class":844},"    \"\"\"The label that keeps cardinality bounded: '\u002Fusers\u002F{user_id}', never '\u002Fusers\u002F8342'.\"\"\"\n",[796,1240,1241,1244,1246,1249,1252],{"class":798,"line":876},[796,1242,1243],{"class":806},"    route ",[796,1245,1006],{"class":802},[796,1247,1248],{"class":806}," request.scope.get(",[796,1250,1251],{"class":844},"\"route\"",[796,1253,854],{"class":806},[796,1255,1256,1259,1262,1265,1267],{"class":798,"line":889},[796,1257,1258],{"class":802},"    return",[796,1260,1261],{"class":892}," getattr",[796,1263,1264],{"class":806},"(route, ",[796,1266,988],{"class":844},[796,1268,1269],{"class":806},", request.url.path)\n",[796,1271,1272],{"class":798,"line":899},[796,1273,822],{"emptyLinePlaceholder":821},[796,1275,1276],{"class":798,"line":905},[796,1277,822],{"emptyLinePlaceholder":821},[796,1279,1280,1283,1285,1288],{"class":798,"line":911},[796,1281,1282],{"class":1223},"@app.middleware",[796,1284,1072],{"class":806},[796,1286,1287],{"class":844},"\"http\"",[796,1289,854],{"class":806},[796,1291,1292,1295,1298,1301],{"class":798,"line":917},[796,1293,1294],{"class":802},"async",[796,1296,1297],{"class":802}," def",[796,1299,1300],{"class":1223}," record_metrics",[796,1302,1303],{"class":806},"(request: Request, call_next):\n",[796,1305,1306,1309,1311],{"class":798,"line":923},[796,1307,1308],{"class":806},"    start ",[796,1310,1006],{"class":802},[796,1312,1313],{"class":806}," time.perf_counter()\n",[796,1315,1316,1319],{"class":798,"line":928},[796,1317,1318],{"class":802},"    try",[796,1320,1233],{"class":806},[796,1322,1323,1326,1328,1331],{"class":798,"line":933},[796,1324,1325],{"class":806},"        response ",[796,1327,1006],{"class":802},[796,1329,1330],{"class":802}," await",[796,1332,1333],{"class":806}," call_next(request)\n",[796,1335,1336,1339,1341],{"class":798,"line":945},[796,1337,1338],{"class":806},"        status ",[796,1340,1006],{"class":802},[796,1342,1343],{"class":806}," response.status_code\n",[796,1345,1346,1349,1352],{"class":798,"line":950},[796,1347,1348],{"class":802},"    except",[796,1350,1351],{"class":892}," Exception",[796,1353,1233],{"class":806},[796,1355,1356,1359,1362,1365],{"class":798,"line":961},[796,1357,1358],{"class":892},"        REQUESTS_TOTAL",[796,1360,1361],{"class":806},".labels(request.method, route_template(request), ",[796,1363,1364],{"class":844},"\"500\"",[796,1366,1367],{"class":806},").inc()\n",[796,1369,1370],{"class":798,"line":969},[796,1371,1372],{"class":802},"        raise\n",[796,1374,1375,1378,1380,1383,1386],{"class":798,"line":977},[796,1376,1377],{"class":806},"    elapsed ",[796,1379,1006],{"class":802},[796,1381,1382],{"class":806}," time.perf_counter() ",[796,1384,1385],{"class":802},"-",[796,1387,1388],{"class":806}," start\n",[796,1390,1391,1393,1395,1398,1400,1402,1404,1407,1410],{"class":798,"line":999},[796,1392,1377],{"class":806},[796,1394,1006],{"class":802},[796,1396,1397],{"class":892}," next",[796,1399,1072],{"class":806},[796,1401,1164],{"class":892},[796,1403,848],{"class":806},[796,1405,1406],{"class":892},"0.012",[796,1408,1409],{"class":806},")      ",[796,1411,1412],{"class":828},"# Delete this line outside the docs: it fakes the clock.\n",[796,1414,1415,1418,1420],{"class":798,"line":1013},[796,1416,1417],{"class":806},"    path ",[796,1419,1006],{"class":802},[796,1421,1422],{"class":806}," route_template(request)\n",[796,1424,1425,1428,1431,1434,1437],{"class":798,"line":1018},[796,1426,1427],{"class":802},"    if",[796,1429,1430],{"class":806}," path ",[796,1432,1433],{"class":802},"!=",[796,1435,1436],{"class":844}," \"\u002Fmetrics\"",[796,1438,1233],{"class":806},[796,1440,1441,1443,1446,1448],{"class":798,"line":1029},[796,1442,1358],{"class":892},[796,1444,1445],{"class":806},".labels(request.method, path, ",[796,1447,1230],{"class":892},[796,1449,1450],{"class":806},"(status)).inc()\n",[796,1452,1453,1456],{"class":798,"line":1037},[796,1454,1455],{"class":892},"        REQUEST_DURATION",[796,1457,1458],{"class":806},".labels(request.method, path).observe(elapsed)\n",[796,1460,1461,1463],{"class":798,"line":1045},[796,1462,1258],{"class":802},[796,1464,1465],{"class":806}," response\n",[796,1467,1468],{"class":798,"line":1058},[796,1469,822],{"emptyLinePlaceholder":821},[796,1471,1472],{"class":798,"line":1064},[796,1473,822],{"emptyLinePlaceholder":821},[796,1475,1476,1479,1481,1484],{"class":798,"line":1126},[796,1477,1478],{"class":1223},"@app.get",[796,1480,1072],{"class":806},[796,1482,1483],{"class":844},"\"\u002Fmetrics\"",[796,1485,854],{"class":806},[796,1487,1488,1490,1492,1495],{"class":798,"line":1137},[796,1489,1294],{"class":802},[796,1491,1297],{"class":802},[796,1493,1494],{"class":1223}," metrics",[796,1496,1497],{"class":806},"() -> Response:\n",[796,1499,1501,1503,1506,1508,1511,1514,1516,1519],{"class":798,"line":1500},33,[796,1502,1258],{"class":802},[796,1504,1505],{"class":806}," Response(generate_latest(",[796,1507,936],{"class":892},[796,1509,1510],{"class":806},"), ",[796,1512,1513],{"class":1002},"media_type",[796,1515,1006],{"class":802},[796,1517,1518],{"class":892},"CONTENT_TYPE_LATEST",[796,1520,854],{"class":806},[590,1522,1523,1524,1527,1528,617,1530,1533,1534,1537,1538,1541],{},"Two details in that middleware are load-bearing. The ",[607,1525,1526],{},"route_template"," call happens ",[619,1529,621],{},[607,1531,1532],{},"await call_next(request)",", because Starlette's router writes ",[607,1535,1536],{},"scope[\"route\"]"," when it matches — read it before and you are reading a key that does not exist yet, silently falling back to the raw path and creating the cardinality explosion you were trying to avoid. And the ",[607,1539,1540],{},"except"," branch counts a 500 before re-raising, so unhandled exceptions appear in the error rate rather than vanishing from it. That branch matters more than it looks: without it, the metric that drives your alerting goes quiet exactly when the service starts crashing.",[743,1543,1545],{"id":1544},"the-real-scrape","The Real Scrape",[590,1547,1548],{},"Here is the actual output of running that app with three successful requests, one 404, and then a scrape. The latencies are fixed in the example so the transcript is byte-stable; everything else is what the library emitted.",[787,1550,1554],{"className":1551,"code":1553,"language":673,"meta":792},[1552],"language-text","$ GET \u002Fusers\u002F999\n404 Not Found\n{\n  \"detail\": \"no such user\"\n}\n\n$ GET \u002Fmetrics\n200 OK\n# HELP http_requests_total Total HTTP requests.\n# TYPE http_requests_total counter\nhttp_requests_total{method=\"GET\",path=\"\u002Fusers\u002F{user_id}\",status=\"200\"} 3.0\nhttp_requests_total{method=\"GET\",path=\"\u002Fusers\u002F{user_id}\",status=\"404\"} 1.0\n# HELP http_request_duration_seconds Request latency.\n# TYPE http_request_duration_seconds histogram\nhttp_request_duration_seconds_bucket{le=\"0.005\",method=\"GET\",path=\"\u002Fusers\u002F{user_id}\"} 1.0\nhttp_request_duration_seconds_bucket{le=\"0.01\",method=\"GET\",path=\"\u002Fusers\u002F{user_id}\"} 1.0\nhttp_request_duration_seconds_bucket{le=\"0.025\",method=\"GET\",path=\"\u002Fusers\u002F{user_id}\"} 1.0\nhttp_request_duration_seconds_bucket{le=\"0.05\",method=\"GET\",path=\"\u002Fusers\u002F{user_id}\"} 2.0\nhttp_request_duration_seconds_bucket{le=\"0.1\",method=\"GET\",path=\"\u002Fusers\u002F{user_id}\"} 2.0\nhttp_request_duration_seconds_bucket{le=\"0.25\",method=\"GET\",path=\"\u002Fusers\u002F{user_id}\"} 3.0\nhttp_request_duration_seconds_bucket{le=\"0.5\",method=\"GET\",path=\"\u002Fusers\u002F{user_id}\"} 3.0\nhttp_request_duration_seconds_bucket{le=\"1.0\",method=\"GET\",path=\"\u002Fusers\u002F{user_id}\"} 4.0\nhttp_request_duration_seconds_bucket{le=\"2.5\",method=\"GET\",path=\"\u002Fusers\u002F{user_id}\"} 4.0\nhttp_request_duration_seconds_bucket{le=\"5.0\",method=\"GET\",path=\"\u002Fusers\u002F{user_id}\"} 4.0\nhttp_request_duration_seconds_bucket{le=\"+Inf\",method=\"GET\",path=\"\u002Fusers\u002F{user_id}\"} 4.0\nhttp_request_duration_seconds_count{method=\"GET\",path=\"\u002Fusers\u002F{user_id}\"} 4.0\nhttp_request_duration_seconds_sum{method=\"GET\",path=\"\u002Fusers\u002F{user_id}\"} 0.954\n",[607,1555,1553],{"__ignoreMap":792},[590,1557,1558],{},"Four requests to four different URLs produced exactly two counter series and one histogram, because the label is the template. The 404 is its own series rather than being folded into the 200s, which is what lets you alert on error ratio without alerting on traffic.",[590,1560,1561,1562,1565,1566,1569,1570,1573,1574,1577],{},"The histogram is cumulative — each ",[607,1563,1564],{},"le"," bucket counts every observation at or below that boundary, which is why the numbers only ever increase down the list and ",[607,1567,1568],{},"+Inf"," equals ",[607,1571,1572],{},"_count",". The four requests took 4ms, 30ms, 220ms and 700ms, and you can read that straight off the buckets: one under 5ms, one more by 50ms, a third by 250ms, the last by 1s. That is the whole point of picking boundaries near your SLO — with the wrong buckets, all four observations land in one bucket and ",[607,1575,1576],{},"histogram_quantile"," has nothing to interpolate between.",[743,1579,1581],{"id":1580},"cardinality-is-the-production-failure","Cardinality Is the Production Failure",[590,1583,1584,1585,1588],{},"Every unique combination of label values is a separate time series, stored, indexed and held in memory by Prometheus. A metric with a ",[607,1586,1587],{},"path"," label taking a million values is a million series from one endpoint on one instance. Multiply by replicas. This is the single most common way a well-meaning metrics change takes down a monitoring stack, and the symptom is usually Prometheus OOMing rather than anything visibly wrong with your service.",[590,1590,1591],{},"The rules that keep it bounded:",[597,1593,1594,1600,1606,1619],{},[600,1595,1596,1599],{},[593,1597,1598],{},"Never label with anything user-supplied."," No user ids, no order ids, no email addresses, no full URLs, no query strings, no free-text error messages. If you need per-user detail, that is a log line or a trace span, not a metric.",[600,1601,1602,1605],{},[593,1603,1604],{},"Label with the route template."," Bounded by the number of routes in your code, which is a number you control.",[600,1607,1608,1611,1612,1614,1615,1618],{},[593,1609,1610],{},"Watch out for 404s on unmatched paths."," When the router does not match, ",[607,1613,1536],{}," is absent and the fallback returns the raw path — which is attacker-controlled. Scanners probing random URLs will happily mint you a series per probe. If your fallback can see unmatched requests, emit a constant such as ",[607,1616,1617],{},"\"\u003Cunmatched>\""," instead.",[600,1620,1621,1624,1625,1628,1629,1628,1632,1635],{},[593,1622,1623],{},"Keep status as the code, not the reason phrase."," Three digits, bounded; and consider bucketing to ",[607,1626,1627],{},"2xx","\u002F",[607,1630,1631],{},"4xx",[607,1633,1634],{},"5xx"," if you do not need the detail.",[590,1637,1638],{},"A quick sanity check on any candidate label: can a client change its value? If yes, it does not belong in a metric.",[743,1640,1642],{"id":1641},"multiple-workers","Multiple Workers",[590,1644,1645],{},"The registry above lives in one Python process. Run uvicorn with four workers and Prometheus scrapes whichever worker the load balancer picks, getting a quarter of the traffic and none of the others — counters that appear to jump backwards as different workers answer successive scrapes.",[590,1647,1648,1649,1651,1652,1655],{},"Two ways out. ",[607,1650,784],{}," ships a multiprocess mode where each worker writes to memory-mapped files in a shared directory (",[607,1653,1654],{},"PROMETHEUS_MULTIPROC_DIR",") and the scrape endpoint aggregates across them; it works but it constrains which metric types behave sensibly, and it needs the directory cleared between deploys. The alternative, which is what most Kubernetes deployments do, is one worker per container and one scrape target per pod, letting Prometheus do the aggregation it is designed for.",[743,1657,1659],{"id":1658},"verification","Verification",[590,1661,1662],{},"The metrics endpoint is testable like anything else, and the assertion worth making is about label shape rather than values:",[787,1664,1666],{"className":789,"code":1665,"language":791,"meta":792,"style":792},"def test_metrics_use_route_templates(client):\n    client.get(\"\u002Fusers\u002F8342\")\n    body = client.get(\"\u002Fmetrics\").text\n    assert 'path=\"\u002Fusers\u002F{user_id}\"' in body     # Templated.\n    assert \"8342\" not in body                    # No user id leaked into a label.\n\n\ndef test_errors_are_counted(client):\n    client.get(\"\u002Fusers\u002F999\")\n    body = client.get(\"\u002Fmetrics\").text\n    assert 'status=\"404\"' in body\n",[607,1667,1668,1678,1688,1703,1726,1744,1748,1752,1761,1770,1782],{"__ignoreMap":792},[796,1669,1670,1672,1675],{"class":798,"line":799},[796,1671,1220],{"class":802},[796,1673,1674],{"class":1223}," test_metrics_use_route_templates",[796,1676,1677],{"class":806},"(client):\n",[796,1679,1680,1683,1686],{"class":798,"line":810},[796,1681,1682],{"class":806},"    client.get(",[796,1684,1685],{"class":844},"\"\u002Fusers\u002F8342\"",[796,1687,854],{"class":806},[796,1689,1690,1693,1695,1698,1700],{"class":798,"line":818},[796,1691,1692],{"class":806},"    body ",[796,1694,1006],{"class":802},[796,1696,1697],{"class":806}," client.get(",[796,1699,1483],{"class":844},[796,1701,1702],{"class":806},").text\n",[796,1704,1705,1708,1711,1714,1717,1720,1723],{"class":798,"line":825},[796,1706,1707],{"class":802},"    assert",[796,1709,1710],{"class":844}," 'path=\"\u002Fusers\u002F",[796,1712,1713],{"class":802},"{user_id}",[796,1715,1716],{"class":844},"\"'",[796,1718,1719],{"class":802}," in",[796,1721,1722],{"class":806}," body     ",[796,1724,1725],{"class":828},"# Templated.\n",[796,1727,1728,1730,1733,1736,1738,1741],{"class":798,"line":832},[796,1729,1707],{"class":802},[796,1731,1732],{"class":844}," \"8342\"",[796,1734,1735],{"class":802}," not",[796,1737,1719],{"class":802},[796,1739,1740],{"class":806}," body                    ",[796,1742,1743],{"class":828},"# No user id leaked into a label.\n",[796,1745,1746],{"class":798,"line":838},[796,1747,822],{"emptyLinePlaceholder":821},[796,1749,1750],{"class":798,"line":857},[796,1751,822],{"emptyLinePlaceholder":821},[796,1753,1754,1756,1759],{"class":798,"line":862},[796,1755,1220],{"class":802},[796,1757,1758],{"class":1223}," test_errors_are_counted",[796,1760,1677],{"class":806},[796,1762,1763,1765,1768],{"class":798,"line":876},[796,1764,1682],{"class":806},[796,1766,1767],{"class":844},"\"\u002Fusers\u002F999\"",[796,1769,854],{"class":806},[796,1771,1772,1774,1776,1778,1780],{"class":798,"line":889},[796,1773,1692],{"class":806},[796,1775,1006],{"class":802},[796,1777,1697],{"class":806},[796,1779,1483],{"class":844},[796,1781,1702],{"class":806},[796,1783,1784,1786,1789,1791],{"class":798,"line":899},[796,1785,1707],{"class":802},[796,1787,1788],{"class":844}," 'status=\"404\"'",[796,1790,1719],{"class":802},[796,1792,1793],{"class":806}," body\n",[590,1795,1796,1797,1800],{},"The second assertion in the first test is the one to copy into every service. It is a cardinality regression test, and it fails the moment someone adds a well-intentioned ",[607,1798,1799],{},"user_id"," label.",[590,1802,1803,1804,1807,1808,848,1811,848,1814,1817,1818,1821],{},"Beyond tests, run ",[607,1805,1806],{},"promtool check metrics"," against a scrape in CI to catch naming problems — Prometheus convention is a base unit suffix (",[607,1809,1810],{},"_seconds",[607,1812,1813],{},"_bytes",[607,1815,1816],{},"_total","), and a metric named ",[607,1819,1820],{},"request_time_ms"," will quietly confuse everyone who queries it.",[743,1823,1825],{"id":1824},"trade-offs-and-when-not-to","Trade-offs and When Not To",[590,1827,1828,1829,1832],{},"The middleware adds a fixed cost to every request: a ",[607,1830,1831],{},"perf_counter"," pair, a label lookup, and two atomic increments. That is small, but it is not free, and it is paid on the hot path.",[590,1834,1835,1836,1839,1840,1844,1845,1849],{},"Metrics are aggregates, and aggregates lose the individual request. When your alert fires, the metric tells you ",[619,1837,1838],{},"that"," the p99 on one route degraded; it cannot tell you which requests were slow or why. That is what traces and logs are for, and the way to move between them is a shared request id, covered in ",[636,1841,1843],{"href":1842},"\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors\u002F","correlating logs, traces and errors"," and ",[636,1846,1848],{"href":1847},"\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids\u002F","structured JSON logging with request IDs",".",[590,1851,1852,1853,1857],{},"If you are already running OpenTelemetry, consider its metrics API instead of a second instrumentation layer — see ",[636,1854,1856],{"href":1855},"\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry\u002F","instrumenting FastAPI with OpenTelemetry",". One pipeline emitting both traces and metrics with consistent attributes is easier to operate than two libraries with two label vocabularies that almost agree.",[590,1859,1860,1861,1864],{},"Finally, do not expose ",[607,1862,1863],{},"\u002Fmetrics"," publicly. The endpoint enumerates every route you serve, how much traffic each gets, and how often each fails — a free reconnaissance report. Internal port, ingress rule, or a dependency that checks a scrape token.",[743,1866,1868],{"id":1867},"faq","FAQ",[590,1870,1871,1874],{},[593,1872,1873],{},"Why must I label metrics with the route template instead of the URL path?","\nBecause every distinct label value creates a separate time series. Labelling with the raw path creates one series per user id, so a million users become a million series and the scrape eventually kills Prometheus. The template \u002Fusers\u002F{user_id} is one series.",[590,1876,1877,1880,1881,1884],{},[593,1878,1879],{},"How do I get the route template inside middleware?","\nRead request.scope",[796,1882,1883],{},"'route'",".path after the router has matched, which means after await call_next(request). Before that point the scope has no route key, so a metrics middleware that reads it too early sees only the raw path.",[590,1886,1887,1890],{},[593,1888,1889],{},"What histogram buckets should I use for request latency?","\nBuckets that straddle your SLO, not the library defaults. If your target is 250ms, include boundaries around it so the quantile you care about is interpolated from a dense region rather than a wide bucket.",[590,1892,1893,1896],{},[593,1894,1895],{},"Do metrics work with multiple uvicorn workers?","\nNot with the default in-process registry, because each worker has its own counters and a scrape reaches only one of them. Use prometheus-client's multiprocess mode with a shared directory, or scrape each worker on its own port.",[590,1898,1899,1902],{},[593,1900,1901],{},"Should the \u002Fmetrics endpoint be public?","\nNo. It reveals your route inventory, traffic volumes and error rates. Bind it to an internal port, restrict it at the ingress, or protect it with a dependency that checks a scrape credential.",[743,1904,1906],{"id":1905},"related-reading","Related Reading",[597,1908,1909,1916,1923,1931],{},[600,1910,1911,617,1914,1849],{},[593,1912,1913],{},"Up to the topic:",[636,1915,639],{"href":638},[600,1917,1918,617,1921,1849],{},[593,1919,1920],{},"Traces alongside metrics:",[636,1922,321],{"href":1855},[600,1924,1925,617,1928,1849],{},[593,1926,1927],{},"From an alert to a request:",[636,1929,1930],{"href":1842},"Correlating logs, traces and errors",[600,1932,1933,617,1936,1849],{},[593,1934,1935],{},"The log substrate:",[636,1937,1938],{"href":1847},"Structured JSON logging with request IDs",[1940,1941,1942],"style",{},"html pre.shiki code .sTJeM, html code.shiki .sTJeM{--shiki-default:#A0111F}html pre.shiki code .sigWx, html code.shiki .sigWx{--shiki-default:#0E1116}html pre.shiki code .sFeEa, html code.shiki .sFeEa{--shiki-default:#66707B}html pre.shiki code .sYEJz, html code.shiki .sYEJz{--shiki-default:#032563}html pre.shiki code .sacAq, html code.shiki .sacAq{--shiki-default:#023B95}html pre.shiki code .sV4o_, html code.shiki .sV4o_{--shiki-default:#702C00}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s3dhs, html code.shiki .s3dhs{--shiki-default:#622CBC}",{"title":792,"searchDepth":810,"depth":810,"links":1944},[1945,1946,1947,1948,1949,1950,1951,1952,1953],{"id":745,"depth":810,"text":746},{"id":778,"depth":810,"text":779},{"id":1544,"depth":810,"text":1545},{"id":1580,"depth":810,"text":1581},{"id":1641,"depth":810,"text":1642},{"id":1658,"depth":810,"text":1659},{"id":1824,"depth":810,"text":1825},{"id":1867,"depth":810,"text":1868},{"id":1905,"depth":810,"text":1906},"2026-07-20","Expose RED metrics from FastAPI with prometheus-client: a \u002Fmetrics endpoint, latency histogram buckets, and path labels that keep cardinality bounded.","md",[1958,1960,1962,1964,1966],{"q":1873,"a":1959},"Because every distinct label value creates a separate time series. Labelling with the raw path creates one series per user id, so a million users become a million series and the scrape eventually kills Prometheus. The template \u002Fusers\u002F{user_id} is one series.",{"q":1879,"a":1961},"Read request.scope['route'].path after the router has matched, which means after await call_next(request). Before that point the scope has no route key, so a metrics middleware that reads it too early sees only the raw path.",{"q":1889,"a":1963},"Buckets that straddle your SLO, not the library defaults. If your target is 250ms, include boundaries around it so the quantile you care about is interpolated from a dense region rather than a wide bucket.",{"q":1895,"a":1965},"Not with the default in-process registry, because each worker has its own counters and a scrape reaches only one of them. Use prometheus-client's multiprocess mode with a shared directory, or scrape each worker on its own port.",{"q":1901,"a":1967},"No. It reveals your route inventory, traffic volumes and error rates. Bind it to an internal port, restrict it at the ingress, or protect it with a dependency that checks a scrape credential.",null,{"slug":588,"breadcrumb":1970},[1971,1973,1976,1978],{"label":1972,"path":1628},"Home",{"label":1974,"path":1975},"Async, Background Tasks & Observability","\u002Fasync-background-tasks-observability\u002F",{"label":1977,"path":638},"Observability & Tracing",{"label":327,"path":1979},"\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fprometheus-metrics-for-fastapi\u002F",{"title":327,"description":1955},"article","C7XxqT9Myl185Y4iWtFZPrDUupxX1rNm8x3bVaY70Hc",[1968,1968],1784588203038]