[{"data":1,"prerenderedAt":2348},["ShallowReactive",2],{"nav":3,"page-\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors\u002F":580,"surround-\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors\u002F":2347},[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":315,"body":582,"dateModified":2315,"datePublished":2315,"description":2316,"extension":2317,"faq":2318,"howto":2329,"meta":2330,"navigation":909,"path":316,"seo":2344,"stem":317,"type":2345,"__hash__":2346},"content\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors\u002Findex.md",{"type":583,"value":584,"toc":2305},"minimark",[585,589,596,640,649,782,787,801,811,833,837,840,1230,1237,1425,1428,1538,1541,1714,1717,1724,1727,1754,1764,1771,1775,1778,1790,1796,1845,1856,1860,1863,1996,2015,2019,2022,2180,2198,2202,2215,2218,2221,2225,2231,2237,2243,2249,2255,2259,2301],[586,587,315],"h1",{"id":588},"correlating-logs-traces-and-errors-in-fastapi",[590,591,592],"p",{},[593,594,595],"strong",{},"Key takeaways:",[597,598,599,603,618,625,637],"ul",{},[600,601,602],"li",{},"One id, set once in middleware, read by the log filter, the span attributes and the error reporter.",[600,604,605,609,610,613,614,617],{},[606,607,608],"code",{},"contextvars"," survive ",[606,611,612],{},"await"," and are copied into ",[606,615,616],{},"asyncio.create_task"," — propagation across async code is automatic.",[600,619,620,621,624],{},"Starlette's ",[606,622,623],{},"run_in_threadpool"," copies the context into the worker thread; a bare executor submit does not.",[600,626,627,628,632,633,636],{},"Context flows ",[629,630,631],"em",{},"into"," threads, never back out. A ",[606,634,635],{},"set()"," inside a thread is discarded.",[600,638,639],{},"Return the id to the client in a response header so a support ticket carries the key to your logs.",[590,641,642,643,648],{},"An alert fires, you open the error report, and you have a stack trace with no way to find the log lines that led to it or the trace that shows which downstream call was slow. This page — part of ",[644,645,647],"a",{"href":646},"\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002F","Observability and Tracing"," — is about making those three views join, and about the exact places where the id stops propagating.",[650,651,652,778],"figure",{},[653,654,662,663,662,667,662,671,662,679,662,686,662,691,662,699,662,704,662,707,662,710,662,714,662,717,662,724,662,729,662,734,662,737,662,740,662,743,662,746,662,750,662,753,662,759,662,763,662,767,662,771,662,775],"svg",{"viewBox":655,"role":656,"ariaLabelledBy":657,"xmlns":660,"style":661},"0 0 720 300","img",[658,659],"corr-title","corr-desc","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;height:auto;max-width:720px;margin:2rem 0","\n  ",[664,665,666],"title",{"id":658},"One request id shared by logs, traces and error reports",[668,669,670],"desc",{"id":659},"A contextvar set by middleware feeds the log filter, the span attributes and the error reporter, while a raw thread pool hop is shown losing the value.",[672,673],"rect",{"x":674,"y":675,"width":674,"height":676,"rx":677,"style":678},"240","24","52","9","fill:#00796B;stroke:#00796B;stroke-width:1.6px",[680,681,685],"text",{"x":682,"y":683,"style":684},"360","46","text-anchor:middle;fill:#ffffff;font:600 14px sans-serif","middleware sets ctxvar",[680,687,690],{"x":682,"y":688,"style":689},"65","text-anchor:middle;fill:#ffffff;font:400 12px sans-serif","request_id = req-1",[692,693],"line",{"x1":694,"y1":695,"x2":696,"y2":697,"style":698},"300","76","150","120","stroke:currentColor;stroke-width:1.4px",[700,701],"polygon",{"points":702,"style":703},"152,116 143,124 148,114","fill:currentColor",[692,705],{"x1":682,"y1":695,"x2":682,"y2":706,"style":698},"116",[700,708],{"points":709,"style":703},"356,116 360,126 364,116",[692,711],{"x1":712,"y1":695,"x2":713,"y2":697,"style":698},"420","570",[700,715],{"points":716,"style":703},"568,114 577,124 566,122",[672,718],{"x":719,"y":720,"width":721,"height":676,"rx":722,"style":723},"30","128","200","8","fill:none;stroke:currentColor;stroke-width:1.4px",[680,725,728],{"x":726,"y":696,"style":727},"130","text-anchor:middle;fill:currentColor;font:600 13px sans-serif","log filter",[680,730,733],{"x":726,"y":731,"style":732},"169","text-anchor:middle;fill:currentColor;font:400 12px sans-serif","every record tagged",[672,735],{"x":736,"y":720,"width":721,"height":676,"rx":722,"style":723},"260",[680,738,739],{"x":682,"y":696,"style":727},"span attributes",[680,741,742],{"x":682,"y":731,"style":732},"trace carries the id",[672,744],{"x":745,"y":720,"width":721,"height":676,"rx":722,"style":723},"490",[680,747,749],{"x":748,"y":696,"style":727},"590","error reporter",[680,751,752],{"x":748,"y":731,"style":732},"tagged at capture",[672,754],{"x":755,"y":756,"width":757,"height":676,"rx":722,"style":758},"140","216","230","fill:none;stroke:#00796B;stroke-width:1.6px",[680,760,623],{"x":761,"y":762,"style":727},"255","238",[680,764,766],{"x":761,"y":765,"style":732},"257","context copied: id kept",[672,768],{"x":769,"y":756,"width":757,"height":676,"rx":722,"style":770},"400","fill:none;stroke:currentColor;stroke-width:1.4px;stroke-dasharray:6 4",[680,772,774],{"x":773,"y":762,"style":727},"515","run_in_executor",[680,776,777],{"x":773,"y":765,"style":732},"fresh context: id lost",[779,780,781],"figcaption",{},"The same contextvar feeds all three views; the bottom row is where propagation depends on how the thread was started.",[783,784,786],"h2",{"id":785},"the-mechanism","The Mechanism",[590,788,789,790,793,794,797,798,800],{},"A ",[606,791,792],{},"ContextVar"," is not thread-local storage and it is not a global. Python maintains a current ",[606,795,796],{},"Context"," — a mapping of context variables to values — and every coroutine runs in one. When you ",[606,799,612],{},", the same context stays current, which is why a value set at the top of a handler is visible in a helper five awaits deep without threading it through any signatures.",[590,802,803,804,806,807,810],{},"When ",[606,805,616],{}," schedules a coroutine, it calls ",[606,808,809],{},"contextvars.copy_context()"," and runs the task in that copy. The task inherits everything set at creation time, and anything it sets afterwards is invisible to the parent. This is exactly the behaviour you want for a request id: children inherit, siblings do not interfere, and a task that overwrites a span id cannot corrupt the caller's.",[590,812,813,814,816,817,820,821,824,825,828,829,832],{},"Threads are the boundary. A thread starts with an empty context unless something explicitly copies one into it. Starlette's ",[606,815,623],{}," — which is what FastAPI uses for every ",[606,818,819],{},"def"," (non-async) endpoint and dependency — goes through anyio's ",[606,822,823],{},"to_thread.run_sync",", which propagates the caller's context. ",[606,826,827],{},"loop.run_in_executor"," and ",[606,830,831],{},"threading.Thread"," do not.",[783,834,836],{"id":835},"proving-where-it-survives","Proving Where It Survives",[590,838,839],{},"The example wires one contextvar for the request id and one for the current span, a logging filter that injects both into every record, and a handler that deliberately crosses each boundary.",[841,842,847],"pre",{"className":843,"code":844,"language":845,"meta":846,"style":846},"language-python shiki shiki-themes github-light-high-contrast","import asyncio\nimport json\nimport logging\nfrom concurrent.futures import ThreadPoolExecutor\nfrom contextvars import ContextVar\n\nfrom fastapi import FastAPI, Request\nfrom starlette.concurrency import run_in_threadpool\n\nrequest_id_ctx: ContextVar[str] = ContextVar(\"request_id\", default=\"-\")\nspan_ctx: ContextVar[str] = ContextVar(\"span_id\", default=\"-\")\n\n\nclass ContextFilter(logging.Filter):\n    def filter(self, record: logging.LogRecord) -> bool:\n        record.request_id = request_id_ctx.get()\n        record.span_id = span_ctx.get()\n        return True\n\n\nclass JsonFormatter(logging.Formatter):\n    def format(self, record: logging.LogRecord) -> str:\n        return json.dumps(\n            {\n                \"level\": record.levelname,\n                \"logger\": record.name,\n                \"request_id\": getattr(record, \"request_id\", \"-\"),\n                \"span_id\": getattr(record, \"span_id\", \"-\"),\n                \"message\": record.getMessage(),\n            }\n        )\n","python","",[606,848,849,861,869,877,891,904,911,924,937,942,980,1007,1012,1017,1041,1059,1070,1081,1090,1095,1100,1119,1133,1141,1147,1156,1165,1189,1209,1218,1224],{"__ignoreMap":846},[850,851,853,857],"span",{"class":692,"line":852},1,[850,854,856],{"class":855},"sTJeM","import",[850,858,860],{"class":859},"sigWx"," asyncio\n",[850,862,864,866],{"class":692,"line":863},2,[850,865,856],{"class":855},[850,867,868],{"class":859}," json\n",[850,870,872,874],{"class":692,"line":871},3,[850,873,856],{"class":855},[850,875,876],{"class":859}," logging\n",[850,878,880,883,886,888],{"class":692,"line":879},4,[850,881,882],{"class":855},"from",[850,884,885],{"class":859}," concurrent.futures ",[850,887,856],{"class":855},[850,889,890],{"class":859}," ThreadPoolExecutor\n",[850,892,894,896,899,901],{"class":692,"line":893},5,[850,895,882],{"class":855},[850,897,898],{"class":859}," contextvars ",[850,900,856],{"class":855},[850,902,903],{"class":859}," ContextVar\n",[850,905,907],{"class":692,"line":906},6,[850,908,910],{"emptyLinePlaceholder":909},true,"\n",[850,912,914,916,919,921],{"class":692,"line":913},7,[850,915,882],{"class":855},[850,917,918],{"class":859}," fastapi ",[850,920,856],{"class":855},[850,922,923],{"class":859}," FastAPI, Request\n",[850,925,927,929,932,934],{"class":692,"line":926},8,[850,928,882],{"class":855},[850,930,931],{"class":859}," starlette.concurrency ",[850,933,856],{"class":855},[850,935,936],{"class":859}," run_in_threadpool\n",[850,938,940],{"class":692,"line":939},9,[850,941,910],{"emptyLinePlaceholder":909},[850,943,945,948,952,955,958,961,965,968,972,974,977],{"class":692,"line":944},10,[850,946,947],{"class":859},"request_id_ctx: ContextVar[",[850,949,951],{"class":950},"sacAq","str",[850,953,954],{"class":859},"] ",[850,956,957],{"class":855},"=",[850,959,960],{"class":859}," ContextVar(",[850,962,964],{"class":963},"sYEJz","\"request_id\"",[850,966,967],{"class":859},", ",[850,969,971],{"class":970},"sV4o_","default",[850,973,957],{"class":855},[850,975,976],{"class":963},"\"-\"",[850,978,979],{"class":859},")\n",[850,981,983,986,988,990,992,994,997,999,1001,1003,1005],{"class":692,"line":982},11,[850,984,985],{"class":859},"span_ctx: ContextVar[",[850,987,951],{"class":950},[850,989,954],{"class":859},[850,991,957],{"class":855},[850,993,960],{"class":859},[850,995,996],{"class":963},"\"span_id\"",[850,998,967],{"class":859},[850,1000,971],{"class":970},[850,1002,957],{"class":855},[850,1004,976],{"class":963},[850,1006,979],{"class":859},[850,1008,1010],{"class":692,"line":1009},12,[850,1011,910],{"emptyLinePlaceholder":909},[850,1013,1015],{"class":692,"line":1014},13,[850,1016,910],{"emptyLinePlaceholder":909},[850,1018,1020,1023,1026,1029,1032,1035,1038],{"class":692,"line":1019},14,[850,1021,1022],{"class":855},"class",[850,1024,1025],{"class":970}," ContextFilter",[850,1027,1028],{"class":859},"(",[850,1030,1031],{"class":950},"logging",[850,1033,1034],{"class":859},".",[850,1036,1037],{"class":950},"Filter",[850,1039,1040],{"class":859},"):\n",[850,1042,1044,1047,1050,1053,1056],{"class":692,"line":1043},15,[850,1045,1046],{"class":855},"    def",[850,1048,1049],{"class":950}," filter",[850,1051,1052],{"class":859},"(self, record: logging.LogRecord) -> ",[850,1054,1055],{"class":950},"bool",[850,1057,1058],{"class":859},":\n",[850,1060,1062,1065,1067],{"class":692,"line":1061},16,[850,1063,1064],{"class":859},"        record.request_id ",[850,1066,957],{"class":855},[850,1068,1069],{"class":859}," request_id_ctx.get()\n",[850,1071,1073,1076,1078],{"class":692,"line":1072},17,[850,1074,1075],{"class":859},"        record.span_id ",[850,1077,957],{"class":855},[850,1079,1080],{"class":859}," span_ctx.get()\n",[850,1082,1084,1087],{"class":692,"line":1083},18,[850,1085,1086],{"class":855},"        return",[850,1088,1089],{"class":950}," True\n",[850,1091,1093],{"class":692,"line":1092},19,[850,1094,910],{"emptyLinePlaceholder":909},[850,1096,1098],{"class":692,"line":1097},20,[850,1099,910],{"emptyLinePlaceholder":909},[850,1101,1103,1105,1108,1110,1112,1114,1117],{"class":692,"line":1102},21,[850,1104,1022],{"class":855},[850,1106,1107],{"class":970}," JsonFormatter",[850,1109,1028],{"class":859},[850,1111,1031],{"class":950},[850,1113,1034],{"class":859},[850,1115,1116],{"class":950},"Formatter",[850,1118,1040],{"class":859},[850,1120,1122,1124,1127,1129,1131],{"class":692,"line":1121},22,[850,1123,1046],{"class":855},[850,1125,1126],{"class":950}," format",[850,1128,1052],{"class":859},[850,1130,951],{"class":950},[850,1132,1058],{"class":859},[850,1134,1136,1138],{"class":692,"line":1135},23,[850,1137,1086],{"class":855},[850,1139,1140],{"class":859}," json.dumps(\n",[850,1142,1144],{"class":692,"line":1143},24,[850,1145,1146],{"class":859},"            {\n",[850,1148,1150,1153],{"class":692,"line":1149},25,[850,1151,1152],{"class":963},"                \"level\"",[850,1154,1155],{"class":859},": record.levelname,\n",[850,1157,1159,1162],{"class":692,"line":1158},26,[850,1160,1161],{"class":963},"                \"logger\"",[850,1163,1164],{"class":859},": record.name,\n",[850,1166,1168,1171,1174,1177,1180,1182,1184,1186],{"class":692,"line":1167},27,[850,1169,1170],{"class":963},"                \"request_id\"",[850,1172,1173],{"class":859},": ",[850,1175,1176],{"class":950},"getattr",[850,1178,1179],{"class":859},"(record, ",[850,1181,964],{"class":963},[850,1183,967],{"class":859},[850,1185,976],{"class":963},[850,1187,1188],{"class":859},"),\n",[850,1190,1192,1195,1197,1199,1201,1203,1205,1207],{"class":692,"line":1191},28,[850,1193,1194],{"class":963},"                \"span_id\"",[850,1196,1173],{"class":859},[850,1198,1176],{"class":950},[850,1200,1179],{"class":859},[850,1202,996],{"class":963},[850,1204,967],{"class":859},[850,1206,976],{"class":963},[850,1208,1188],{"class":859},[850,1210,1212,1215],{"class":692,"line":1211},29,[850,1213,1214],{"class":963},"                \"message\"",[850,1216,1217],{"class":859},": record.getMessage(),\n",[850,1219,1221],{"class":692,"line":1220},30,[850,1222,1223],{"class":859},"            }\n",[850,1225,1227],{"class":692,"line":1226},31,[850,1228,1229],{"class":859},"        )\n",[590,1231,1232,1233,1236],{},"The middleware sets both values and — importantly — resets them in a ",[606,1234,1235],{},"finally",", then echoes the id back to the caller:",[841,1238,1240],{"className":843,"code":1239,"language":845,"meta":846,"style":846},"@app.middleware(\"http\")\nasync def correlate(request: Request, call_next):\n    # Production: `or f\"req-{uuid.uuid4().hex[:8]}\"`. A counter keeps this transcript stable.\n    COUNTER[\"n\"] += 1\n    rid = request.headers.get(\"x-request-id\") or f\"req-{COUNTER['n']}\"\n    token = request_id_ctx.set(rid)\n    span_token = span_ctx.set(\"span-http\")\n    try:\n        response = await call_next(request)\n        response.headers[\"x-request-id\"] = rid   # Give the caller the id to quote in a ticket.\n        return response\n    finally:\n        span_ctx.reset(span_token)\n        request_id_ctx.reset(token)\n",[606,1241,1242,1255,1269,1275,1294,1339,1349,1364,1371,1384,1401,1408,1415,1420],{"__ignoreMap":846},[850,1243,1244,1248,1250,1253],{"class":692,"line":852},[850,1245,1247],{"class":1246},"s3dhs","@app.middleware",[850,1249,1028],{"class":859},[850,1251,1252],{"class":963},"\"http\"",[850,1254,979],{"class":859},[850,1256,1257,1260,1263,1266],{"class":692,"line":863},[850,1258,1259],{"class":855},"async",[850,1261,1262],{"class":855}," def",[850,1264,1265],{"class":1246}," correlate",[850,1267,1268],{"class":859},"(request: Request, call_next):\n",[850,1270,1271],{"class":692,"line":871},[850,1272,1274],{"class":1273},"sFeEa","    # Production: `or f\"req-{uuid.uuid4().hex[:8]}\"`. A counter keeps this transcript stable.\n",[850,1276,1277,1280,1283,1286,1288,1291],{"class":692,"line":879},[850,1278,1279],{"class":950},"    COUNTER",[850,1281,1282],{"class":859},"[",[850,1284,1285],{"class":963},"\"n\"",[850,1287,954],{"class":859},[850,1289,1290],{"class":855},"+=",[850,1292,1293],{"class":950}," 1\n",[850,1295,1296,1299,1301,1304,1307,1310,1313,1316,1319,1322,1325,1327,1330,1333,1336],{"class":692,"line":893},[850,1297,1298],{"class":859},"    rid ",[850,1300,957],{"class":855},[850,1302,1303],{"class":859}," request.headers.get(",[850,1305,1306],{"class":963},"\"x-request-id\"",[850,1308,1309],{"class":859},") ",[850,1311,1312],{"class":855},"or",[850,1314,1315],{"class":855}," f",[850,1317,1318],{"class":963},"\"req-",[850,1320,1321],{"class":855},"{",[850,1323,1324],{"class":950},"COUNTER",[850,1326,1282],{"class":859},[850,1328,1329],{"class":963},"'n'",[850,1331,1332],{"class":859},"]",[850,1334,1335],{"class":855},"}",[850,1337,1338],{"class":963},"\"\n",[850,1340,1341,1344,1346],{"class":692,"line":906},[850,1342,1343],{"class":859},"    token ",[850,1345,957],{"class":855},[850,1347,1348],{"class":859}," request_id_ctx.set(rid)\n",[850,1350,1351,1354,1356,1359,1362],{"class":692,"line":913},[850,1352,1353],{"class":859},"    span_token ",[850,1355,957],{"class":855},[850,1357,1358],{"class":859}," span_ctx.set(",[850,1360,1361],{"class":963},"\"span-http\"",[850,1363,979],{"class":859},[850,1365,1366,1369],{"class":692,"line":926},[850,1367,1368],{"class":855},"    try",[850,1370,1058],{"class":859},[850,1372,1373,1376,1378,1381],{"class":692,"line":939},[850,1374,1375],{"class":859},"        response ",[850,1377,957],{"class":855},[850,1379,1380],{"class":855}," await",[850,1382,1383],{"class":859}," call_next(request)\n",[850,1385,1386,1389,1391,1393,1395,1398],{"class":692,"line":944},[850,1387,1388],{"class":859},"        response.headers[",[850,1390,1306],{"class":963},[850,1392,954],{"class":859},[850,1394,957],{"class":855},[850,1396,1397],{"class":859}," rid   ",[850,1399,1400],{"class":1273},"# Give the caller the id to quote in a ticket.\n",[850,1402,1403,1405],{"class":692,"line":982},[850,1404,1086],{"class":855},[850,1406,1407],{"class":859}," response\n",[850,1409,1410,1413],{"class":692,"line":1009},[850,1411,1412],{"class":855},"    finally",[850,1414,1058],{"class":859},[850,1416,1417],{"class":692,"line":1014},[850,1418,1419],{"class":859},"        span_ctx.reset(span_token)\n",[850,1421,1422],{"class":692,"line":1019},[850,1423,1424],{"class":859},"        request_id_ctx.reset(token)\n",[590,1426,1427],{},"The error reporter reads the same variables at capture time, which is what makes an error event joinable to the logs:",[841,1429,1431],{"className":843,"code":1430,"language":845,"meta":846,"style":846},"def capture_error(exc: Exception) -> None:\n    \"\"\"Stand-in for Sentry: attach the same id the logs and spans carry.\"\"\"\n    ERROR_REPORTS.append(\n        {\n            \"error\": f\"{type(exc).__name__}: {exc}\",\n            \"request_id\": request_id_ctx.get(),\n            \"span_id\": span_ctx.get(),\n        }\n    )\n",[606,1432,1433,1454,1459,1467,1472,1512,1520,1528,1533],{"__ignoreMap":846},[850,1434,1435,1437,1440,1443,1446,1449,1452],{"class":692,"line":852},[850,1436,819],{"class":855},[850,1438,1439],{"class":1246}," capture_error",[850,1441,1442],{"class":859},"(exc: ",[850,1444,1445],{"class":950},"Exception",[850,1447,1448],{"class":859},") -> ",[850,1450,1451],{"class":950},"None",[850,1453,1058],{"class":859},[850,1455,1456],{"class":692,"line":863},[850,1457,1458],{"class":963},"    \"\"\"Stand-in for Sentry: attach the same id the logs and spans carry.\"\"\"\n",[850,1460,1461,1464],{"class":692,"line":871},[850,1462,1463],{"class":950},"    ERROR_REPORTS",[850,1465,1466],{"class":859},".append(\n",[850,1468,1469],{"class":692,"line":879},[850,1470,1471],{"class":859},"        {\n",[850,1473,1474,1477,1479,1482,1485,1487,1490,1493,1496,1498,1500,1502,1505,1507,1509],{"class":692,"line":893},[850,1475,1476],{"class":963},"            \"error\"",[850,1478,1173],{"class":859},[850,1480,1481],{"class":855},"f",[850,1483,1484],{"class":963},"\"",[850,1486,1321],{"class":855},[850,1488,1489],{"class":950},"type",[850,1491,1492],{"class":859},"(exc).",[850,1494,1495],{"class":950},"__name__",[850,1497,1335],{"class":855},[850,1499,1173],{"class":963},[850,1501,1321],{"class":855},[850,1503,1504],{"class":859},"exc",[850,1506,1335],{"class":855},[850,1508,1484],{"class":963},[850,1510,1511],{"class":859},",\n",[850,1513,1514,1517],{"class":692,"line":906},[850,1515,1516],{"class":963},"            \"request_id\"",[850,1518,1519],{"class":859},": request_id_ctx.get(),\n",[850,1521,1522,1525],{"class":692,"line":913},[850,1523,1524],{"class":963},"            \"span_id\"",[850,1526,1527],{"class":859},": span_ctx.get(),\n",[850,1529,1530],{"class":692,"line":926},[850,1531,1532],{"class":859},"        }\n",[850,1534,1535],{"class":692,"line":939},[850,1536,1537],{"class":859},"    )\n",[590,1539,1540],{},"And the handler crosses every boundary in one request — a nested await, a child task, a Starlette threadpool call and a raw executor submit:",[841,1542,1544],{"className":843,"code":1543,"language":845,"meta":846,"style":846},"@app.get(\"\u002Fprofile\u002F{user_id}\")\nasync def profile(user_id: str) -> dict[str, object]:\n    log.info(\"handling profile request\")\n    profile_data = await load_profile(user_id)\n    # asyncio tasks copy the current context at creation time, so the id follows them.\n    child = asyncio.create_task(load_profile(user_id + \"-related\"))\n    await child\n    # Starlette's run_in_threadpool copies the context into the worker thread.\n    via_starlette = await run_in_threadpool(render_report, user_id)\n    # A bare executor submit does NOT: the callable starts with a fresh, empty context.\n    loop = asyncio.get_running_loop()\n    via_raw_executor = await loop.run_in_executor(POOL, render_report, user_id)\n    return {\"profile\": profile_data, \"reports\": [via_starlette, via_raw_executor]}\n",[606,1545,1546,1563,1590,1600,1612,1617,1636,1644,1649,1661,1666,1676,1694],{"__ignoreMap":846},[850,1547,1548,1551,1553,1556,1559,1561],{"class":692,"line":852},[850,1549,1550],{"class":1246},"@app.get",[850,1552,1028],{"class":859},[850,1554,1555],{"class":963},"\"\u002Fprofile\u002F",[850,1557,1558],{"class":855},"{user_id}",[850,1560,1484],{"class":963},[850,1562,979],{"class":859},[850,1564,1565,1567,1569,1572,1575,1577,1580,1582,1584,1587],{"class":692,"line":863},[850,1566,1259],{"class":855},[850,1568,1262],{"class":855},[850,1570,1571],{"class":1246}," profile",[850,1573,1574],{"class":859},"(user_id: ",[850,1576,951],{"class":950},[850,1578,1579],{"class":859},") -> dict[",[850,1581,951],{"class":950},[850,1583,967],{"class":859},[850,1585,1586],{"class":950},"object",[850,1588,1589],{"class":859},"]:\n",[850,1591,1592,1595,1598],{"class":692,"line":871},[850,1593,1594],{"class":859},"    log.info(",[850,1596,1597],{"class":963},"\"handling profile request\"",[850,1599,979],{"class":859},[850,1601,1602,1605,1607,1609],{"class":692,"line":879},[850,1603,1604],{"class":859},"    profile_data ",[850,1606,957],{"class":855},[850,1608,1380],{"class":855},[850,1610,1611],{"class":859}," load_profile(user_id)\n",[850,1613,1614],{"class":692,"line":893},[850,1615,1616],{"class":1273},"    # asyncio tasks copy the current context at creation time, so the id follows them.\n",[850,1618,1619,1622,1624,1627,1630,1633],{"class":692,"line":906},[850,1620,1621],{"class":859},"    child ",[850,1623,957],{"class":855},[850,1625,1626],{"class":859}," asyncio.create_task(load_profile(user_id ",[850,1628,1629],{"class":855},"+",[850,1631,1632],{"class":963}," \"-related\"",[850,1634,1635],{"class":859},"))\n",[850,1637,1638,1641],{"class":692,"line":913},[850,1639,1640],{"class":855},"    await",[850,1642,1643],{"class":859}," child\n",[850,1645,1646],{"class":692,"line":926},[850,1647,1648],{"class":1273},"    # Starlette's run_in_threadpool copies the context into the worker thread.\n",[850,1650,1651,1654,1656,1658],{"class":692,"line":939},[850,1652,1653],{"class":859},"    via_starlette ",[850,1655,957],{"class":855},[850,1657,1380],{"class":855},[850,1659,1660],{"class":859}," run_in_threadpool(render_report, user_id)\n",[850,1662,1663],{"class":692,"line":944},[850,1664,1665],{"class":1273},"    # A bare executor submit does NOT: the callable starts with a fresh, empty context.\n",[850,1667,1668,1671,1673],{"class":692,"line":982},[850,1669,1670],{"class":859},"    loop ",[850,1672,957],{"class":855},[850,1674,1675],{"class":859}," asyncio.get_running_loop()\n",[850,1677,1678,1681,1683,1685,1688,1691],{"class":692,"line":1009},[850,1679,1680],{"class":859},"    via_raw_executor ",[850,1682,957],{"class":855},[850,1684,1380],{"class":855},[850,1686,1687],{"class":859}," loop.run_in_executor(",[850,1689,1690],{"class":950},"POOL",[850,1692,1693],{"class":859},", render_report, user_id)\n",[850,1695,1696,1699,1702,1705,1708,1711],{"class":692,"line":1014},[850,1697,1698],{"class":855},"    return",[850,1700,1701],{"class":859}," {",[850,1703,1704],{"class":963},"\"profile\"",[850,1706,1707],{"class":859},": profile_data, ",[850,1709,1710],{"class":963},"\"reports\"",[850,1712,1713],{"class":859},": [via_starlette, via_raw_executor]}\n",[590,1715,1716],{},"This is the real log sequence that request produced, followed by a second request that failed and was reported:",[841,1718,1722],{"className":1719,"code":1721,"language":680,"meta":846},[1720],"language-text","$ GET \u002Flogs\n200 OK\n{\n  \"log_lines\": [\n    {\n      \"level\": \"INFO\",\n      \"logger\": \"app\",\n      \"request_id\": \"req-1\",\n      \"span_id\": \"span-http\",\n      \"message\": \"handling profile request\"\n    },\n    {\n      \"level\": \"INFO\",\n      \"logger\": \"app\",\n      \"request_id\": \"req-1\",\n      \"span_id\": \"span-db\",\n      \"message\": \"SELECT profile user_id=u-42\"\n    },\n    {\n      \"level\": \"INFO\",\n      \"logger\": \"app\",\n      \"request_id\": \"req-1\",\n      \"span_id\": \"span-db\",\n      \"message\": \"SELECT profile user_id=u-42-related\"\n    },\n    {\n      \"level\": \"INFO\",\n      \"logger\": \"app\",\n      \"request_id\": \"req-1\",\n      \"span_id\": \"span-http\",\n      \"message\": \"rendering report for u-42\"\n    },\n    {\n      \"level\": \"INFO\",\n      \"logger\": \"app\",\n      \"request_id\": \"-\",\n      \"span_id\": \"-\",\n      \"message\": \"rendering report for u-42\"\n    },\n    {\n      \"level\": \"ERROR\",\n      \"logger\": \"app\",\n      \"request_id\": \"req-2\",\n      \"span_id\": \"span-http\",\n      \"message\": \"request failed\"\n    }\n  ],\n  \"errors\": [\n    {\n      \"error\": \"ValueError: downstream billing service returned 502\",\n      \"request_id\": \"req-2\",\n      \"span_id\": \"span-http\"\n    }\n  ]\n}\n",[606,1723,1721],{"__ignoreMap":846},[590,1725,1726],{},"Every line of that sequence is worth reading closely.",[590,1728,1729,1730,1733,1734,1736,1737,1739,1740,1743,1744,1747,1748,1750,1751,1753],{},"Lines one to four all carry ",[606,1731,1732],{},"req-1",". The nested ",[606,1735,612],{}," kept it. The ",[606,1738,616],{}," child kept it, and it also carried the ",[606,1741,1742],{},"span-db"," value the child set for itself — while line four, back in the handler, is ",[606,1745,1746],{},"span-http"," again, because the child's ",[606,1749,635],{}," happened in its own copy of the context. The ",[606,1752,623],{}," call kept the id too, which is the behaviour that makes correlation work automatically for synchronous endpoints and dependencies.",[590,1755,1756,1757,1759,1760,1763],{},"Line five is the failure. Identical function, identical log call, dispatched through ",[606,1758,827],{}," — and both fields are ",[606,1761,1762],{},"-",". There is nothing in that log line to tie it to the request that caused it. In a real system this is the log entry you find during an incident, containing exactly the message you needed and none of the context required to use it.",[590,1765,1766,1767,1770],{},"Line six comes from the second request and carries ",[606,1768,1769],{},"req-2",", matching the error report captured underneath. That match is the payoff: the error event and the log lines share a key, so one click goes from the exception to the request's full log context.",[783,1772,1774],{"id":1773},"fixing-the-threadpool-hop","Fixing the Threadpool Hop",[590,1776,1777],{},"Three options, in order of preference.",[590,1779,1780,1785,1786,1034],{},[593,1781,1782,1783,1034],{},"Use ",[606,1784,623],{}," If you are offloading blocking work from an async endpoint, this is the right primitive anyway — it uses the same limiter as FastAPI's own sync-endpoint dispatch, so your offloaded work is subject to the same concurrency bound. Context propagation comes free. The concurrency implications are covered in ",[644,1787,1789],{"href":1788},"\u002Fasync-background-tasks-observability\u002Fasync-correctness-concurrency\u002Frunning-sync-code-in-a-threadpool\u002F","running sync code in a threadpool",[590,1791,1792,1795],{},[593,1793,1794],{},"Copy the context explicitly."," When you must use a specific executor — a dedicated pool for CPU-bound work, say — copy the context and run the callable inside it:",[841,1797,1799],{"className":843,"code":1798,"language":845,"meta":846,"style":846},"import contextvars\nimport functools\n\nctx = contextvars.copy_context()\nresult = await loop.run_in_executor(POOL, functools.partial(ctx.run, render_report, user_id))\n",[606,1800,1801,1808,1815,1819,1829],{"__ignoreMap":846},[850,1802,1803,1805],{"class":692,"line":852},[850,1804,856],{"class":855},[850,1806,1807],{"class":859}," contextvars\n",[850,1809,1810,1812],{"class":692,"line":863},[850,1811,856],{"class":855},[850,1813,1814],{"class":859}," functools\n",[850,1816,1817],{"class":692,"line":871},[850,1818,910],{"emptyLinePlaceholder":909},[850,1820,1821,1824,1826],{"class":692,"line":879},[850,1822,1823],{"class":859},"ctx ",[850,1825,957],{"class":855},[850,1827,1828],{"class":859}," contextvars.copy_context()\n",[850,1830,1831,1834,1836,1838,1840,1842],{"class":692,"line":893},[850,1832,1833],{"class":859},"result ",[850,1835,957],{"class":855},[850,1837,1380],{"class":855},[850,1839,1687],{"class":859},[850,1841,1690],{"class":950},[850,1843,1844],{"class":859},", functools.partial(ctx.run, render_report, user_id))\n",[590,1846,1847,1850,1851,1855],{},[593,1848,1849],{},"Pass the id as an argument."," Unavoidable when the boundary is a process rather than a thread, and the only option for background jobs. A Celery or ARQ worker is a different process with a different context; the id must travel in the job payload and be re-set at the top of the worker function. That is the same discipline described in ",[644,1852,1854],{"href":1853},"\u002Fasync-background-tasks-observability\u002Fbackground-task-processing\u002Fretry-and-idempotency-for-tasks\u002F","retry and idempotency for tasks"," — and it is why a background task failure often has no request id unless you deliberately forwarded one.",[783,1857,1859],{"id":1858},"joining-to-traces","Joining to Traces",[590,1861,1862],{},"If you are running OpenTelemetry, the span context already carries a trace id and a span id, and the cheapest correlation is to put them on every log record:",[841,1864,1866],{"className":843,"code":1865,"language":845,"meta":846,"style":846},"from opentelemetry import trace\n\n\nclass TraceFilter(logging.Filter):\n    def filter(self, record: logging.LogRecord) -> bool:\n        span = trace.get_current_span()\n        ctx = span.get_span_context()\n        record.trace_id = format(ctx.trace_id, \"032x\") if ctx.is_valid else \"-\"\n        record.span_id = format(ctx.span_id, \"016x\") if ctx.is_valid else \"-\"\n        return True\n",[606,1867,1868,1880,1884,1888,1905,1917,1927,1937,1966,1990],{"__ignoreMap":846},[850,1869,1870,1872,1875,1877],{"class":692,"line":852},[850,1871,882],{"class":855},[850,1873,1874],{"class":859}," opentelemetry ",[850,1876,856],{"class":855},[850,1878,1879],{"class":859}," trace\n",[850,1881,1882],{"class":692,"line":863},[850,1883,910],{"emptyLinePlaceholder":909},[850,1885,1886],{"class":692,"line":871},[850,1887,910],{"emptyLinePlaceholder":909},[850,1889,1890,1892,1895,1897,1899,1901,1903],{"class":692,"line":879},[850,1891,1022],{"class":855},[850,1893,1894],{"class":970}," TraceFilter",[850,1896,1028],{"class":859},[850,1898,1031],{"class":950},[850,1900,1034],{"class":859},[850,1902,1037],{"class":950},[850,1904,1040],{"class":859},[850,1906,1907,1909,1911,1913,1915],{"class":692,"line":893},[850,1908,1046],{"class":855},[850,1910,1049],{"class":950},[850,1912,1052],{"class":859},[850,1914,1055],{"class":950},[850,1916,1058],{"class":859},[850,1918,1919,1922,1924],{"class":692,"line":906},[850,1920,1921],{"class":859},"        span ",[850,1923,957],{"class":855},[850,1925,1926],{"class":859}," trace.get_current_span()\n",[850,1928,1929,1932,1934],{"class":692,"line":913},[850,1930,1931],{"class":859},"        ctx ",[850,1933,957],{"class":855},[850,1935,1936],{"class":859}," span.get_span_context()\n",[850,1938,1939,1942,1944,1946,1949,1952,1954,1957,1960,1963],{"class":692,"line":926},[850,1940,1941],{"class":859},"        record.trace_id ",[850,1943,957],{"class":855},[850,1945,1126],{"class":950},[850,1947,1948],{"class":859},"(ctx.trace_id, ",[850,1950,1951],{"class":963},"\"032x\"",[850,1953,1309],{"class":859},[850,1955,1956],{"class":855},"if",[850,1958,1959],{"class":859}," ctx.is_valid ",[850,1961,1962],{"class":855},"else",[850,1964,1965],{"class":963}," \"-\"\n",[850,1967,1968,1970,1972,1974,1977,1980,1982,1984,1986,1988],{"class":692,"line":939},[850,1969,1075],{"class":859},[850,1971,957],{"class":855},[850,1973,1126],{"class":950},[850,1975,1976],{"class":859},"(ctx.span_id, ",[850,1978,1979],{"class":963},"\"016x\"",[850,1981,1309],{"class":859},[850,1983,1956],{"class":855},[850,1985,1959],{"class":859},[850,1987,1962],{"class":855},[850,1989,1965],{"class":963},[850,1991,1992,1994],{"class":692,"line":944},[850,1993,1086],{"class":855},[850,1995,1089],{"class":950},[590,1997,1998,1999,2001,2002,2005,2006,2010,2011,1034],{},"OpenTelemetry propagates its span context through the same ",[606,2000,608],{}," machinery, so everything above applies unchanged — including the threadpool caveat. Setting the request id as a span attribute (",[606,2003,2004],{},"span.set_attribute(\"request.id\", rid)",") closes the loop in the other direction, letting you find the trace from a log line. The instrumentation itself is covered in ",[644,2007,2009],{"href":2008},"\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Finstrumenting-fastapi-with-opentelemetry\u002F","instrumenting FastAPI with OpenTelemetry",", and the formatter and filter plumbing in ",[644,2012,2014],{"href":2013},"\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fstructured-json-logging-with-request-ids\u002F","structured JSON logging with request IDs",[783,2016,2018],{"id":2017},"verification","Verification",[590,2020,2021],{},"The test that matters asserts propagation across the boundary you actually cross:",[841,2023,2025],{"className":843,"code":2024,"language":845,"meta":846,"style":846},"def test_request_id_reaches_threadpool_code(client, caplog):\n    client.get(\"\u002Fprofile\u002Fu-42\", headers={\"x-request-id\": \"rid-7\"})\n    ids = {getattr(record, \"request_id\", None) for record in caplog.records}\n    assert ids == {\"rid-7\"}          # No record escaped without the id.\n\n\ndef test_response_echoes_the_id(client):\n    response = client.get(\"\u002Fprofile\u002Fu-42\", headers={\"x-request-id\": \"rid-7\"})\n    assert response.headers[\"x-request-id\"] == \"rid-7\"\n",[606,2026,2027,2037,2064,2097,2118,2122,2126,2136,2164],{"__ignoreMap":846},[850,2028,2029,2031,2034],{"class":692,"line":852},[850,2030,819],{"class":855},[850,2032,2033],{"class":1246}," test_request_id_reaches_threadpool_code",[850,2035,2036],{"class":859},"(client, caplog):\n",[850,2038,2039,2042,2045,2047,2050,2052,2054,2056,2058,2061],{"class":692,"line":863},[850,2040,2041],{"class":859},"    client.get(",[850,2043,2044],{"class":963},"\"\u002Fprofile\u002Fu-42\"",[850,2046,967],{"class":859},[850,2048,2049],{"class":970},"headers",[850,2051,957],{"class":855},[850,2053,1321],{"class":859},[850,2055,1306],{"class":963},[850,2057,1173],{"class":859},[850,2059,2060],{"class":963},"\"rid-7\"",[850,2062,2063],{"class":859},"})\n",[850,2065,2066,2069,2071,2073,2075,2077,2079,2081,2083,2085,2088,2091,2094],{"class":692,"line":871},[850,2067,2068],{"class":859},"    ids ",[850,2070,957],{"class":855},[850,2072,1701],{"class":859},[850,2074,1176],{"class":950},[850,2076,1179],{"class":859},[850,2078,964],{"class":963},[850,2080,967],{"class":859},[850,2082,1451],{"class":950},[850,2084,1309],{"class":859},[850,2086,2087],{"class":855},"for",[850,2089,2090],{"class":859}," record ",[850,2092,2093],{"class":855},"in",[850,2095,2096],{"class":859}," caplog.records}\n",[850,2098,2099,2102,2105,2108,2110,2112,2115],{"class":692,"line":879},[850,2100,2101],{"class":855},"    assert",[850,2103,2104],{"class":859}," ids ",[850,2106,2107],{"class":855},"==",[850,2109,1701],{"class":859},[850,2111,2060],{"class":963},[850,2113,2114],{"class":859},"}          ",[850,2116,2117],{"class":1273},"# No record escaped without the id.\n",[850,2119,2120],{"class":692,"line":893},[850,2121,910],{"emptyLinePlaceholder":909},[850,2123,2124],{"class":692,"line":906},[850,2125,910],{"emptyLinePlaceholder":909},[850,2127,2128,2130,2133],{"class":692,"line":913},[850,2129,819],{"class":855},[850,2131,2132],{"class":1246}," test_response_echoes_the_id",[850,2134,2135],{"class":859},"(client):\n",[850,2137,2138,2141,2143,2146,2148,2150,2152,2154,2156,2158,2160,2162],{"class":692,"line":926},[850,2139,2140],{"class":859},"    response ",[850,2142,957],{"class":855},[850,2144,2145],{"class":859}," client.get(",[850,2147,2044],{"class":963},[850,2149,967],{"class":859},[850,2151,2049],{"class":970},[850,2153,957],{"class":855},[850,2155,1321],{"class":859},[850,2157,1306],{"class":963},[850,2159,1173],{"class":859},[850,2161,2060],{"class":963},[850,2163,2063],{"class":859},[850,2165,2166,2168,2171,2173,2175,2177],{"class":692,"line":939},[850,2167,2101],{"class":855},[850,2169,2170],{"class":859}," response.headers[",[850,2172,1306],{"class":963},[850,2174,954],{"class":859},[850,2176,2107],{"class":855},[850,2178,2179],{"class":963}," \"rid-7\"\n",[590,2181,2182,2183,2186,2187,2189,2190,2193,2194,2197],{},"The first assertion is deliberately strict: comparing the ",[629,2184,2185],{},"set"," of ids to a single value catches the ",[606,2188,1762],{}," records that a lost context produces, which an ",[606,2191,2192],{},"assert \"rid-7\" in ids"," would not. In production the equivalent check is a dashboard panel counting log lines where ",[606,2195,2196],{},"request_id"," is empty. That number should be zero, and when it is not, it points straight at a code path that crossed a boundary without carrying context.",[783,2199,2201],{"id":2200},"trade-offs-and-when-not-to","Trade-offs and When Not To",[590,2203,2204,2205,2207,2208,2211,2212,2214],{},"Resetting the contextvar in a ",[606,2206,1235],{}," is not optional. Skip it and the value survives into whatever the worker handles next, so a request with no id inherits the previous one's — worse than no correlation, because it is confidently wrong. The ",[606,2209,2210],{},"Token"," returned by ",[606,2213,635],{}," exists for exactly this.",[590,2216,2217],{},"Do not put large objects in contextvars. They are cheap to read and they keep whatever you store alive for the lifetime of the context, so a request body or an ORM object parked there is a memory leak that scales with concurrency. Store the id and look everything else up.",[590,2219,2220],{},"And accept that correlation ids are not free at scale: they add a field to every log line and a tag to every error event. That cost is trivial next to the alternative, which is an incident spent grepping timestamps.",[783,2222,2224],{"id":2223},"faq","FAQ",[590,2226,2227,2230],{},[593,2228,2229],{},"Do contextvars survive an await?","\nYes. A coroutine runs in the context that was current when it was scheduled, so a value set before an await is still readable after it. asyncio.create_task copies the context at creation time, so tasks inherit the values that existed at that moment.",[590,2232,2233,2236],{},[593,2234,2235],{},"Why does my request id disappear inside a threadpool call?","\nIt depends on how the thread was started. Starlette's run_in_threadpool copies the current context into the worker thread, so the id survives. A bare loop.run_in_executor or a raw threading.Thread starts with an empty context and the id is gone.",[590,2238,2239,2242],{},[593,2240,2241],{},"Can a background thread write back to a contextvar the request can read?","\nNo. The thread gets a copy of the context, so any set() it performs applies to that copy and is discarded when the callable returns. Values flow in, never out; return the value instead.",[590,2244,2245,2248],{},[593,2246,2247],{},"Should the request id be the same as the trace id?","\nThey can be, and using the trace id as the correlation id removes a mapping step. Keep a separate header when clients or a gateway generate their own id, and log both fields so either one finds the request.",[590,2250,2251,2254],{},[593,2252,2253],{},"How do I correlate an error report with the logs around it?","\nAttach the request id as a tag on the error event at capture time, reading it from the same contextvar the log filter uses. The error report then links to a log query, and both link to the trace.",[783,2256,2258],{"id":2257},"related-reading","Related Reading",[597,2260,2261,2269,2277,2284,2293],{},[600,2262,2263,2266,2267,1034],{},[593,2264,2265],{},"Up to the topic:"," ",[644,2268,647],{"href":646},[600,2270,2271,2266,2274,1034],{},[593,2272,2273],{},"The logging substrate:",[644,2275,2276],{"href":2013},"Structured JSON logging with request IDs",[600,2278,2279,2266,2282,1034],{},[593,2280,2281],{},"Traces:",[644,2283,321],{"href":2008},[600,2285,2286,2266,2289,1034],{},[593,2287,2288],{},"Where the id is set:",[644,2290,2292],{"href":2291},"\u002Fcore-architecture-routing-patterns\u002Fmiddleware-implementation\u002Fimplementing-custom-middleware-for-request-tracing\u002F","Implementing custom middleware for request tracing",[600,2294,2295,2266,2298,1034],{},[593,2296,2297],{},"Crossing into threads:",[644,2299,2300],{"href":1788},"Running sync code in a threadpool",[2302,2303,2304],"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 .sacAq, html code.shiki .sacAq{--shiki-default:#023B95}html pre.shiki code .sYEJz, html code.shiki .sYEJz{--shiki-default:#032563}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}html pre.shiki code .sFeEa, html code.shiki .sFeEa{--shiki-default:#66707B}",{"title":846,"searchDepth":863,"depth":863,"links":2306},[2307,2308,2309,2310,2311,2312,2313,2314],{"id":785,"depth":863,"text":786},{"id":835,"depth":863,"text":836},{"id":1773,"depth":863,"text":1774},{"id":1858,"depth":863,"text":1859},{"id":2017,"depth":863,"text":2018},{"id":2200,"depth":863,"text":2201},{"id":2223,"depth":863,"text":2224},{"id":2257,"depth":863,"text":2258},"2026-07-20","Carry one request id through logs, spans and error reports with contextvars, and see where propagation survives an await and where a thread hop drops it.","md",[2319,2321,2323,2325,2327],{"q":2229,"a":2320},"Yes. A coroutine runs in the context that was current when it was scheduled, so a value set before an await is still readable after it. asyncio.create_task copies the context at creation time, so tasks inherit the values that existed at that moment.",{"q":2235,"a":2322},"It depends on how the thread was started. Starlette's run_in_threadpool copies the current context into the worker thread, so the id survives. A bare loop.run_in_executor or a raw threading.Thread starts with an empty context and the id is gone.",{"q":2241,"a":2324},"No. The thread gets a copy of the context, so any set() it performs applies to that copy and is discarded when the callable returns. Values flow in, never out; return the value instead.",{"q":2247,"a":2326},"They can be, and using the trace id as the correlation id removes a mapping step. Keep a separate header when clients or a gateway generate their own id, and log both fields so either one finds the request.",{"q":2253,"a":2328},"Attach the request id as a tag on the error event at capture time, reading it from the same contextvar the log filter uses. The error report then links to a log query, and both link to the trace.",null,{"slug":2331,"breadcrumb":2332},"correlating-logs-traces-and-errors",[2333,2336,2339,2341],{"label":2334,"path":2335},"Home","\u002F",{"label":2337,"path":2338},"Async, Background Tasks & Observability","\u002Fasync-background-tasks-observability\u002F",{"label":2340,"path":646},"Observability & Tracing",{"label":2342,"path":2343},"Correlating Logs, Traces and Errors","\u002Fasync-background-tasks-observability\u002Fobservability-and-tracing\u002Fcorrelating-logs-traces-and-errors\u002F",{"title":315,"description":2316},"article","rJpITYkwC06wmfNFui4cyzPOlbxtYkA4_M6HU2R-5tA",[2329,2329],1784588203038]