fastapi-observer 0.3.2__tar.gz → 0.4.1__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 (114) hide show
  1. {fastapi_observer-0.3.2/src/fastapi_observer.egg-info → fastapi_observer-0.4.1}/PKG-INFO +22 -5
  2. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/README.md +21 -4
  3. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1/src/fastapi_observer.egg-info}/PKG-INFO +22 -5
  4. fastapi_observer-0.4.1/src/fastapi_observer.egg-info/SOURCES.txt +106 -0
  5. fastapi_observer-0.4.1/src/fastapiobserver/_version.py +1 -0
  6. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/control_plane.py +7 -1
  7. fastapi_observer-0.4.1/src/fastapiobserver/logging/__init__.py +36 -0
  8. fastapi_observer-0.4.1/src/fastapiobserver/logging/circuit_breaker.py +158 -0
  9. fastapi_observer-0.4.1/src/fastapiobserver/logging/filters.py +53 -0
  10. fastapi_observer-0.4.1/src/fastapiobserver/logging/formatter.py +157 -0
  11. fastapi_observer-0.4.1/src/fastapiobserver/logging/queueing.py +197 -0
  12. fastapi_observer-0.4.1/src/fastapiobserver/logging/setup.py +239 -0
  13. fastapi_observer-0.4.1/src/fastapiobserver/logging/state.py +17 -0
  14. fastapi_observer-0.4.1/src/fastapiobserver/metrics/__init__.py +41 -0
  15. fastapi_observer-0.4.1/src/fastapiobserver/metrics/builder.py +48 -0
  16. fastapi_observer-0.4.1/src/fastapiobserver/metrics/contracts.py +39 -0
  17. fastapi_observer-0.4.1/src/fastapiobserver/metrics/endpoint.py +176 -0
  18. fastapi_observer-0.4.1/src/fastapiobserver/metrics/noop.py +15 -0
  19. fastapi_observer-0.4.1/src/fastapiobserver/metrics/pathing.py +18 -0
  20. fastapi_observer-0.4.1/src/fastapiobserver/metrics/prometheus/__init__.py +21 -0
  21. fastapi_observer-0.4.1/src/fastapiobserver/metrics/prometheus/backend.py +126 -0
  22. fastapi_observer-0.4.1/src/fastapiobserver/metrics/prometheus/client.py +19 -0
  23. fastapi_observer-0.4.1/src/fastapiobserver/metrics/prometheus/collector.py +170 -0
  24. fastapi_observer-0.4.1/src/fastapiobserver/metrics/prometheus/exemplars.py +19 -0
  25. fastapi_observer-0.4.1/src/fastapiobserver/metrics/prometheus/multiprocess.py +38 -0
  26. fastapi_observer-0.4.1/src/fastapiobserver/metrics/registry.py +42 -0
  27. fastapi_observer-0.4.1/src/fastapiobserver/middleware/__init__.py +27 -0
  28. fastapi_observer-0.4.1/src/fastapiobserver/middleware/body_capture.py +37 -0
  29. fastapi_observer-0.4.1/src/fastapiobserver/middleware/context.py +63 -0
  30. fastapi_observer-0.4.1/src/fastapiobserver/middleware/events.py +90 -0
  31. fastapi_observer-0.4.1/src/fastapiobserver/middleware/headers.py +21 -0
  32. fastapi_observer-0.4.1/src/fastapiobserver/middleware/ip.py +57 -0
  33. fastapi_observer-0.4.1/src/fastapiobserver/middleware/metrics.py +61 -0
  34. fastapi_observer-0.4.1/src/fastapiobserver/middleware/request_logging.py +154 -0
  35. fastapi_observer-0.4.1/src/fastapiobserver/middleware/span_errors.py +34 -0
  36. fastapi_observer-0.4.1/src/fastapiobserver/security/__init__.py +31 -0
  37. fastapi_observer-0.4.1/src/fastapiobserver/security/normalize.py +8 -0
  38. fastapi_observer-0.3.2/src/fastapiobserver/security.py → fastapi_observer-0.4.1/src/fastapiobserver/security/policy.py +20 -203
  39. fastapi_observer-0.4.1/src/fastapiobserver/security/proxies.py +49 -0
  40. fastapi_observer-0.4.1/src/fastapiobserver/security/redaction.py +154 -0
  41. fastapi_observer-0.4.1/src/fastapiobserver/sinks/__init__.py +25 -0
  42. fastapi_observer-0.4.1/src/fastapiobserver/sinks/builtin.py +56 -0
  43. fastapi_observer-0.4.1/src/fastapiobserver/sinks/discovery.py +68 -0
  44. fastapi_observer-0.4.1/src/fastapiobserver/sinks/factory.py +73 -0
  45. fastapi_observer-0.4.1/src/fastapiobserver/sinks/logtail/__init__.py +5 -0
  46. fastapi_observer-0.4.1/src/fastapiobserver/sinks/logtail/dlq.py +130 -0
  47. fastapi_observer-0.4.1/src/fastapiobserver/sinks/logtail/handler.py +205 -0
  48. fastapi_observer-0.4.1/src/fastapiobserver/sinks/logtail/sink.py +66 -0
  49. fastapi_observer-0.4.1/src/fastapiobserver/sinks/protocol.py +15 -0
  50. fastapi_observer-0.4.1/src/fastapiobserver/sinks/registry.py +33 -0
  51. fastapi_observer-0.4.1/src/fastapiobserver/sinks/stats.py +55 -0
  52. fastapi_observer-0.4.1/tests/test_dx_parity.py +79 -0
  53. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_log_queue_controls.py +17 -0
  54. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_logging_schema_contract.py +33 -12
  55. fastapi_observer-0.4.1/tests/test_metrics_contracts.py +111 -0
  56. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_metrics_optional_dependency.py +4 -4
  57. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_middleware.py +34 -0
  58. fastapi_observer-0.4.1/tests/test_security_audit_fixes.py +161 -0
  59. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_sink_circuit_breaker.py +21 -0
  60. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_sinks.py +80 -36
  61. fastapi_observer-0.3.2/src/fastapi_observer.egg-info/SOURCES.txt +0 -63
  62. fastapi_observer-0.3.2/src/fastapiobserver/_version.py +0 -1
  63. fastapi_observer-0.3.2/src/fastapiobserver/logging.py +0 -825
  64. fastapi_observer-0.3.2/src/fastapiobserver/metrics.py +0 -668
  65. fastapi_observer-0.3.2/src/fastapiobserver/middleware.py +0 -447
  66. fastapi_observer-0.3.2/src/fastapiobserver/sinks.py +0 -677
  67. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/LICENSE +0 -0
  68. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/pyproject.toml +0 -0
  69. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/setup.cfg +0 -0
  70. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapi_observer.egg-info/dependency_links.txt +0 -0
  71. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapi_observer.egg-info/requires.txt +0 -0
  72. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapi_observer.egg-info/top_level.txt +0 -0
  73. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/__init__.py +0 -0
  74. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/config.py +0 -0
  75. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/fastapi.py +0 -0
  76. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/integrations/__init__.py +0 -0
  77. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/integrations/strawberry.py +0 -0
  78. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/loguru.py +0 -0
  79. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/otel/__init__.py +0 -0
  80. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/otel/lifecycle.py +0 -0
  81. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/otel/logs.py +0 -0
  82. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/otel/metrics.py +0 -0
  83. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/otel/resource.py +0 -0
  84. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/otel/settings.py +0 -0
  85. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/otel/tracing.py +0 -0
  86. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/otel/types.py +0 -0
  87. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/plugins.py +0 -0
  88. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/propagation.py +0 -0
  89. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/py.typed +0 -0
  90. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/request_context.py +0 -0
  91. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/src/fastapiobserver/utils.py +0 -0
  92. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_body_media_allowlist.py +0 -0
  93. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_config_settings.py +0 -0
  94. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_excluded_url_precedence.py +0 -0
  95. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_fastapi_install.py +0 -0
  96. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_graphql_integrations.py +0 -0
  97. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_logtail_dlq.py +0 -0
  98. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_loguru_bridge.py +0 -0
  99. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_negotiate_content_type.py +0 -0
  100. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_otel_integration.py +0 -0
  101. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_otel_lifecycle.py +0 -0
  102. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_otel_log_correlation.py +0 -0
  103. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_otel_logs_from_env.py +0 -0
  104. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_otel_logs_pipeline.py +0 -0
  105. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_otel_metrics_from_env.py +0 -0
  106. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_otlp_export_integration.py +0 -0
  107. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_plugin_isolation.py +0 -0
  108. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_propagation.py +0 -0
  109. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_redaction_presets.py +0 -0
  110. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_route_template_cardinality.py +0 -0
  111. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_runtime_control_auth.py +0 -0
  112. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_security_redaction.py +0 -0
  113. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/tests/test_trust_boundary.py +0 -0
  114. {fastapi_observer-0.3.2 → fastapi_observer-0.4.1}/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.2
