fastapi-observer 0.3.1__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. {fastapi_observer-0.3.1/src/fastapi_observer.egg-info → fastapi_observer-0.4.0}/PKG-INFO +95 -5
  2. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/README.md +93 -4
  3. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/pyproject.toml +1 -0
  4. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0/src/fastapi_observer.egg-info}/PKG-INFO +95 -5
  5. fastapi_observer-0.4.0/src/fastapi_observer.egg-info/SOURCES.txt +104 -0
  6. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapi_observer.egg-info/requires.txt +1 -0
  7. fastapi_observer-0.4.0/src/fastapiobserver/_version.py +1 -0
  8. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/control_plane.py +7 -1
  9. fastapi_observer-0.4.0/src/fastapiobserver/logging/__init__.py +36 -0
  10. fastapi_observer-0.4.0/src/fastapiobserver/logging/circuit_breaker.py +158 -0
  11. fastapi_observer-0.4.0/src/fastapiobserver/logging/filters.py +53 -0
  12. fastapi_observer-0.4.0/src/fastapiobserver/logging/formatter.py +157 -0
  13. fastapi_observer-0.4.0/src/fastapiobserver/logging/queueing.py +197 -0
  14. fastapi_observer-0.4.0/src/fastapiobserver/logging/setup.py +239 -0
  15. fastapi_observer-0.4.0/src/fastapiobserver/logging/state.py +17 -0
  16. fastapi_observer-0.4.0/src/fastapiobserver/metrics/__init__.py +41 -0
  17. fastapi_observer-0.4.0/src/fastapiobserver/metrics/builder.py +48 -0
  18. fastapi_observer-0.4.0/src/fastapiobserver/metrics/contracts.py +39 -0
  19. fastapi_observer-0.4.0/src/fastapiobserver/metrics/endpoint.py +176 -0
  20. fastapi_observer-0.4.0/src/fastapiobserver/metrics/noop.py +15 -0
  21. fastapi_observer-0.4.0/src/fastapiobserver/metrics/pathing.py +18 -0
  22. fastapi_observer-0.4.0/src/fastapiobserver/metrics/prometheus/__init__.py +21 -0
  23. fastapi_observer-0.4.0/src/fastapiobserver/metrics/prometheus/backend.py +126 -0
  24. fastapi_observer-0.4.0/src/fastapiobserver/metrics/prometheus/client.py +19 -0
  25. fastapi_observer-0.4.0/src/fastapiobserver/metrics/prometheus/collector.py +170 -0
  26. fastapi_observer-0.4.0/src/fastapiobserver/metrics/prometheus/exemplars.py +19 -0
  27. fastapi_observer-0.4.0/src/fastapiobserver/metrics/prometheus/multiprocess.py +38 -0
  28. fastapi_observer-0.4.0/src/fastapiobserver/metrics/registry.py +42 -0
  29. fastapi_observer-0.4.0/src/fastapiobserver/middleware/__init__.py +27 -0
  30. fastapi_observer-0.4.0/src/fastapiobserver/middleware/body_capture.py +37 -0
  31. fastapi_observer-0.4.0/src/fastapiobserver/middleware/context.py +63 -0
  32. fastapi_observer-0.4.0/src/fastapiobserver/middleware/events.py +81 -0
  33. fastapi_observer-0.4.0/src/fastapiobserver/middleware/headers.py +21 -0
  34. fastapi_observer-0.4.0/src/fastapiobserver/middleware/ip.py +40 -0
  35. fastapi_observer-0.4.0/src/fastapiobserver/middleware/metrics.py +61 -0
  36. fastapi_observer-0.4.0/src/fastapiobserver/middleware/request_logging.py +154 -0
  37. fastapi_observer-0.4.0/src/fastapiobserver/middleware/span_errors.py +34 -0
  38. fastapi_observer-0.4.0/src/fastapiobserver/security/__init__.py +31 -0
  39. fastapi_observer-0.4.0/src/fastapiobserver/security/normalize.py +8 -0
  40. fastapi_observer-0.3.1/src/fastapiobserver/security.py → fastapi_observer-0.4.0/src/fastapiobserver/security/policy.py +19 -195
  41. fastapi_observer-0.4.0/src/fastapiobserver/security/proxies.py +49 -0
  42. fastapi_observer-0.4.0/src/fastapiobserver/security/redaction.py +150 -0
  43. fastapi_observer-0.4.0/src/fastapiobserver/sinks/__init__.py +25 -0
  44. fastapi_observer-0.4.0/src/fastapiobserver/sinks/builtin.py +56 -0
  45. fastapi_observer-0.4.0/src/fastapiobserver/sinks/discovery.py +68 -0
  46. fastapi_observer-0.4.0/src/fastapiobserver/sinks/factory.py +73 -0
  47. fastapi_observer-0.4.0/src/fastapiobserver/sinks/logtail/__init__.py +5 -0
  48. fastapi_observer-0.4.0/src/fastapiobserver/sinks/logtail/dlq.py +130 -0
  49. fastapi_observer-0.4.0/src/fastapiobserver/sinks/logtail/handler.py +205 -0
  50. fastapi_observer-0.4.0/src/fastapiobserver/sinks/logtail/sink.py +66 -0
  51. fastapi_observer-0.4.0/src/fastapiobserver/sinks/protocol.py +15 -0
  52. fastapi_observer-0.4.0/src/fastapiobserver/sinks/registry.py +33 -0
  53. fastapi_observer-0.4.0/src/fastapiobserver/sinks/stats.py +55 -0
  54. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_log_queue_controls.py +17 -0
  55. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_logging_schema_contract.py +33 -12
  56. fastapi_observer-0.4.0/tests/test_metrics_contracts.py +111 -0
  57. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_metrics_optional_dependency.py +4 -4
  58. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_middleware.py +34 -0
  59. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_sink_circuit_breaker.py +21 -0
  60. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_sinks.py +80 -36
  61. fastapi_observer-0.3.1/src/fastapi_observer.egg-info/SOURCES.txt +0 -63
  62. fastapi_observer-0.3.1/src/fastapiobserver/_version.py +0 -1
  63. fastapi_observer-0.3.1/src/fastapiobserver/logging.py +0 -825
  64. fastapi_observer-0.3.1/src/fastapiobserver/metrics.py +0 -668
  65. fastapi_observer-0.3.1/src/fastapiobserver/middleware.py +0 -443
  66. fastapi_observer-0.3.1/src/fastapiobserver/sinks.py +0 -677
  67. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/LICENSE +0 -0
  68. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/setup.cfg +0 -0
  69. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapi_observer.egg-info/dependency_links.txt +0 -0
  70. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapi_observer.egg-info/top_level.txt +0 -0
  71. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/__init__.py +0 -0
  72. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/config.py +0 -0
  73. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/fastapi.py +0 -0
  74. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/integrations/__init__.py +0 -0
  75. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/integrations/strawberry.py +0 -0
  76. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/loguru.py +0 -0
  77. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/otel/__init__.py +0 -0
  78. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/otel/lifecycle.py +0 -0
  79. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/otel/logs.py +0 -0
  80. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/otel/metrics.py +0 -0
  81. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/otel/resource.py +0 -0
  82. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/otel/settings.py +0 -0
  83. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/otel/tracing.py +0 -0
  84. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/otel/types.py +0 -0
  85. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/plugins.py +0 -0
  86. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/propagation.py +0 -0
  87. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/py.typed +0 -0
  88. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/request_context.py +0 -0
  89. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/src/fastapiobserver/utils.py +0 -0
  90. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_body_media_allowlist.py +0 -0
  91. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_config_settings.py +0 -0
  92. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_excluded_url_precedence.py +0 -0
  93. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_fastapi_install.py +0 -0
  94. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_graphql_integrations.py +0 -0
  95. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_logtail_dlq.py +0 -0
  96. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_loguru_bridge.py +0 -0
  97. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_negotiate_content_type.py +0 -0
  98. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_otel_integration.py +0 -0
  99. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_otel_lifecycle.py +0 -0
  100. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_otel_log_correlation.py +0 -0
  101. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_otel_logs_from_env.py +0 -0
  102. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_otel_logs_pipeline.py +0 -0
  103. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_otel_metrics_from_env.py +0 -0
  104. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_otlp_export_integration.py +0 -0
  105. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_plugin_isolation.py +0 -0
  106. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_propagation.py +0 -0
  107. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_redaction_presets.py +0 -0
  108. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_route_template_cardinality.py +0 -0
  109. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_runtime_control_auth.py +0 -0
  110. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_security_redaction.py +0 -0
  111. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_trust_boundary.py +0 -0
  112. {fastapi_observer-0.3.1 → fastapi_observer-0.4.0}/tests/test_utils_refactors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-observer
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: Zero-glue FastAPI observability with security presets and runtime controls
5
5
  Author-email: Vitaee <opensource@vitaee.dev>
