fastapi-observer 1.0.0__tar.gz → 1.2.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 (116) hide show
  1. {fastapi_observer-1.0.0/src/fastapi_observer.egg-info → fastapi_observer-1.2.0}/PKG-INFO +118 -4
  2. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/README.md +111 -3
  3. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/pyproject.toml +8 -0
  4. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0/src/fastapi_observer.egg-info}/PKG-INFO +118 -4
  5. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapi_observer.egg-info/SOURCES.txt +7 -0
  6. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapi_observer.egg-info/requires.txt +8 -0
  7. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/__init__.py +22 -0
  8. fastapi_observer-1.2.0/src/fastapiobserver/_version.py +1 -0
  9. fastapi_observer-1.2.0/src/fastapiobserver/audit/__init__.py +14 -0
  10. fastapi_observer-1.2.0/src/fastapiobserver/audit/formatter.py +96 -0
  11. fastapi_observer-1.2.0/src/fastapiobserver/audit/providers.py +51 -0
  12. fastapi_observer-1.2.0/src/fastapiobserver/audit/verify.py +129 -0
  13. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/config.py +8 -0
  14. fastapi_observer-1.2.0/src/fastapiobserver/db_tracing.py +143 -0
  15. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/fastapi.py +36 -1
  16. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/logging/setup.py +15 -2
  17. fastapi_observer-1.2.0/tests/test_audit_chain.py +287 -0
  18. fastapi_observer-1.2.0/tests/test_db_tracing.py +166 -0
  19. fastapi_observer-1.0.0/src/fastapiobserver/_version.py +0 -1
  20. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/LICENSE +0 -0
  21. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/setup.cfg +0 -0
  22. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapi_observer.egg-info/dependency_links.txt +0 -0
  23. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapi_observer.egg-info/top_level.txt +0 -0
  24. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/control_plane.py +0 -0
  25. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/integrations/__init__.py +0 -0
  26. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/integrations/strawberry.py +0 -0
  27. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/logging/__init__.py +0 -0
  28. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/logging/circuit_breaker.py +0 -0
  29. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/logging/filters.py +0 -0
  30. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/logging/formatter.py +0 -0
  31. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/logging/queueing.py +0 -0
  32. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/logging/state.py +0 -0
  33. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/loguru.py +0 -0
  34. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/metrics/__init__.py +0 -0
  35. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/metrics/builder.py +0 -0
  36. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/metrics/contracts.py +0 -0
  37. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/metrics/endpoint.py +0 -0
  38. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/metrics/noop.py +0 -0
  39. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/metrics/pathing.py +0 -0
  40. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/metrics/prometheus/__init__.py +0 -0
  41. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/metrics/prometheus/backend.py +0 -0
  42. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/metrics/prometheus/client.py +0 -0
  43. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/metrics/prometheus/collector.py +0 -0
  44. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/metrics/prometheus/exemplars.py +0 -0
  45. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/metrics/prometheus/multiprocess.py +0 -0
  46. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/metrics/registry.py +0 -0
  47. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/middleware/__init__.py +0 -0
  48. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/middleware/body_capture.py +0 -0
  49. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/middleware/context.py +0 -0
  50. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/middleware/events.py +0 -0
  51. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/middleware/headers.py +0 -0
  52. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/middleware/ip.py +0 -0
  53. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/middleware/metrics.py +0 -0
  54. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/middleware/request_logging.py +0 -0
  55. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/middleware/span_errors.py +0 -0
  56. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/otel/__init__.py +0 -0
  57. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/otel/lifecycle.py +0 -0
  58. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/otel/logs.py +0 -0
  59. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/otel/metrics.py +0 -0
  60. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/otel/resource.py +0 -0
  61. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/otel/settings.py +0 -0
  62. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/otel/tracing.py +0 -0
  63. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/otel/types.py +0 -0
  64. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/plugins.py +0 -0
  65. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/propagation.py +0 -0
  66. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/py.typed +0 -0
  67. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/request_context.py +0 -0
  68. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/security/__init__.py +0 -0
  69. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/security/normalize.py +0 -0
  70. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/security/policy.py +0 -0
  71. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/security/proxies.py +0 -0
  72. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/security/redaction.py +0 -0
  73. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/sinks/__init__.py +0 -0
  74. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/sinks/builtin.py +0 -0
  75. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/sinks/discovery.py +0 -0
  76. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/sinks/factory.py +0 -0
  77. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/sinks/logtail/__init__.py +0 -0
  78. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/sinks/logtail/dlq.py +0 -0
  79. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/sinks/logtail/handler.py +0 -0
  80. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/sinks/logtail/sink.py +0 -0
  81. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/sinks/protocol.py +0 -0
  82. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/sinks/registry.py +0 -0
  83. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/sinks/stats.py +0 -0
  84. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/src/fastapiobserver/utils.py +0 -0
  85. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_body_media_allowlist.py +0 -0
  86. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_config_settings.py +0 -0
  87. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_dx_parity.py +0 -0
  88. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_excluded_url_precedence.py +0 -0
  89. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_fastapi_install.py +0 -0
  90. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_graphql_integrations.py +0 -0
  91. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_log_queue_controls.py +0 -0
  92. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_logging_schema_contract.py +0 -0
  93. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_logtail_dlq.py +0 -0
  94. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_loguru_bridge.py +0 -0
  95. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_metrics_contracts.py +0 -0
  96. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_metrics_optional_dependency.py +0 -0
  97. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_middleware.py +0 -0
  98. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_negotiate_content_type.py +0 -0
  99. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_otel_integration.py +0 -0
  100. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_otel_lifecycle.py +0 -0
  101. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_otel_log_correlation.py +0 -0
  102. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_otel_logs_from_env.py +0 -0
  103. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_otel_logs_pipeline.py +0 -0
  104. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_otel_metrics_from_env.py +0 -0
  105. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_otlp_export_integration.py +0 -0
  106. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_plugin_isolation.py +0 -0
  107. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_propagation.py +0 -0
  108. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_redaction_presets.py +0 -0
  109. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_route_template_cardinality.py +0 -0
  110. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_runtime_control_auth.py +0 -0
  111. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_security_audit_fixes.py +0 -0
  112. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_security_redaction.py +0 -0
  113. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_sink_circuit_breaker.py +0 -0
  114. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_sinks.py +0 -0
  115. {fastapi_observer-1.0.0 → fastapi_observer-1.2.0}/tests/test_trust_boundary.py +0 -0
  116. {fastapi_observer-1.0.0 → fastapi_observer-1.2.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: 1.0.0
3
+ Version: 1.2.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
@@ -45,6 +45,10 @@ Provides-Extra: otel-httpx
45
45
  Requires-Dist: opentelemetry-instrumentation-httpx>=0.60b1; extra == "otel-httpx"
46
46
  Provides-Extra: otel-requests
47
47
  Requires-Dist: opentelemetry-instrumentation-requests>=0.60b1; extra == "otel-requests"
48
+ Provides-Extra: audit
49
+ Requires-Dist: cryptography>=46.0.5; extra == "audit"
50
+ Provides-Extra: otel-sqlalchemy
51
+ Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.60b1; extra == "otel-sqlalchemy"
48
52
  Provides-Extra: all
49
53
  Requires-Dist: orjson>=3.11.7; extra == "all"
50
54
  Requires-Dist: prometheus-client>=0.24.1; extra == "all"
@@ -56,6 +60,8 @@ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.60b1; extra == "all"
56
60
  Requires-Dist: opentelemetry-instrumentation-logging>=0.60b1; extra == "all"
57
61
  Requires-Dist: opentelemetry-instrumentation-httpx>=0.60b1; extra == "all"
58
62
  Requires-Dist: opentelemetry-instrumentation-requests>=0.60b1; extra == "all"
63
+ Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.60b1; extra == "all"
64
+ Requires-Dist: cryptography>=46.0.5; extra == "all"
59
65
  Provides-Extra: dev
60
66
  Requires-Dist: httpx>=0.28.1; extra == "dev"
61
67
  Requires-Dist: loguru>=0.7.2; extra == "dev"
@@ -645,6 +651,8 @@ The `examples/` directory contains runnable demos:
645
651
  | [`security_presets_app.py`](examples/security_presets_app.py) | Preset-based security policy |
646
652
  | [`allowlist_app.py`](examples/allowlist_app.py) | Allowlist-only sanitization |
647
653
  | [`otel_app.py`](examples/otel_app.py) | OTel tracing and resource attributes |
654
+ | [`audit_app.py`](examples/audit_app.py) | Tamper-evident cryptographic log signatures |
655
+ | [`db_tracing_app.py`](examples/db_tracing_app.py) | SQLCommenter and database trace injection |
648
656
  | [`graphql_app.py`](examples/graphql_app.py) | Native Strawberry GraphQL observability |
649
657
  | [`benchmarks/`](examples/benchmarks/) | Baseline vs observer benchmark harness |
650
658
  | [`k8s/`](examples/k8s/) | Kubernetes-native stack with Prometheus + Loki + Tempo + Grafana |
@@ -776,6 +784,111 @@ Notes:
776
784
  > [!TIP]
777
785
  > The Logtail Dead Letter Queue (DLQ) provides best-effort local durability. If the internal memory queue overflows under immense API pressure (`queue.Full`), or if an external network partition completely exhausts the outbound HTTP retry backoff, the dropped log payloads are immediately salvaged into local NDJSON envelopes. You can replay these files to BetterStack later using the provided `scripts/replay_dlq.py` utility.
778
786
 
787
+ ### Tamper-evident audit logging
788
+
789
+ For regulated industries (Fintech, Healthcare, SOC 2) where you must prove logs were not altered, deleted, or reordered:
790
+
791
+ ```bash
792
+ pip install "fastapi-observer[audit]"
793
+ export OBS_AUDIT_SECRET_KEY="your-signing-secret"
794
+ export OBS_AUDIT_LOGGING_ENABLED="true"
795
+ ```
796
+
797
+ ```python
798
+ install_observability(app, settings)
799
+ # Every JSON log now contains _audit_seq and _audit_sig fields
800
+ ```
801
+
802
+ Each log record is chained via HMAC-SHA256: record B's signature includes the signature of record A. Breaking any link (tamper, delete, reorder) invalidates the chain.
803
+
804
+ | Variable | Default | Description |
805
+ |---|---|---|
806
+ | `OBS_AUDIT_LOGGING_ENABLED` | `false` | Enable HMAC-SHA256 hash chain |
807
+ | `OBS_AUDIT_KEY_ENV_VAR` | `OBS_AUDIT_SECRET_KEY` | Name of env var containing the signing key |
808
+ | `OBS_AUDIT_SECRET_KEY` | - | The HMAC signing key (read by `LocalHMACProvider`) |
809
+
810
+ Custom key provider (e.g. KMS / Vault):
811
+
812
+ ```python
813
+ from fastapiobserver import AuditKeyProvider
814
+
815
+ class VaultKeyProvider:
816
+ def get_key(self) -> bytes:
817
+ return vault_client.get_secret("audit-signing-key").encode()
818
+
819
+ install_observability(app, settings, audit_key_provider=VaultKeyProvider())
820
+ ```
821
+
822
+ Verify logs with the CLI:
823
+
824
+ ```bash
825
+ export OBS_AUDIT_SECRET_KEY="your-signing-secret"
826
+ python scripts/verify_audit_chain.py exported_logs.ndjson
827
+ # PASS — 1042 records verified, chain intact.
828
+ ```
829
+
830
+ ### SQLCommenter & database tracing
831
+
832
+ Bridges the gap between FastAPI application traces and database performance monitoring. When enabled, the `trace_id` is automatically injected into raw SQL queries as a comment:
833
+
834
+ ```sql
835
+ SELECT * FROM users /*traceparent='00-abc123def456-01',route='/api/users'*/
836
+ ```
837
+
838
+ This lets DBAs correlate slow Postgres/MySQL queries directly back to the originating HTTP request.
839
+
840
+ ```bash
841
+ pip install "fastapi-observer[otel-sqlalchemy]"
842
+ ```
843
+
844
+ **Automatic** (via `install_observability`):
845
+
846
+ ```python
847
+ from sqlalchemy import create_engine
848
+
849
+ engine = create_engine("postgresql://...")
850
+ install_observability(app, settings, otel_settings=otel_settings, db_engine=engine)
851
+ ```
852
+
853
+ Multiple engines (e.g. read/write replicas):
854
+
855
+ ```python
856
+ write_engine = create_engine("postgresql://primary/...")
857
+ read_engine = create_engine("postgresql://replica/...")
858
+ install_observability(
859
+ app, settings,
860
+ otel_settings=otel_settings,
861
+ db_engine=[write_engine, read_engine],
862
+ db_commenter_options={"opentelemetry_values": True, "route": False},
863
+ )
864
+ ```
865
+
866
+ **Manual** (standalone):
867
+
868
+ ```python
869
+ from fastapiobserver import instrument_sqlalchemy, instrument_sqlalchemy_async
870
+
871
+ # Sync engine
872
+ instrument_sqlalchemy(engine)
873
+
874
+ # Async engine (extracts .sync_engine automatically)
875
+ instrument_sqlalchemy_async(async_engine)
876
+ ```
877
+
878
+ Custom commenter options:
879
+
880
+ ```python
881
+ instrument_sqlalchemy(engine, commenter_options={
882
+ "opentelemetry_values": True, # traceparent (default: True)
883
+ "db_driver": True, # e.g. psycopg2 (default: True)
884
+ "route": True, # e.g. /api/users (default: True)
885
+ "db_framework": True, # e.g. sqlalchemy (default: False)
886
+ })
887
+ ```
888
+
889
+ > [!CAUTION]
890
+ > **Database statement PII risk:** The OpenTelemetry SQLAlchemy instrumentor captures raw SQL as the `db.statement` span attribute and exports it directly to your tracing backend (Jaeger, Tempo, Datadog). These span attributes are **not** scrubbed by `fastapi-observer`'s `SecurityPolicy`. If your application has poorly parameterized queries (e.g. `WHERE email = 'user@example.com'`), PII will leak into your trace storage. Always use parameterized queries and review your `db.statement` exports in production.
891
+
779
892
  ---
780
893
 
781
894
  ## Advanced Operations
@@ -1055,9 +1168,10 @@ Reproducible benchmark harness and methodology:
1055
1168
  - `0.2.x`: OTel interoperability, security presets, allowlists
1056
1169
  - `0.3.x`: GraphQL observability, error fingerprinting, and Logtail DLQ durability
1057
1170
  - `0.4.x`: package modularization, sink/registry hardening, and runtime control token rotation
1058
- - `1.0.0`: first stable release contract for production deployments
1171
+ - `1.0.x`: first stable release contract for production deployments
1172
+ - `1.2.0`: tamper-evident audit logging and SQLAlchemy trace/commenter integration
1059
1173
 
1060
- Current release version: `1.0.0`
1174
+ Current release version: `1.2.0`
1061
1175
 
1062
1176
  ## Changelog Policy
1063
1177
 
@@ -1070,7 +1184,7 @@ Breaking changes must be listed under a `Breaking Changes` section in `CHANGELOG
1070
1184
  Recommended release command (uses `.env` with `PYPI_TOKEN`):
1071
1185
 
1072
1186
  ```bash
1073
- scripts/deploy_pypi.sh --tag v1.0.0 --push-tag
1187
+ scripts/deploy_pypi.sh --tag v1.2.0 --push-tag
1074
1188
  ```
1075
1189
 
1076
1190
  ### 1) Build distributions
@@ -575,6 +575,8 @@ The `examples/` directory contains runnable demos:
575
575
  | [`security_presets_app.py`](examples/security_presets_app.py) | Preset-based security policy |
576
576
  | [`allowlist_app.py`](examples/allowlist_app.py) | Allowlist-only sanitization |
577
577
  | [`otel_app.py`](examples/otel_app.py) | OTel tracing and resource attributes |
578
+ | [`audit_app.py`](examples/audit_app.py) | Tamper-evident cryptographic log signatures |
579
+ | [`db_tracing_app.py`](examples/db_tracing_app.py) | SQLCommenter and database trace injection |
578
580
  | [`graphql_app.py`](examples/graphql_app.py) | Native Strawberry GraphQL observability |
579
581
  | [`benchmarks/`](examples/benchmarks/) | Baseline vs observer benchmark harness |
580
582
  | [`k8s/`](examples/k8s/) | Kubernetes-native stack with Prometheus + Loki + Tempo + Grafana |
@@ -706,6 +708,111 @@ Notes:
706
708
  > [!TIP]
707
709
  > The Logtail Dead Letter Queue (DLQ) provides best-effort local durability. If the internal memory queue overflows under immense API pressure (`queue.Full`), or if an external network partition completely exhausts the outbound HTTP retry backoff, the dropped log payloads are immediately salvaged into local NDJSON envelopes. You can replay these files to BetterStack later using the provided `scripts/replay_dlq.py` utility.
708
710
 
711
+ ### Tamper-evident audit logging
712
+
713
+ For regulated industries (Fintech, Healthcare, SOC 2) where you must prove logs were not altered, deleted, or reordered:
714
+
715
+ ```bash
716
+ pip install "fastapi-observer[audit]"
717
+ export OBS_AUDIT_SECRET_KEY="your-signing-secret"
718
+ export OBS_AUDIT_LOGGING_ENABLED="true"
719
+ ```
720
+
721
+ ```python
722
+ install_observability(app, settings)
723
+ # Every JSON log now contains _audit_seq and _audit_sig fields
724
+ ```
725
+
726
+ Each log record is chained via HMAC-SHA256: record B's signature includes the signature of record A. Breaking any link (tamper, delete, reorder) invalidates the chain.
727
+
728
+ | Variable | Default | Description |
729
+ |---|---|---|
730
+ | `OBS_AUDIT_LOGGING_ENABLED` | `false` | Enable HMAC-SHA256 hash chain |
731
+ | `OBS_AUDIT_KEY_ENV_VAR` | `OBS_AUDIT_SECRET_KEY` | Name of env var containing the signing key |
732
+ | `OBS_AUDIT_SECRET_KEY` | - | The HMAC signing key (read by `LocalHMACProvider`) |
733
+
734
+ Custom key provider (e.g. KMS / Vault):
735
+
736
+ ```python
737
+ from fastapiobserver import AuditKeyProvider
738
+
739
+ class VaultKeyProvider:
740
+ def get_key(self) -> bytes:
741
+ return vault_client.get_secret("audit-signing-key").encode()
742
+
743
+ install_observability(app, settings, audit_key_provider=VaultKeyProvider())
744
+ ```
745
+
746
+ Verify logs with the CLI:
747
+
748
+ ```bash
749
+ export OBS_AUDIT_SECRET_KEY="your-signing-secret"
750
+ python scripts/verify_audit_chain.py exported_logs.ndjson
751
+ # PASS — 1042 records verified, chain intact.
752
+ ```
753
+
754
+ ### SQLCommenter & database tracing
755
+
756
+ Bridges the gap between FastAPI application traces and database performance monitoring. When enabled, the `trace_id` is automatically injected into raw SQL queries as a comment:
757
+
758
+ ```sql
759
+ SELECT * FROM users /*traceparent='00-abc123def456-01',route='/api/users'*/
760
+ ```
761
+
762
+ This lets DBAs correlate slow Postgres/MySQL queries directly back to the originating HTTP request.
763
+
764
+ ```bash
765
+ pip install "fastapi-observer[otel-sqlalchemy]"
766
+ ```
767
+
768
+ **Automatic** (via `install_observability`):
769
+
770
+ ```python
771
+ from sqlalchemy import create_engine
772
+
773
+ engine = create_engine("postgresql://...")
774
+ install_observability(app, settings, otel_settings=otel_settings, db_engine=engine)
775
+ ```
776
+
777
+ Multiple engines (e.g. read/write replicas):
778
+
779
+ ```python
780
+ write_engine = create_engine("postgresql://primary/...")
781
+ read_engine = create_engine("postgresql://replica/...")
782
+ install_observability(
783
+ app, settings,
784
+ otel_settings=otel_settings,
785
+ db_engine=[write_engine, read_engine],
786
+ db_commenter_options={"opentelemetry_values": True, "route": False},
787
+ )
788
+ ```
789
+
790
+ **Manual** (standalone):
791
+
792
+ ```python
793
+ from fastapiobserver import instrument_sqlalchemy, instrument_sqlalchemy_async
794
+
795
+ # Sync engine
796
+ instrument_sqlalchemy(engine)
797
+
798
+ # Async engine (extracts .sync_engine automatically)
799
+ instrument_sqlalchemy_async(async_engine)
800
+ ```
801
+
802
+ Custom commenter options:
803
+
804
+ ```python
805
+ instrument_sqlalchemy(engine, commenter_options={
806
+ "opentelemetry_values": True, # traceparent (default: True)
807
+ "db_driver": True, # e.g. psycopg2 (default: True)
808
+ "route": True, # e.g. /api/users (default: True)
809
+ "db_framework": True, # e.g. sqlalchemy (default: False)
810
+ })
811
+ ```
812
+
813
+ > [!CAUTION]
814
+ > **Database statement PII risk:** The OpenTelemetry SQLAlchemy instrumentor captures raw SQL as the `db.statement` span attribute and exports it directly to your tracing backend (Jaeger, Tempo, Datadog). These span attributes are **not** scrubbed by `fastapi-observer`'s `SecurityPolicy`. If your application has poorly parameterized queries (e.g. `WHERE email = 'user@example.com'`), PII will leak into your trace storage. Always use parameterized queries and review your `db.statement` exports in production.
815
+
709
816
  ---
710
817
 
711
818
  ## Advanced Operations
@@ -985,9 +1092,10 @@ Reproducible benchmark harness and methodology:
985
1092
  - `0.2.x`: OTel interoperability, security presets, allowlists
986
1093
  - `0.3.x`: GraphQL observability, error fingerprinting, and Logtail DLQ durability
987
1094
  - `0.4.x`: package modularization, sink/registry hardening, and runtime control token rotation
988
- - `1.0.0`: first stable release contract for production deployments
1095
+ - `1.0.x`: first stable release contract for production deployments
1096
+ - `1.2.0`: tamper-evident audit logging and SQLAlchemy trace/commenter integration
989
1097
 
990
- Current release version: `1.0.0`
1098
+ Current release version: `1.2.0`
991
1099
 
992
1100
  ## Changelog Policy
993
1101
 
@@ -1000,7 +1108,7 @@ Breaking changes must be listed under a `Breaking Changes` section in `CHANGELOG
1000
1108
  Recommended release command (uses `.env` with `PYPI_TOKEN`):
1001
1109
 
1002
1110
  ```bash
1003
- scripts/deploy_pypi.sh --tag v1.0.0 --push-tag
1111
+ scripts/deploy_pypi.sh --tag v1.2.0 --push-tag
1004
1112
  ```
1005
1113
 
1006
1114
  ### 1) Build distributions
@@ -65,6 +65,12 @@ otel-httpx = [
65
65
  otel-requests = [
66
66
  "opentelemetry-instrumentation-requests>=0.60b1",
67
67
  ]
68
+ audit = [
69
+ "cryptography>=46.0.5",
70
+ ]
71
+ otel-sqlalchemy = [
72
+ "opentelemetry-instrumentation-sqlalchemy>=0.60b1",
73
+ ]
68
74
  all = [
69
75
  "orjson>=3.11.7",
70
76
  "prometheus-client>=0.24.1",
@@ -76,6 +82,8 @@ all = [
76
82
  "opentelemetry-instrumentation-logging>=0.60b1",
77
83
  "opentelemetry-instrumentation-httpx>=0.60b1",
78
84
  "opentelemetry-instrumentation-requests>=0.60b1",
85
+ "opentelemetry-instrumentation-sqlalchemy>=0.60b1",
86
+ "cryptography>=46.0.5",
79
87
  ]
80
88
  dev = [
81
89
  "httpx>=0.28.1",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-observer
3
- Version: 1.0.0
3
+ Version: 1.2.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
@@ -45,6 +45,10 @@ Provides-Extra: otel-httpx
45
45
  Requires-Dist: opentelemetry-instrumentation-httpx>=0.60b1; extra == "otel-httpx"
46
46
  Provides-Extra: otel-requests
47
47
  Requires-Dist: opentelemetry-instrumentation-requests>=0.60b1; extra == "otel-requests"
48
+ Provides-Extra: audit
49
+ Requires-Dist: cryptography>=46.0.5; extra == "audit"
50
+ Provides-Extra: otel-sqlalchemy
51
+ Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.60b1; extra == "otel-sqlalchemy"
48
52
  Provides-Extra: all
49
53
  Requires-Dist: orjson>=3.11.7; extra == "all"
50
54
  Requires-Dist: prometheus-client>=0.24.1; extra == "all"
@@ -56,6 +60,8 @@ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.60b1; extra == "all"
56
60
  Requires-Dist: opentelemetry-instrumentation-logging>=0.60b1; extra == "all"
57
61
  Requires-Dist: opentelemetry-instrumentation-httpx>=0.60b1; extra == "all"
58
62
  Requires-Dist: opentelemetry-instrumentation-requests>=0.60b1; extra == "all"
63
+ Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.60b1; extra == "all"
64
+ Requires-Dist: cryptography>=46.0.5; extra == "all"
59
65
  Provides-Extra: dev
60
66
  Requires-Dist: httpx>=0.28.1; extra == "dev"
61
67
  Requires-Dist: loguru>=0.7.2; extra == "dev"
@@ -645,6 +651,8 @@ The `examples/` directory contains runnable demos:
645
651
  | [`security_presets_app.py`](examples/security_presets_app.py) | Preset-based security policy |
646
652
  | [`allowlist_app.py`](examples/allowlist_app.py) | Allowlist-only sanitization |
647
653
  | [`otel_app.py`](examples/otel_app.py) | OTel tracing and resource attributes |
654
+ | [`audit_app.py`](examples/audit_app.py) | Tamper-evident cryptographic log signatures |
655
+ | [`db_tracing_app.py`](examples/db_tracing_app.py) | SQLCommenter and database trace injection |
648
656
  | [`graphql_app.py`](examples/graphql_app.py) | Native Strawberry GraphQL observability |
649
657
  | [`benchmarks/`](examples/benchmarks/) | Baseline vs observer benchmark harness |
650
658
  | [`k8s/`](examples/k8s/) | Kubernetes-native stack with Prometheus + Loki + Tempo + Grafana |
@@ -776,6 +784,111 @@ Notes:
776
784
  > [!TIP]
777
785
  > The Logtail Dead Letter Queue (DLQ) provides best-effort local durability. If the internal memory queue overflows under immense API pressure (`queue.Full`), or if an external network partition completely exhausts the outbound HTTP retry backoff, the dropped log payloads are immediately salvaged into local NDJSON envelopes. You can replay these files to BetterStack later using the provided `scripts/replay_dlq.py` utility.
778
786
 
787
+ ### Tamper-evident audit logging
788
+
789
+ For regulated industries (Fintech, Healthcare, SOC 2) where you must prove logs were not altered, deleted, or reordered:
790
+
791
+ ```bash
792
+ pip install "fastapi-observer[audit]"
793
+ export OBS_AUDIT_SECRET_KEY="your-signing-secret"
794
+ export OBS_AUDIT_LOGGING_ENABLED="true"
795
+ ```
796
+
797
+ ```python
798
+ install_observability(app, settings)
799
+ # Every JSON log now contains _audit_seq and _audit_sig fields
800
+ ```
801
+
802
+ Each log record is chained via HMAC-SHA256: record B's signature includes the signature of record A. Breaking any link (tamper, delete, reorder) invalidates the chain.
803
+
804
+ | Variable | Default | Description |
805
+ |---|---|---|
806
+ | `OBS_AUDIT_LOGGING_ENABLED` | `false` | Enable HMAC-SHA256 hash chain |
807
+ | `OBS_AUDIT_KEY_ENV_VAR` | `OBS_AUDIT_SECRET_KEY` | Name of env var containing the signing key |
808
+ | `OBS_AUDIT_SECRET_KEY` | - | The HMAC signing key (read by `LocalHMACProvider`) |
809
+
810
+ Custom key provider (e.g. KMS / Vault):
811
+
812
+ ```python
813
+ from fastapiobserver import AuditKeyProvider
814
+
815
+ class VaultKeyProvider:
816
+ def get_key(self) -> bytes:
817
+ return vault_client.get_secret("audit-signing-key").encode()
818
+
819
+ install_observability(app, settings, audit_key_provider=VaultKeyProvider())
820
+ ```
821
+
822
+ Verify logs with the CLI:
823
+
824
+ ```bash
825
+ export OBS_AUDIT_SECRET_KEY="your-signing-secret"
826
+ python scripts/verify_audit_chain.py exported_logs.ndjson
827
+ # PASS — 1042 records verified, chain intact.
828
+ ```
829
+
830
+ ### SQLCommenter & database tracing
831
+
832
+ Bridges the gap between FastAPI application traces and database performance monitoring. When enabled, the `trace_id` is automatically injected into raw SQL queries as a comment:
833
+
834
+ ```sql
835
+ SELECT * FROM users /*traceparent='00-abc123def456-01',route='/api/users'*/
836
+ ```
837
+
838
+ This lets DBAs correlate slow Postgres/MySQL queries directly back to the originating HTTP request.
839
+
840
+ ```bash
841
+ pip install "fastapi-observer[otel-sqlalchemy]"
842
+ ```
843
+
844
+ **Automatic** (via `install_observability`):
845
+
846
+ ```python
847
+ from sqlalchemy import create_engine
848
+
849
+ engine = create_engine("postgresql://...")
850
+ install_observability(app, settings, otel_settings=otel_settings, db_engine=engine)
851
+ ```
852
+
853
+ Multiple engines (e.g. read/write replicas):
854
+
855
+ ```python
856
+ write_engine = create_engine("postgresql://primary/...")
857
+ read_engine = create_engine("postgresql://replica/...")
858
+ install_observability(
859
+ app, settings,
860
+ otel_settings=otel_settings,
861
+ db_engine=[write_engine, read_engine],
862
+ db_commenter_options={"opentelemetry_values": True, "route": False},
863
+ )
864
+ ```
865
+
866
+ **Manual** (standalone):
867
+
868
+ ```python
869
+ from fastapiobserver import instrument_sqlalchemy, instrument_sqlalchemy_async
870
+
871
+ # Sync engine
872
+ instrument_sqlalchemy(engine)
873
+
874
+ # Async engine (extracts .sync_engine automatically)
875
+ instrument_sqlalchemy_async(async_engine)
876
+ ```
877
+
878
+ Custom commenter options:
879
+
880
+ ```python
881
+ instrument_sqlalchemy(engine, commenter_options={
882
+ "opentelemetry_values": True, # traceparent (default: True)
883
+ "db_driver": True, # e.g. psycopg2 (default: True)
884
+ "route": True, # e.g. /api/users (default: True)
885
+ "db_framework": True, # e.g. sqlalchemy (default: False)
886
+ })
887
+ ```
888
+
889
+ > [!CAUTION]
890
+ > **Database statement PII risk:** The OpenTelemetry SQLAlchemy instrumentor captures raw SQL as the `db.statement` span attribute and exports it directly to your tracing backend (Jaeger, Tempo, Datadog). These span attributes are **not** scrubbed by `fastapi-observer`'s `SecurityPolicy`. If your application has poorly parameterized queries (e.g. `WHERE email = 'user@example.com'`), PII will leak into your trace storage. Always use parameterized queries and review your `db.statement` exports in production.
891
+
779
892
  ---
780
893
 
781
894
  ## Advanced Operations
@@ -1055,9 +1168,10 @@ Reproducible benchmark harness and methodology:
1055
1168
  - `0.2.x`: OTel interoperability, security presets, allowlists
1056
1169
  - `0.3.x`: GraphQL observability, error fingerprinting, and Logtail DLQ durability
1057
1170
  - `0.4.x`: package modularization, sink/registry hardening, and runtime control token rotation
1058
- - `1.0.0`: first stable release contract for production deployments
1171
+ - `1.0.x`: first stable release contract for production deployments
1172
+ - `1.2.0`: tamper-evident audit logging and SQLAlchemy trace/commenter integration
1059
1173
 
1060
- Current release version: `1.0.0`
1174
+ Current release version: `1.2.0`
1061
1175
 
1062
1176
  ## Changelog Policy
1063
1177
 
@@ -1070,7 +1184,7 @@ Breaking changes must be listed under a `Breaking Changes` section in `CHANGELOG
1070
1184
  Recommended release command (uses `.env` with `PYPI_TOKEN`):
1071
1185
 
1072
1186
  ```bash
1073
- scripts/deploy_pypi.sh --tag v1.0.0 --push-tag
1187
+ scripts/deploy_pypi.sh --tag v1.2.0 --push-tag
1074
1188
  ```
1075
1189
 
1076
1190
  ### 1) Build distributions
@@ -10,6 +10,7 @@ src/fastapiobserver/__init__.py
10
10
  src/fastapiobserver/_version.py
11
11
  src/fastapiobserver/config.py
12
12
  src/fastapiobserver/control_plane.py
13
+ src/fastapiobserver/db_tracing.py
13
14
  src/fastapiobserver/fastapi.py
14
15
  src/fastapiobserver/loguru.py
15
16
  src/fastapiobserver/plugins.py
@@ -17,6 +18,10 @@ src/fastapiobserver/propagation.py
17
18
  src/fastapiobserver/py.typed
18
19
  src/fastapiobserver/request_context.py
19
20
  src/fastapiobserver/utils.py
21
+ src/fastapiobserver/audit/__init__.py
22
+ src/fastapiobserver/audit/formatter.py
23
+ src/fastapiobserver/audit/providers.py
24
+ src/fastapiobserver/audit/verify.py
20
25
  src/fastapiobserver/integrations/__init__.py
21
26
  src/fastapiobserver/integrations/strawberry.py
22
27
  src/fastapiobserver/logging/__init__.py
@@ -72,8 +77,10 @@ src/fastapiobserver/sinks/logtail/__init__.py
72
77
  src/fastapiobserver/sinks/logtail/dlq.py
73
78
  src/fastapiobserver/sinks/logtail/handler.py
74
79
  src/fastapiobserver/sinks/logtail/sink.py
80
+ tests/test_audit_chain.py
75
81
  tests/test_body_media_allowlist.py
76
82
  tests/test_config_settings.py
83
+ tests/test_db_tracing.py
77
84
  tests/test_dx_parity.py
78
85
  tests/test_excluded_url_precedence.py
79
86
  tests/test_fastapi_install.py
@@ -13,6 +13,11 @@ opentelemetry-instrumentation-fastapi>=0.60b1
13
13
  opentelemetry-instrumentation-logging>=0.60b1
14
14
  opentelemetry-instrumentation-httpx>=0.60b1
15
15
  opentelemetry-instrumentation-requests>=0.60b1
16
+ opentelemetry-instrumentation-sqlalchemy>=0.60b1
17
+ cryptography>=46.0.5
18
+
19
+ [audit]
20
+ cryptography>=46.0.5
16
21
 
17
22
  [dev]
18
23
  httpx>=0.28.1
@@ -44,5 +49,8 @@ opentelemetry-instrumentation-httpx>=0.60b1
44
49
  [otel-requests]
45
50
  opentelemetry-instrumentation-requests>=0.60b1
46
51
 
52
+ [otel-sqlalchemy]
53
+ opentelemetry-instrumentation-sqlalchemy>=0.60b1
54
+
47
55
  [prometheus]
48
56
  prometheus-client>=0.24.1
@@ -1,6 +1,18 @@
1
1
  from ._version import __version__
2
+ from .audit import (
3
+ AuditChainFormatter,
4
+ AuditKeyProvider,
5
+ AuditVerificationResult,
6
+ LocalHMACProvider,
7
+ verify_audit_chain,
8
+ )
2
9
  from .config import ObservabilitySettings
3
10
  from .control_plane import RuntimeControlSettings, mount_control_plane
11
+ from .db_tracing import (
12
+ instrument_sqlalchemy,
13
+ instrument_sqlalchemy_async,
14
+ uninstrument_sqlalchemy,
15
+ )
4
16
  from .fastapi import install_observability
5
17
  from .logging import (
6
18
  LOG_SCHEMA_VERSION,
@@ -165,4 +177,14 @@ __all__ = [
165
177
  "set_user_context",
166
178
  # Version
167
179
  "__version__",
180
+ # Audit
181
+ "AuditChainFormatter",
182
+ "AuditKeyProvider",
183
+ "AuditVerificationResult",
184
+ "LocalHMACProvider",
185
+ "verify_audit_chain",
186
+ # Database tracing
187
+ "instrument_sqlalchemy",
188
+ "instrument_sqlalchemy_async",
189
+ "uninstrument_sqlalchemy",
168
190
  ]
@@ -0,0 +1 @@
1
+ __version__ = "1.2.0"
@@ -0,0 +1,14 @@
1
+ """Tamper-evident audit logging — optional HMAC-SHA256 hash chain."""
2
+ from __future__ import annotations
3
+
4
+ from .formatter import AuditChainFormatter
5
+ from .providers import AuditKeyProvider, LocalHMACProvider
6
+ from .verify import AuditVerificationResult, verify_audit_chain
7
+
8
+ __all__ = [
9
+ "AuditChainFormatter",
10
+ "AuditKeyProvider",
11
+ "AuditVerificationResult",
12
+ "LocalHMACProvider",
13
+ "verify_audit_chain",
14
+ ]