3
+ Version: 0.4.1
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
@@ -392,6 +392,19 @@ Request arrives
392
392
  -> context cleared
393
393
  ```
394
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
+
395
408
  ---
396
409
 
397
410
  ## Example JSON Log Event
@@ -691,6 +704,9 @@ The library supports configuration from code and env vars. Below are the most re
691
704
  | `METRICS_EXEMPLARS_ENABLED` | `false` | Enable exemplars where supported |
692
705
  | `METRICS_FORMAT` | `negotiate` | `prometheus`, `openmetrics`, or `negotiate` |
693
706
 
707
+ > [!CAUTION]
708
+ > The `/metrics` endpoint is **unauthenticated by default**. In production it should be restricted to internal networks (e.g. behind a Kubernetes `NetworkPolicy`, VPC security group, or ingress rule that only allows your Prometheus scraper). Exposing it publicly leaks service topology, error rates, and request patterns.
709
+
694
710
  ### Security and trust boundary
695
711
 
696
712
  | Variable | Default | Description |
@@ -1028,8 +1044,8 @@ Repository integration tests include:
1028
1044
 
1029
1045
  Reproducible benchmark harness and methodology:
1030
1046
  - Guide: [`benchmarks.md`](benchmarks.md)
1031
- - Apps: `examples/benchmarks/plain_fastapi.py` and `examples/benchmarks/observer_fastapi.py`
1032
- - Runner: `examples/benchmarks/run_local_benchmark.sh`
1047
+ - Apps: `examples/benchmarks/app.py`
1048
+ - Runner: `examples/benchmarks/harness.py`
1033
1049
 
1034
1050
  ---
1035
1051
 
@@ -1037,9 +1053,10 @@ Reproducible benchmark harness and methodology:
1037
1053
 
1038
1054
  - `0.1.x`: secure-by-default core
1039
1055
  - `0.2.x`: OTel interoperability, security presets, allowlists
1056
+ - `0.3.x`: package modularization, sink/metrics hardening, and performance reliability updates
1040
1057
  - `1.0.0`: dynamic runtime controls and plugin stability
1041
1058
 
1042
- Current release version: `0.3.1`
1059
+ Current release version: `0.4.1`
1043
1060
 
1044
1061
  ## Changelog Policy
1045
1062
 
@@ -1094,4 +1111,4 @@ The pre-push hook runs:
1094
1111
 
1095
1112
  ## Roadmap Tracking
1096
1113
 
1097
- See [NEXT_STEPS.md](NEXT_STEPS.md) for the active `0.2.0` roadmap and release checklist.
1114
+ 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
@@ -621,6 +634,9 @@ The library supports configuration from code and env vars. Below are the most re
621
634
  | `METRICS_EXEMPLARS_ENABLED` | `false` | Enable exemplars where supported |
622
635
  | `METRICS_FORMAT` | `negotiate` | `prometheus`, `openmetrics`, or `negotiate` |
623
636
 
637
+ > [!CAUTION]
638
+ > The `/metrics` endpoint is **unauthenticated by default**. In production it should be restricted to internal networks (e.g. behind a Kubernetes `NetworkPolicy`, VPC security group, or ingress rule that only allows your Prometheus scraper). Exposing it publicly leaks service topology, error rates, and request patterns.
639
+
624
640
  ### Security and trust boundary
625
641
 
626
642
  | Variable | Default | Description |
@@ -958,8 +974,8 @@ Repository integration tests include:
958
974
 
959
975
  Reproducible benchmark harness and methodology:
960
976
  - Guide: [`benchmarks.md`](benchmarks.md)
961
- - Apps: `examples/benchmarks/plain_fastapi.py` and `examples/benchmarks/observer_fastapi.py`
962
- - Runner: `examples/benchmarks/run_local_benchmark.sh`
977
+ - Apps: `examples/benchmarks/app.py`
978
+ - Runner: `examples/benchmarks/harness.py`
963
979
 
964
980
  ---
965
981
 
@@ -967,9 +983,10 @@ Reproducible benchmark harness and methodology:
967
983
 
968
984
  - `0.1.x`: secure-by-default core
969
985
  - `0.2.x`: OTel interoperability, security presets, allowlists
986
+ - `0.3.x`: package modularization, sink/metrics hardening, and performance reliability updates
970
987
  - `1.0.0`: dynamic runtime controls and plugin stability
971
988
 
972
- Current release version: `0.3.1`
989
+ Current release version: `0.4.1`
973
990
 
974
991
  ## Changelog Policy
975
992
 
@@ -1024,4 +1041,4 @@ The pre-push hook runs:
1024
1041
 
1025
1042
  ## Roadmap Tracking
1026
1043
 
1027
- See [NEXT_STEPS.md](NEXT_STEPS.md) for the active `0.2.0` roadmap and release checklist.
1044
+ See [NEXT_STEPS.md](NEXT_STEPS.md) for the active roadmap and release checklist.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-observer
3
- Version: 0.3.2
3
+ Version: 0.4.1
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
@@ -392,6 +392,19 @@ Request arrives
392
392
  -> context cleared
393
393
  ```