6
6
  License-Expression: MIT
@@ -65,6 +65,7 @@ Requires-Dist: pytest>=9.0.2; extra == "dev"
65
65
  Requires-Dist: pytest-randomly>=3.16.0; extra == "dev"
66
66
  Requires-Dist: ruff>=0.15.1; extra == "dev"
67
67
  Requires-Dist: cyclonedx-bom>=7.2.1; extra == "dev"
68
+ Requires-Dist: uvicorn>=0.30.0; extra == "dev"
68
69
  Dynamic: license-file
69
70
 
70
71
  # fastapi-observer
@@ -391,6 +392,19 @@ Request arrives
391
392
  -> context cleared
392
393
  ```
393
394
 
395
+ ### Internal Package Layout (Contributor Map)
396
+
397
+ The project is now organized as focused subpackages instead of large monolithic modules:
398
+
399
+ - `fastapiobserver/logging/`: formatter, queueing, filters, setup lifecycle, sink circuit-breakers.
400
+ - `fastapiobserver/middleware/`: request logging orchestration, context, IP resolution, headers, body capture, metrics hooks.
401
+ - `fastapiobserver/sinks/`: sink protocol, registry/discovery, built-ins, factory wiring, Logtail + DLQ implementation.
402
+ - `fastapiobserver/metrics/`: backend contracts/registry/builder/endpoint, Prometheus integration subpackage.
403
+ - `fastapiobserver/security/`: policy/settings models, normalization helpers, redaction engine, trusted-proxy utilities.
404
+ - `fastapiobserver/otel/`: OTel settings/resource/tracing/logs/metrics/lifecycle helpers.
405
+
406
+ Public imports remain backward-compatible via package facades (`__init__.py` re-exports).
407
+
394
408
  ---
395
409
 
396
410
  ## Example JSON Log Event
@@ -570,11 +584,57 @@ kubectl -n observability rollout status deployment/traffic-generator
570
584
  kubectl -n observability port-forward svc/grafana 3000:3000
571
585
  ```