394
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
+
395
408
  ---
396
409
 
397
410
  ## Example JSON Log Event
@@ -691,6 +704,9 @@ The library supports configuration from code and env vars. Below are the most re
691
704
  | `METRICS_EXEMPLARS_ENABLED` | `false` | Enable exemplars where supported |
692
705
  | `METRICS_FORMAT` | `negotiate` | `prometheus`, `openmetrics`, or `negotiate` |
693
706
 
707
+ > [!CAUTION]
708
+ > The `/metrics` endpoint is **unauthenticated by default**. In production it should be restricted to internal networks (e.g. behind a Kubernetes `NetworkPolicy`, VPC security group, or ingress rule that only allows your Prometheus scraper). Exposing it publicly leaks service topology, error rates, and request patterns.
709
+
694
710
  ### Security and trust boundary
695
711
 
696
712
  | Variable | Default | Description |
@@ -1028,8 +1044,8 @@ Repository integration tests include:
1028
1044
 
1029
1045
  Reproducible benchmark harness and methodology:
1030
1046
  - Guide: [`benchmarks.md`](benchmarks.md)
1031
- - Apps: `examples/benchmarks/plain_fastapi.py` and `examples/benchmarks/observer_fastapi.py`
1032
- - Runner: `examples/benchmarks/run_local_benchmark.sh`
1047
+ - Apps: `examples/benchmarks/app.py`
1048
+ - Runner: `examples/benchmarks/harness.py`
1033
1049
 
1034
1050
  ---
1035
1051
 
@@ -1037,9 +1053,10 @@ Reproducible benchmark harness and methodology:
1037
1053
 
1038
1054
  - `0.1.x`: secure-by-default core
1039
1055
  - `0.2.x`: OTel interoperability, security presets, allowlists
1056
+ - `0.3.x`: package modularization, sink/metrics hardening, and performance reliability updates
1040
1057
  - `1.0.0`: dynamic runtime controls and plugin stability
1041
1058
 
1042
- Current release version: `0.3.1`
1059
+ Current release version: `0.4.1`
1043
1060
 
1044
1061
  ## Changelog Policy
1045
1062
 
@@ -1094,4 +1111,4 @@ The pre-push hook runs:
1094
1111
 
1095
1112
  ## Roadmap Tracking
1096
1113
 
1097
- See [NEXT_STEPS.md](NEXT_STEPS.md) for the active `0.2.0` roadmap and release checklist.
1114
+ See [NEXT_STEPS.md](NEXT_STEPS.md) for the active roadmap and release checklist.
@@ -0,0 +1,106 @@
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_dx_parity.py
78
+ tests/test_excluded_url_precedence.py
79
+ tests/test_fastapi_install.py
80
+ tests/test_graphql_integrations.py
81
+ tests/test_log_queue_controls.py
82
+ tests/test_logging_schema_contract.py
83
+ tests/test_logtail_dlq.py
84
+ tests/test_loguru_bridge.py
85
+ tests/test_metrics_contracts.py
86
+ tests/test_metrics_optional_dependency.py
87
+ tests/test_middleware.py
88
+ tests/test_negotiate_content_type.py
89
+ tests/test_otel_integration.py
90
+ tests/test_otel_lifecycle.py
91
+ tests/test_otel_log_correlation.py
92
+ tests/test_otel_logs_from_env.py
93
+ tests/test_otel_logs_pipeline.py
94
+ tests/test_otel_metrics_from_env.py
95
+ tests/test_otlp_export_integration.py
96
+ tests/test_plugin_isolation.py
97
+ tests/test_propagation.py
98
+ tests/test_redaction_presets.py
99
+ tests/test_route_template_cardinality.py
100
+ tests/test_runtime_control_auth.py
101
+ tests/test_security_audit_fixes.py
102
+ tests/test_security_redaction.py
103
+ tests/test_sink_circuit_breaker.py
104
+ tests/test_sinks.py
105
+ tests/test_trust_boundary.py
106
+ tests/test_utils_refactors.py
@@ -0,0 +1 @@
1
+ __version__ = "0.4.1"
@@ -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
+ ]
@@ -0,0 +1,158 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import threading
5
+ import time
6
+ from dataclasses import dataclass
7
+ from typing import Callable, Literal
8
+
9
+ CircuitBreakerState = Literal["closed", "open", "half_open"]
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class SinkCircuitBreakerSnapshot:
14
+ sink_name: str
15
+ state: CircuitBreakerState
16
+ failure_threshold: int
17
+ recovery_timeout_seconds: float
18
+ consecutive_failures: int
19
+ handled_total: int
20
+ failures_total: int
21
+ skipped_total: int
22
+ opens_total: int
23
+ half_open_total: int
24
+ closes_total: int
25
+
26
+ def as_dict(self) -> dict[str, int | float | str]:
27
+ return {
28
+ "sink_name": self.sink_name,
29
+ "state": self.state,
30
+ "failure_threshold": self.failure_threshold,
31
+ "recovery_timeout_seconds": self.recovery_timeout_seconds,
32
+ "consecutive_failures": self.consecutive_failures,
33
+ "handled_total": self.handled_total,
34
+ "failures_total": self.failures_total,
35
+ "skipped_total": self.skipped_total,
36
+ "opens_total": self.opens_total,
37
+ "half_open_total": self.half_open_total,
38
+ "closes_total": self.closes_total,
39
+ }
40
+
41
+
42
+ class SinkCircuitBreakerHandler(logging.Handler):
43
+ """Protect sink handlers with a basic open/half-open/closed breaker."""
44
+
45
+ def __init__(
46
+ self,
47
+ *,
48
+ sink_name: str,
49
+ delegate: logging.Handler,
50
+ failure_threshold: int,
51
+ recovery_timeout_seconds: float,
52
+ clock: Callable[[], float] | None = None,
53
+ ) -> None:
54
+ super().__init__(level=delegate.level)
55
+ self.sink_name = sink_name
56
+ self._delegate = delegate
57
+ self._failure_threshold = max(1, int(failure_threshold))
58
+ self._recovery_timeout_seconds = max(0.001, float(recovery_timeout_seconds))
59
+ self._clock = clock or time.monotonic
60
+ self._lock = threading.Lock()
61
+
62
+ self._state: CircuitBreakerState = "closed"
63
+ self._opened_until = 0.0
64
+ self._consecutive_failures = 0
65
+ self._handled_total = 0
66
+ self._failures_total = 0
67
+ self._skipped_total = 0
68
+ self._opens_total = 0
69
+ self._half_open_total = 0
70
+ self._closes_total = 0
71
+
72
+ def emit(self, record: logging.LogRecord) -> None:
73
+ if self._should_skip():
74
+ return
75
+
76
+ try:
77
+ self._delegate.handle(record)
78
+ except Exception:
79
+ self._record_failure()
80
+ self.handleError(record)
81
+ return
82
+
83
+ self._record_success()
84
+
85
+ def setFormatter(self, fmt: logging.Formatter | None) -> None: # noqa: N802
86
+ super().setFormatter(fmt)
87
+ self._delegate.setFormatter(fmt)
88
+
89
+ def flush(self) -> None:
90
+ self._delegate.flush()
91
+
92
+ def close(self) -> None:
93
+ try:
94
+ self._delegate.close()
95
+ finally:
96
+ super().close()
97
+
98
+ def snapshot(self) -> SinkCircuitBreakerSnapshot:
99
+ with self._lock:
100
+ return SinkCircuitBreakerSnapshot(
101
+ sink_name=self.sink_name,
102
+ state=self._state,
103
+ failure_threshold=self._failure_threshold,
104
+ recovery_timeout_seconds=self._recovery_timeout_seconds,
105
+ consecutive_failures=self._consecutive_failures,
106
+ handled_total=self._handled_total,
107
+ failures_total=self._failures_total,
108
+ skipped_total=self._skipped_total,
109
+ opens_total=self._opens_total,
110
+ half_open_total=self._half_open_total,
111
+ closes_total=self._closes_total,
112
+ )
113
+
114
+ def _should_skip(self) -> bool:
115
+ with self._lock:
116
+ now = self._clock()
117
+ if self._state != "open":
118
+ return False
119
+ if now >= self._opened_until:
120
+ self._state = "half_open"
121
+ self._half_open_total += 1
122
+ return False
123
+ self._skipped_total += 1
124
+ return True
125
+
126
+ def _record_failure(self) -> None:
127
+ with self._lock:
128
+ now = self._clock()
129
+ self._failures_total += 1
130
+ self._consecutive_failures += 1
131
+
132
+ should_open = (
133
+ self._state == "half_open"
134
+ or self._consecutive_failures >= self._failure_threshold
135
+ )
136
+ if should_open:
137
+ self._state = "open"
138
+ self._opens_total += 1
139
+ self._opened_until = now + self._recovery_timeout_seconds
140
+ self._consecutive_failures = 0
141
+
142
+ def _record_success(self) -> None:
143
+ with self._lock:
144
+ self._handled_total += 1
145
+ self._consecutive_failures = 0
146
+ if self._state == "half_open":
147
+ self._state = "closed"
148
+ self._closes_total += 1
149
+ self._opened_until = 0.0
150
+
151
+
152
+ def get_sink_circuit_breaker_stats() -> dict[str, dict[str, int | float | str]]:
153
+ """Return per-sink circuit-breaker snapshots."""
154
+ from .state import _LOGGING_LOCK, _SINK_CIRCUIT_BREAKERS
155
+
156
+ with _LOGGING_LOCK:
157
+ breakers = list(_SINK_CIRCUIT_BREAKERS)
158
+ return {breaker.sink_name: breaker.snapshot().as_dict() for breaker in breakers}
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+
5
+ from ..plugins import apply_log_filters
6
+ from ..request_context import get_request_id, get_span_id, get_trace_id
7
+
8
+ _LOGGER = logging.getLogger("fastapiobserver.logging")
9
+
10
+
11
+ class RequestIdFilter(logging.Filter):
12
+ """Attach request ID from context vars to every log record."""
13
+
14
+ def filter(self, record: logging.LogRecord) -> bool:
15
+ if not getattr(record, "request_id", None):
16
+ record.request_id = get_request_id()
17
+ return True
18
+
19
+
20
+ class TraceContextFilter(logging.Filter):
21
+ """Attach OTel trace_id / span_id to every log record."""
22
+
23
+ def filter(self, record: logging.LogRecord) -> bool:
24
+ trace_id = get_trace_id()
25
+ span_id = get_span_id()
26
+
27
+ try:
28
+ from opentelemetry import trace as otel_trace # type: ignore
29
+
30
+ span = otel_trace.get_current_span()
31
+ span_context = span.get_span_context()
32
+ if span_context and span_context.is_valid:
33
+ trace_id = f"{span_context.trace_id:032x}"
34
+ span_id = f"{span_context.span_id:016x}"
35
+ except Exception:
36
+ _LOGGER.debug(
37
+ "trace_context_filter.otel_lookup_failed",
38
+ exc_info=True,
39
+ extra={"_skip_enrichers": True},
40
+ )
41
+
42
+ if trace_id:
43
+ record.trace_id = trace_id # type: ignore[attr-defined]
44
+ if span_id:
45
+ record.span_id = span_id # type: ignore[attr-defined]
46
+ return True
47
+
48
+
49
+ class PluginLogFilter(logging.Filter):
50
+ """Apply user-registered log filters with fault isolation."""
51
+
52
+ def filter(self, record: logging.LogRecord) -> bool:
53
+ return apply_log_filters(record)