572
586
 
573
- Open [http://localhost:3000](http://localhost:3000).
587
+ Open [http://localhost:3000](http://localhost:3000).
574
588
  Full guide: [`kubernetes.md`](kubernetes.md)
575
589
 
576
590
  ---
577
591
 
592
+ ## Low-Overhead & Production Tuning (Advanced)
593
+
594
+ `fastapi-observer` integrates natively with the core OpenTelemetry Python SDK, meaning you can aggressively tune its resource usage purely via standard environment variables without altering your application code.
595
+
596
+ For high-throughput services (e.g. `10k+ RPS`), apply these exact variables to minimize the observer footprint:
597
+
598
+ ### 1. Head-Based Sampling
599
+
600
+ Tracing 100% of requests is too expensive at scale. You should configure `fastapi-observer` to respect upstream trace flags, while only sampling a fraction of net-new requests:
601
+
602
+ ```bash
603
+ # Keep the parent's sample decision if it exists, otherwise sample 5%
604
+ export OTEL_TRACES_SAMPLER="parentbased_traceidratio"
605
+ export OTEL_TRACES_SAMPLER_ARG="0.05"
606
+ ```
607
+
608
+ ### 2. Exclude Noisy URLs from the SDK
609
+
610
+ Do not waste cycles generating spans for health checks or static assets. `fastapi-observer` will auto-derive metrics exclusions, but you can explicitly drop them from tracing at the C-extension level:
611
+
612
+ ```bash
613
+ export OTEL_PYTHON_FASTAPI_EXCLUDED_URLS="healthz,metrics,favicon.ico"
614
+ ```
615
+
616
+ ### 3. Cap Span Attributes
617
+
618
+ Prevent large, unmanageable spans from consuming excessive memory in the `BatchSpanProcessor`:
619
+
620
+ ```bash
621
+ export OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT="128"
622
+ export OTEL_SPAN_EVENT_COUNT_LIMIT="128"
623
+ export OTEL_SPAN_LINK_COUNT_LIMIT="128"
624
+ ```
625
+
626
+ ### 4. Optimize Output Buffers
627
+
628
+ The default OpenTelemetry batch limits are too conservative for high-throughput ASGI microservices. Increase the max queue limits so spikes aren't dropped, but decrease the timeout so the process memory is flushed faster:
629
+
630
+ ```bash
631
+ export OTEL_BSP_MAX_QUEUE_SIZE="10000"
632
+ export OTEL_BSP_MAX_EXPORT_BATCH_SIZE="5000"
633
+ export OTEL_BSP_SCHEDULE_DELAY="1000"
634
+ ```
635
+
636
+ ---
637
+
578
638
  ## Examples
579
639
 
580
640
  The `examples/` directory contains runnable demos:
@@ -729,7 +789,36 @@ install_observability(app, settings, security_policy=SecurityPolicy(log_request_
729
789
  app.add_middleware(CORSMiddleware, allow_origins=["*"])
730
790
  ```
731
791
 
732
- ### Multi-worker Gunicorn with Prometheus
792
+ ### Multi-worker Gunicorn
793
+
794
+ > [!WARNING]
795
+ > `--preload-app` is dangerous if visibility is initialized at the module level.
796
+ >
797
+ > When `--preload-app` is used, the application is loaded into the master process *before* forking the worker processes. `fastapi-observer` uses a highly-performant background thread (`QueueListener`) for non-blocking logging, and OpenTelemetry similarly spawns background export threads.
798
+ >
799
+ > Threads **do not survive** a process fork. If you call `install_observability()` at the module level (e.g., right under `app = FastAPI()`), the background threads will be created in the master process, and all workers will silently drop logs because their logging threads never started.
800
+ >
801
+ > **How to fix:** Either remove `--preload-app`, OR initialize observability inside the FastAPI `lifespan` context manager so it starts safely after the fork inside each worker:
802
+ >
803
+ > ```python
804
+ > from contextlib import asynccontextmanager
805
+ > from fastapi import FastAPI
806
+ > from fastapiobserver import ObservabilitySettings, install_observability
807
+ >
808
+ > settings = ObservabilitySettings(service="api")
809
+ >
810
+ > @asynccontextmanager
811
+ > async def lifespan(app: FastAPI):
812
+ > install_observability(app, settings) # Safely initializes per-worker
813
+ > yield
814
+ > # fastapiobserver automatically registers shutdown hooks so no teardown needed here
815
+ >
816
+ > app = FastAPI(lifespan=lifespan)
817
+ > ```
818
+
819
+ #### Prometheus Multiprocess Mode
820
+
821
+ If you are using Prometheus with multiple Gunicorn workers, you must configure a shared metrics directory:
733
822
 
734
823
  ```bash
735
824
  export PROMETHEUS_MULTIPROC_DIR=/tmp/prometheus-metrics
@@ -961,9 +1050,10 @@ Reproducible benchmark harness and methodology:
961
1050
 
962
1051
  - `0.1.x`: secure-by-default core
963
1052
  - `0.2.x`: OTel interoperability, security presets, allowlists
1053
+ - `0.3.x`: package modularization, sink/metrics hardening, and performance reliability updates
964
1054
  - `1.0.0`: dynamic runtime controls and plugin stability
965
1055
 
966
- Current release version: `0.3.1`
1056
+ Current release version: `0.4.0`
967
1057
 
968
1058
  ## Changelog Policy
969
1059
 
@@ -1018,4 +1108,4 @@ The pre-push hook runs:
1018
1108
 
1019
1109
  ## Roadmap Tracking
1020
1110
 
1021
- See [NEXT_STEPS.md](NEXT_STEPS.md) for the active `0.2.0` roadmap and release checklist.
1111
+ See [NEXT_STEPS.md](NEXT_STEPS.md) for the active roadmap and release checklist.
@@ -322,6 +322,19 @@ Request arrives
322
322
  -> context cleared
323
323
  ```
324
324
 
325
+ ### Internal Package Layout (Contributor Map)
326
+
327
+ The project is now organized as focused subpackages instead of large monolithic modules:
328
+
329
+ - `fastapiobserver/logging/`: formatter, queueing, filters, setup lifecycle, sink circuit-breakers.
330
+ - `fastapiobserver/middleware/`: request logging orchestration, context, IP resolution, headers, body capture, metrics hooks.
331
+ - `fastapiobserver/sinks/`: sink protocol, registry/discovery, built-ins, factory wiring, Logtail + DLQ implementation.
332
+ - `fastapiobserver/metrics/`: backend contracts/registry/builder/endpoint, Prometheus integration subpackage.
333
+ - `fastapiobserver/security/`: policy/settings models, normalization helpers, redaction engine, trusted-proxy utilities.
334
+ - `fastapiobserver/otel/`: OTel settings/resource/tracing/logs/metrics/lifecycle helpers.
335
+
336
+ Public imports remain backward-compatible via package facades (`__init__.py` re-exports).
337
+
325
338
  ---
326
339
 
327
340
  ## Example JSON Log Event
@@ -501,11 +514,57 @@ kubectl -n observability rollout status deployment/traffic-generator
501
514
  kubectl -n observability port-forward svc/grafana 3000:3000
502
515
  ```
503
516
 
504
- Open [http://localhost:3000](http://localhost:3000).
517
+ Open [http://localhost:3000](http://localhost:3000).
505
518
  Full guide: [`kubernetes.md`](kubernetes.md)
506
519
 
507
520
  ---
508
521
 
522
+ ## Low-Overhead & Production Tuning (Advanced)
523
+
524
+ `fastapi-observer` integrates natively with the core OpenTelemetry Python SDK, meaning you can aggressively tune its resource usage purely via standard environment variables without altering your application code.
525
+
526
+ For high-throughput services (e.g. `10k+ RPS`), apply these exact variables to minimize the observer footprint:
527
+
528
+ ### 1. Head-Based Sampling
529
+
530
+ Tracing 100% of requests is too expensive at scale. You should configure `fastapi-observer` to respect upstream trace flags, while only sampling a fraction of net-new requests:
531
+
532
+ ```bash
533
+ # Keep the parent's sample decision if it exists, otherwise sample 5%
534
+ export OTEL_TRACES_SAMPLER="parentbased_traceidratio"
535
+ export OTEL_TRACES_SAMPLER_ARG="0.05"
536
+ ```
537
+
538
+ ### 2. Exclude Noisy URLs from the SDK
539
+
540
+ Do not waste cycles generating spans for health checks or static assets. `fastapi-observer` will auto-derive metrics exclusions, but you can explicitly drop them from tracing at the C-extension level:
541
+
542
+ ```bash
543
+ export OTEL_PYTHON_FASTAPI_EXCLUDED_URLS="healthz,metrics,favicon.ico"
544
+ ```
545
+
546
+ ### 3. Cap Span Attributes
547
+
548
+ Prevent large, unmanageable spans from consuming excessive memory in the `BatchSpanProcessor`:
549
+
550
+ ```bash
551
+ export OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT="128"
552
+ export OTEL_SPAN_EVENT_COUNT_LIMIT="128"
553
+ export OTEL_SPAN_LINK_COUNT_LIMIT="128"
554
+ ```
555
+
556
+ ### 4. Optimize Output Buffers
557
+
558
+ The default OpenTelemetry batch limits are too conservative for high-throughput ASGI microservices. Increase the max queue limits so spikes aren't dropped, but decrease the timeout so the process memory is flushed faster:
559
+
560
+ ```bash
561
+ export OTEL_BSP_MAX_QUEUE_SIZE="10000"
562
+ export OTEL_BSP_MAX_EXPORT_BATCH_SIZE="5000"
563
+ export OTEL_BSP_SCHEDULE_DELAY="1000"
564
+ ```
565
+
566
+ ---
567
+
509
568
  ## Examples
510
569
 
511
570
  The `examples/` directory contains runnable demos:
@@ -660,7 +719,36 @@ install_observability(app, settings, security_policy=SecurityPolicy(log_request_
660
719
  app.add_middleware(CORSMiddleware, allow_origins=["*"])
661
720
  ```
662
721
 
663
- ### Multi-worker Gunicorn with Prometheus
722
+ ### Multi-worker Gunicorn
723
+
724
+ > [!WARNING]
725
+ > `--preload-app` is dangerous if visibility is initialized at the module level.
726
+ >
727
+ > When `--preload-app` is used, the application is loaded into the master process *before* forking the worker processes. `fastapi-observer` uses a highly-performant background thread (`QueueListener`) for non-blocking logging, and OpenTelemetry similarly spawns background export threads.
728
+ >
729
+ > Threads **do not survive** a process fork. If you call `install_observability()` at the module level (e.g., right under `app = FastAPI()`), the background threads will be created in the master process, and all workers will silently drop logs because their logging threads never started.
730
+ >
731
+ > **How to fix:** Either remove `--preload-app`, OR initialize observability inside the FastAPI `lifespan` context manager so it starts safely after the fork inside each worker:
732
+ >
733
+ > ```python
734
+ > from contextlib import asynccontextmanager
735
+ > from fastapi import FastAPI
736
+ > from fastapiobserver import ObservabilitySettings, install_observability
737
+ >
738
+ > settings = ObservabilitySettings(service="api")
739
+ >
740
+ > @asynccontextmanager
741
+ > async def lifespan(app: FastAPI):
742
+ > install_observability(app, settings) # Safely initializes per-worker
743
+ > yield
744
+ > # fastapiobserver automatically registers shutdown hooks so no teardown needed here
745
+ >
746
+ > app = FastAPI(lifespan=lifespan)
747
+ > ```
748
+
749
+ #### Prometheus Multiprocess Mode
750
+
751
+ If you are using Prometheus with multiple Gunicorn workers, you must configure a shared metrics directory:
664
752
 
665
753
  ```bash
666
754
  export PROMETHEUS_MULTIPROC_DIR=/tmp/prometheus-metrics
@@ -892,9 +980,10 @@ Reproducible benchmark harness and methodology:
892
980
 
893
981
  - `0.1.x`: secure-by-default core
894
982
  - `0.2.x`: OTel interoperability, security presets, allowlists
983
+ - `0.3.x`: package modularization, sink/metrics hardening, and performance reliability updates
895
984
  - `1.0.0`: dynamic runtime controls and plugin stability
896
985
 
897
- Current release version: `0.3.1`
986
+ Current release version: `0.4.0`
898
987
 
899
988
  ## Changelog Policy
900
989
 
@@ -949,4 +1038,4 @@ The pre-push hook runs:
949
1038
 
950
1039
  ## Roadmap Tracking
951
1040
 
952
- See [NEXT_STEPS.md](NEXT_STEPS.md) for the active `0.2.0` roadmap and release checklist.
1041
+ See [NEXT_STEPS.md](NEXT_STEPS.md) for the active roadmap and release checklist.
@@ -86,6 +86,7 @@ dev = [
86
86
  "pytest-randomly>=3.16.0",
87
87
  "ruff>=0.15.1",
88
88
  "cyclonedx-bom>=7.2.1",
89
+ "uvicorn>=0.30.0",
89
90
  ]
90
91
 
91
92
  [tool.setuptools]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-observer
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: Zero-glue FastAPI observability with security presets and runtime controls
5
5
  Author-email: Vitaee <opensource@vitaee.dev>
6
6
  License-Expression: MIT
@@ -65,6 +65,7 @@ Requires-Dist: pytest>=9.0.2; extra == "dev"
65
65
  Requires-Dist: pytest-randomly>=3.16.0; extra == "dev"
66
66
  Requires-Dist: ruff>=0.15.1; extra == "dev"
67
67
  Requires-Dist: cyclonedx-bom>=7.2.1; extra == "dev"
68
+ Requires-Dist: uvicorn>=0.30.0; extra == "dev"
68
69
  Dynamic: license-file
69
70
 
70
71
  # fastapi-observer
@@ -391,6 +392,19 @@ Request arrives
391
392
  -> context cleared
392
393
  ```
393
394
 
395
+ ### Internal Package Layout (Contributor Map)
396
+
397
+ The project is now organized as focused subpackages instead of large monolithic modules:
398
+
399
+ - `fastapiobserver/logging/`: formatter, queueing, filters, setup lifecycle, sink circuit-breakers.
400
+ - `fastapiobserver/middleware/`: request logging orchestration, context, IP resolution, headers, body capture, metrics hooks.
401
+ - `fastapiobserver/sinks/`: sink protocol, registry/discovery, built-ins, factory wiring, Logtail + DLQ implementation.
402
+ - `fastapiobserver/metrics/`: backend contracts/registry/builder/endpoint, Prometheus integration subpackage.
403
+ - `fastapiobserver/security/`: policy/settings models, normalization helpers, redaction engine, trusted-proxy utilities.
404
+ - `fastapiobserver/otel/`: OTel settings/resource/tracing/logs/metrics/lifecycle helpers.
405
+
406
+ Public imports remain backward-compatible via package facades (`__init__.py` re-exports).
407
+
394
408
  ---
395
409
 
396
410
  ## Example JSON Log Event
@@ -570,11 +584,57 @@ kubectl -n observability rollout status deployment/traffic-generator
570
584
  kubectl -n observability port-forward svc/grafana 3000:3000
571
585
  ```
572
586
 
573
- Open [http://localhost:3000](http://localhost:3000).
587
+ Open [http://localhost:3000](http://localhost:3000).
574
588
  Full guide: [`kubernetes.md`](kubernetes.md)
575
589
 
576
590
  ---
577
591
 
592
+ ## Low-Overhead & Production Tuning (Advanced)
593
+
594
+ `fastapi-observer` integrates natively with the core OpenTelemetry Python SDK, meaning you can aggressively tune its resource usage purely via standard environment variables without altering your application code.
595
+
596
+ For high-throughput services (e.g. `10k+ RPS`), apply these exact variables to minimize the observer footprint:
597
+
598
+ ### 1. Head-Based Sampling
599
+
600
+ Tracing 100% of requests is too expensive at scale. You should configure `fastapi-observer` to respect upstream trace flags, while only sampling a fraction of net-new requests:
601
+
602
+ ```bash
603
+ # Keep the parent's sample decision if it exists, otherwise sample 5%
604
+ export OTEL_TRACES_SAMPLER="parentbased_traceidratio"
605
+ export OTEL_TRACES_SAMPLER_ARG="0.05"
606
+ ```
607
+
608
+ ### 2. Exclude Noisy URLs from the SDK
609
+
610
+ Do not waste cycles generating spans for health checks or static assets. `fastapi-observer` will auto-derive metrics exclusions, but you can explicitly drop them from tracing at the C-extension level:
611
+
612
+ ```bash
613
+ export OTEL_PYTHON_FASTAPI_EXCLUDED_URLS="healthz,metrics,favicon.ico"
614
+ ```
615
+
616
+ ### 3. Cap Span Attributes
617
+
618
+ Prevent large, unmanageable spans from consuming excessive memory in the `BatchSpanProcessor`:
619
+
620
+ ```bash
621
+ export OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT="128"
622
+ export OTEL_SPAN_EVENT_COUNT_LIMIT="128"
623
+ export OTEL_SPAN_LINK_COUNT_LIMIT="128"
624
+ ```
625
+
626
+ ### 4. Optimize Output Buffers
627
+
628
+ The default OpenTelemetry batch limits are too conservative for high-throughput ASGI microservices. Increase the max queue limits so spikes aren't dropped, but decrease the timeout so the process memory is flushed faster:
629
+
630
+ ```bash
631
+ export OTEL_BSP_MAX_QUEUE_SIZE="10000"
632
+ export OTEL_BSP_MAX_EXPORT_BATCH_SIZE="5000"
633
+ export OTEL_BSP_SCHEDULE_DELAY="1000"
634
+ ```
635
+
636
+ ---
637
+
578
638
  ## Examples
579
639
 
580
640
  The `examples/` directory contains runnable demos:
@@ -729,7 +789,36 @@ install_observability(app, settings, security_policy=SecurityPolicy(log_request_
729
789
  app.add_middleware(CORSMiddleware, allow_origins=["*"])
730
790
  ```
731
791
 
732
- ### Multi-worker Gunicorn with Prometheus
792
+ ### Multi-worker Gunicorn
793
+
794
+ > [!WARNING]
795
+ > `--preload-app` is dangerous if visibility is initialized at the module level.
796
+ >
797
+ > When `--preload-app` is used, the application is loaded into the master process *before* forking the worker processes. `fastapi-observer` uses a highly-performant background thread (`QueueListener`) for non-blocking logging, and OpenTelemetry similarly spawns background export threads.
798
+ >
799
+ > Threads **do not survive** a process fork. If you call `install_observability()` at the module level (e.g., right under `app = FastAPI()`), the background threads will be created in the master process, and all workers will silently drop logs because their logging threads never started.
800
+ >
801
+ > **How to fix:** Either remove `--preload-app`, OR initialize observability inside the FastAPI `lifespan` context manager so it starts safely after the fork inside each worker:
802
+ >
803
+ > ```python
804
+ > from contextlib import asynccontextmanager
805
+ > from fastapi import FastAPI
806
+ > from fastapiobserver import ObservabilitySettings, install_observability
807
+ >
808
+ > settings = ObservabilitySettings(service="api")
809
+ >
810
+ > @asynccontextmanager
811
+ > async def lifespan(app: FastAPI):
812
+ > install_observability(app, settings) # Safely initializes per-worker
813
+ > yield
814
+ > # fastapiobserver automatically registers shutdown hooks so no teardown needed here
815
+ >
816
+ > app = FastAPI(lifespan=lifespan)
817
+ > ```
818
+
819
+ #### Prometheus Multiprocess Mode
820
+
821
+ If you are using Prometheus with multiple Gunicorn workers, you must configure a shared metrics directory:
733
822
 
734
823
  ```bash
735
824
  export PROMETHEUS_MULTIPROC_DIR=/tmp/prometheus-metrics
@@ -961,9 +1050,10 @@ Reproducible benchmark harness and methodology:
961
1050
 
962
1051
  - `0.1.x`: secure-by-default core
963
1052
  - `0.2.x`: OTel interoperability, security presets, allowlists
1053
+ - `0.3.x`: package modularization, sink/metrics hardening, and performance reliability updates
964
1054
  - `1.0.0`: dynamic runtime controls and plugin stability
965
1055
 
966
- Current release version: `0.3.1`
1056
+ Current release version: `0.4.0`
967
1057
 
968
1058
  ## Changelog Policy
969
1059
 
@@ -1018,4 +1108,4 @@ The pre-push hook runs:
1018
1108
 
1019
1109
  ## Roadmap Tracking
1020
1110
 
1021
- See [NEXT_STEPS.md](NEXT_STEPS.md) for the active `0.2.0` roadmap and release checklist.
1111
+ See [NEXT_STEPS.md](NEXT_STEPS.md) for the active roadmap and release checklist.
@@ -0,0 +1,104 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/fastapi_observer.egg-info/PKG-INFO
5
+ src/fastapi_observer.egg-info/SOURCES.txt
6
+ src/fastapi_observer.egg-info/dependency_links.txt
7
+ src/fastapi_observer.egg-info/requires.txt
8
+ src/fastapi_observer.egg-info/top_level.txt
9
+ src/fastapiobserver/__init__.py
10
+ src/fastapiobserver/_version.py
11
+ src/fastapiobserver/config.py
12
+ src/fastapiobserver/control_plane.py
13
+ src/fastapiobserver/fastapi.py
14
+ src/fastapiobserver/loguru.py
15
+ src/fastapiobserver/plugins.py
16
+ src/fastapiobserver/propagation.py
17
+ src/fastapiobserver/py.typed
18
+ src/fastapiobserver/request_context.py
19
+ src/fastapiobserver/utils.py
20
+ src/fastapiobserver/integrations/__init__.py
21
+ src/fastapiobserver/integrations/strawberry.py
22
+ src/fastapiobserver/logging/__init__.py
23
+ src/fastapiobserver/logging/circuit_breaker.py
24
+ src/fastapiobserver/logging/filters.py
25
+ src/fastapiobserver/logging/formatter.py
26
+ src/fastapiobserver/logging/queueing.py
27
+ src/fastapiobserver/logging/setup.py
28
+ src/fastapiobserver/logging/state.py
29
+ src/fastapiobserver/metrics/__init__.py
30
+ src/fastapiobserver/metrics/builder.py
31
+ src/fastapiobserver/metrics/contracts.py
32
+ src/fastapiobserver/metrics/endpoint.py
33
+ src/fastapiobserver/metrics/noop.py
34
+ src/fastapiobserver/metrics/pathing.py
35
+ src/fastapiobserver/metrics/registry.py
36
+ src/fastapiobserver/metrics/prometheus/__init__.py
37
+ src/fastapiobserver/metrics/prometheus/backend.py
38
+ src/fastapiobserver/metrics/prometheus/client.py
39
+ src/fastapiobserver/metrics/prometheus/collector.py
40
+ src/fastapiobserver/metrics/prometheus/exemplars.py
41
+ src/fastapiobserver/metrics/prometheus/multiprocess.py
42
+ src/fastapiobserver/middleware/__init__.py
43
+ src/fastapiobserver/middleware/body_capture.py
44
+ src/fastapiobserver/middleware/context.py
45
+ src/fastapiobserver/middleware/events.py
46
+ src/fastapiobserver/middleware/headers.py
47
+ src/fastapiobserver/middleware/ip.py
48
+ src/fastapiobserver/middleware/metrics.py
49
+ src/fastapiobserver/middleware/request_logging.py
50
+ src/fastapiobserver/middleware/span_errors.py
51
+ src/fastapiobserver/otel/__init__.py
52
+ src/fastapiobserver/otel/lifecycle.py
53
+ src/fastapiobserver/otel/logs.py
54
+ src/fastapiobserver/otel/metrics.py
55
+ src/fastapiobserver/otel/resource.py
56
+ src/fastapiobserver/otel/settings.py
57
+ src/fastapiobserver/otel/tracing.py
58
+ src/fastapiobserver/otel/types.py
59
+ src/fastapiobserver/security/__init__.py
60
+ src/fastapiobserver/security/normalize.py
61
+ src/fastapiobserver/security/policy.py
62
+ src/fastapiobserver/security/proxies.py
63
+ src/fastapiobserver/security/redaction.py
64
+ src/fastapiobserver/sinks/__init__.py
65
+ src/fastapiobserver/sinks/builtin.py
66
+ src/fastapiobserver/sinks/discovery.py
67
+ src/fastapiobserver/sinks/factory.py
68
+ src/fastapiobserver/sinks/protocol.py
69
+ src/fastapiobserver/sinks/registry.py
70
+ src/fastapiobserver/sinks/stats.py
71
+ src/fastapiobserver/sinks/logtail/__init__.py
72
+ src/fastapiobserver/sinks/logtail/dlq.py
73
+ src/fastapiobserver/sinks/logtail/handler.py
74
+ src/fastapiobserver/sinks/logtail/sink.py
75
+ tests/test_body_media_allowlist.py
76
+ tests/test_config_settings.py
77
+ tests/test_excluded_url_precedence.py
78
+ tests/test_fastapi_install.py
79
+ tests/test_graphql_integrations.py
80
+ tests/test_log_queue_controls.py
81
+ tests/test_logging_schema_contract.py
82
+ tests/test_logtail_dlq.py
83
+ tests/test_loguru_bridge.py
84
+ tests/test_metrics_contracts.py
85
+ tests/test_metrics_optional_dependency.py
86
+ tests/test_middleware.py
87
+ tests/test_negotiate_content_type.py
88
+ tests/test_otel_integration.py
89
+ tests/test_otel_lifecycle.py
90
+ tests/test_otel_log_correlation.py
91
+ tests/test_otel_logs_from_env.py
92
+ tests/test_otel_logs_pipeline.py
93
+ tests/test_otel_metrics_from_env.py
94
+ tests/test_otlp_export_integration.py
95
+ tests/test_plugin_isolation.py
96
+ tests/test_propagation.py
97
+ tests/test_redaction_presets.py
98
+ tests/test_route_template_cardinality.py
99
+ tests/test_runtime_control_auth.py
100
+ tests/test_security_redaction.py
101
+ tests/test_sink_circuit_breaker.py
102
+ tests/test_sinks.py
103
+ tests/test_trust_boundary.py
104
+ tests/test_utils_refactors.py
@@ -23,6 +23,7 @@ pytest>=9.0.2
23
23
  pytest-randomly>=3.16.0
24
24
  ruff>=0.15.1
25
25
  cyclonedx-bom>=7.2.1
26
+ uvicorn>=0.30.0
26
27
 
27
28
  [fast-json]
28
29
  orjson>=3.11.7
@@ -0,0 +1 @@
1
+ __version__ = "0.4.0"
@@ -72,7 +72,13 @@ def mount_control_plane(app: FastAPI, settings: RuntimeControlSettings) -> None:
72
72
  def authorize(
73
73
  authorization: Annotated[str | None, Header()] = None,
74
74
  ) -> None:
75
- expected = f"Bearer {token}"
75
+ current_token = os.getenv(settings.token_env_var)
76
+ if not current_token:
77
+ raise HTTPException(
78
+ status_code=status.HTTP_401_UNAUTHORIZED,
79
+ detail="Unauthorized",
80
+ )
81
+ expected = f"Bearer {current_token}"
76
82
  if not authorization or not secrets.compare_digest(authorization, expected):
77
83
  raise HTTPException(
78
84
  status_code=status.HTTP_401_UNAUTHORIZED,
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ from .circuit_breaker import (
4
+ CircuitBreakerState,
5
+ SinkCircuitBreakerHandler,
6
+ SinkCircuitBreakerSnapshot,
7
+ get_sink_circuit_breaker_stats,
8
+ )
9
+ from .filters import PluginLogFilter, RequestIdFilter, TraceContextFilter
10
+ from .formatter import LOG_SCHEMA_VERSION, StructuredJsonFormatter
11
+ from .queueing import (
12
+ LogQueueStatsSnapshot,
13
+ LogQueueTelemetry,
14
+ OverflowPolicyQueueHandler,
15
+ get_log_queue_stats,
16
+ )
17
+ from .setup import get_managed_output_handlers, setup_logging, shutdown_logging
18
+
19
+ __all__ = [
20
+ "LOG_SCHEMA_VERSION",
21
+ "CircuitBreakerState",
22
+ "LogQueueStatsSnapshot",
23
+ "LogQueueTelemetry",
24
+ "OverflowPolicyQueueHandler",
25
+ "PluginLogFilter",
26
+ "RequestIdFilter",
27
+ "SinkCircuitBreakerHandler",
28
+ "SinkCircuitBreakerSnapshot",
29
+ "StructuredJsonFormatter",
30
+ "TraceContextFilter",
31
+ "get_log_queue_stats",
32
+ "get_managed_output_handlers",
33
+ "get_sink_circuit_breaker_stats",
34
+ "setup_logging",
35
+ "shutdown_logging",
36
+ ]