fastapi-observer 1.4.0__tar.gz → 1.5.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.
- {fastapi_observer-1.4.0/src/fastapi_observer.egg-info → fastapi_observer-1.5.0}/PKG-INFO +22 -22
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/README.md +18 -16
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/pyproject.toml +4 -6
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0/src/fastapi_observer.egg-info}/PKG-INFO +22 -22
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapi_observer.egg-info/requires.txt +1 -6
- fastapi_observer-1.5.0/src/fastapiobserver/_version.py +1 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/fastapi.py +7 -0
- fastapi_observer-1.5.0/src/fastapiobserver/logging/filters.py +61 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/logging/formatter.py +9 -4
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/logging/queueing.py +11 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/middleware/metrics.py +6 -2
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/middleware/request_logging.py +31 -19
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/otel/tracing.py +0 -14
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/plugins.py +3 -1
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/security/normalize.py +4 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/security/proxies.py +15 -10
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/security/redaction.py +37 -20
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_fastapi_install.py +12 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_logging_schema_contract.py +28 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_otel_log_correlation.py +7 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_trust_boundary.py +22 -1
- fastapi_observer-1.4.0/src/fastapiobserver/_version.py +0 -1
- fastapi_observer-1.4.0/src/fastapiobserver/logging/filters.py +0 -53
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/LICENSE +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/setup.cfg +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapi_observer.egg-info/SOURCES.txt +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapi_observer.egg-info/dependency_links.txt +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapi_observer.egg-info/top_level.txt +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/__init__.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/audit/__init__.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/audit/formatter.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/audit/providers.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/audit/verify.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/config.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/control_plane.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/db_tracing.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/integrations/__init__.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/integrations/strawberry.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/logging/__init__.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/logging/circuit_breaker.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/logging/setup.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/logging/state.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/loguru.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/metrics/__init__.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/metrics/builder.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/metrics/contracts.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/metrics/endpoint.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/metrics/noop.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/metrics/pathing.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/metrics/prometheus/__init__.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/metrics/prometheus/backend.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/metrics/prometheus/client.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/metrics/prometheus/collector.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/metrics/prometheus/exemplars.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/metrics/prometheus/multiprocess.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/metrics/registry.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/middleware/__init__.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/middleware/body_capture.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/middleware/context.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/middleware/events.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/middleware/headers.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/middleware/ip.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/middleware/span_errors.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/otel/__init__.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/otel/exclusions.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/otel/lifecycle.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/otel/logs.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/otel/metrics.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/otel/resource.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/otel/settings.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/otel/types.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/profiles.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/propagation.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/py.typed +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/request_context.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/security/__init__.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/security/policy.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/sinks/__init__.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/sinks/builtin.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/sinks/discovery.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/sinks/factory.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/sinks/logtail/__init__.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/sinks/logtail/dlq.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/sinks/logtail/handler.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/sinks/logtail/sink.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/sinks/protocol.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/sinks/registry.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/sinks/stats.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/utils.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_audit_chain.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_body_media_allowlist.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_config_settings.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_db_tracing.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_dx_parity.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_excluded_url_precedence.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_fork_queue_listener.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_graphql_integrations.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_lifespan_teardown.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_log_queue_controls.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_logtail_dlq.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_loguru_bridge.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_metrics_contracts.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_metrics_optional_dependency.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_middleware.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_negotiate_content_type.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_otel_integration.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_otel_lifecycle.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_otel_logs_from_env.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_otel_logs_pipeline.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_otel_metrics_from_env.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_otlp_export_integration.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_plugin_isolation.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_profiles.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_propagation.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_redaction_presets.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_route_template_cardinality.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_runtime_control_auth.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_security_audit_fixes.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_security_redaction.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_sink_circuit_breaker.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_sinks.py +0 -0
- {fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/tests/test_utils_refactors.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-observer
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.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
|
|
7
7
|
Project-URL: Homepage, https://github.com/Vitaee/FastapiObserver
|
|
8
|
-
Project-URL: Documentation, https://github.
|
|
8
|
+
Project-URL: Documentation, https://vitaee.github.io/FastapiObserver/
|
|
9
9
|
Project-URL: Repository, https://github.com/Vitaee/FastapiObserver.git
|
|
10
10
|
Project-URL: Issues, https://github.com/Vitaee/FastapiObserver/issues
|
|
11
11
|
Project-URL: Funding, https://buymeacoffee.com/FYbPCSu
|
|
@@ -14,7 +14,6 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -24,7 +23,7 @@ Classifier: Framework :: FastAPI
|
|
|
24
23
|
Classifier: Topic :: System :: Logging
|
|
25
24
|
Classifier: Topic :: System :: Monitoring
|
|
26
25
|
Classifier: Typing :: Typed
|
|
27
|
-
Requires-Python:
|
|
26
|
+
Requires-Python: >=3.11
|
|
28
27
|
Description-Content-Type: text/markdown
|
|
29
28
|
License-File: LICENSE
|
|
30
29
|
Requires-Dist: fastapi>=0.133.0
|
|
@@ -64,8 +63,7 @@ Requires-Dist: opentelemetry-instrumentation-requests>=0.66b1; extra == "all"
|
|
|
64
63
|
Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.66b1; extra == "all"
|
|
65
64
|
Requires-Dist: cryptography>=50.0.0; extra == "all"
|
|
66
65
|
Provides-Extra: dev
|
|
67
|
-
Requires-Dist:
|
|
68
|
-
Requires-Dist: httpx2>=2.16.0; python_version >= "3.11" and extra == "dev"
|
|
66
|
+
Requires-Dist: httpx2>=2.16.0; extra == "dev"
|
|
69
67
|
Requires-Dist: loguru>=0.7.3; extra == "dev"
|
|
70
68
|
Requires-Dist: mypy>=2.4.1; extra == "dev"
|
|
71
69
|
Requires-Dist: pip-audit>=2.10.1; extra == "dev"
|
|
@@ -85,7 +83,9 @@ Dynamic: license-file
|
|
|
85
83
|
|
|
86
84
|
`fastapi-observer` gives you structured JSON logs, request correlation, Prometheus metrics, OpenTelemetry tracing, security redaction presets, and runtime controls in one install step and one function call.
|
|
87
85
|
|
|
88
|
-
**
|
|
86
|
+
**Documentation:** [vitaee.github.io/FastapiObserver](https://vitaee.github.io/FastapiObserver/)
|
|
87
|
+
|
|
88
|
+
**Supported Python versions:** `3.11` and newer (CI-tested on `3.11` to `3.15`)
|
|
89
89
|
|
|
90
90
|
---
|
|
91
91
|
|
|
@@ -94,7 +94,7 @@ Dynamic: license-file
|
|
|
94
94
|
|
|
95
95
|
| Component | Supported / Tested |
|
|
96
96
|
|---|---|
|
|
97
|
-
| Python | `3.
|
|
97
|
+
| Python | `>=3.11` (CI matrix: `3.11` to `3.15`) |
|
|
98
98
|
| FastAPI | `>=0.133.0` |
|
|
99
99
|
| Starlette | `>=1.3.1` |
|
|
100
100
|
| pydantic-settings | `>=2.10.1` |
|
|
@@ -233,17 +233,17 @@ To dramatically reduce boilerplate, you can use the `OBS_PROFILE` environment va
|
|
|
233
233
|
|
|
234
234
|
## Documentation Map
|
|
235
235
|
|
|
236
|
-
|
|
237
|
-
- [Security & Presets](
|
|
238
|
-
- [Environment Variables](
|
|
239
|
-
- [Runtime Control](
|
|
240
|
-
- [OpenTelemetry](
|
|
241
|
-
- [Logtail Sink](
|
|
242
|
-
- [Audit Logging](
|
|
243
|
-
- [Database Tracing](
|
|
244
|
-
- [GraphQL Support](
|
|
245
|
-
- [Architecture & Operations](
|
|
246
|
-
- [Production Deployment](
|
|
247
|
-
- [Performance Tuning](
|
|
248
|
-
- [Advanced Operations](
|
|
249
|
-
- [Maintenance & Contributing](
|
|
236
|
+
Full documentation lives at [vitaee.github.io/FastapiObserver](https://vitaee.github.io/FastapiObserver/). Deep dives:
|
|
237
|
+
- [Security & Presets](https://vitaee.github.io/FastapiObserver/security/)
|
|
238
|
+
- [Environment Variables](https://vitaee.github.io/FastapiObserver/configuration/)
|
|
239
|
+
- [Runtime Control](https://vitaee.github.io/FastapiObserver/runtime-control/)
|
|
240
|
+
- [OpenTelemetry](https://vitaee.github.io/FastapiObserver/opentelemetry/)
|
|
241
|
+
- [Logtail Sink](https://vitaee.github.io/FastapiObserver/logtail-sink/)
|
|
242
|
+
- [Audit Logging](https://vitaee.github.io/FastapiObserver/audit-logging/)
|
|
243
|
+
- [Database Tracing](https://vitaee.github.io/FastapiObserver/db-tracing/)
|
|
244
|
+
- [GraphQL Support](https://vitaee.github.io/FastapiObserver/graphql/)
|
|
245
|
+
- [Architecture & Operations](https://vitaee.github.io/FastapiObserver/architecture/)
|
|
246
|
+
- [Production Deployment](https://vitaee.github.io/FastapiObserver/deployment/)
|
|
247
|
+
- [Performance Tuning](https://vitaee.github.io/FastapiObserver/tuning/)
|
|
248
|
+
- [Advanced Operations](https://vitaee.github.io/FastapiObserver/advanced/)
|
|
249
|
+
- [Maintenance & Contributing](https://vitaee.github.io/FastapiObserver/contributing/) (quality gates, release flow, runtime stress harness)
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
`fastapi-observer` gives you structured JSON logs, request correlation, Prometheus metrics, OpenTelemetry tracing, security redaction presets, and runtime controls in one install step and one function call.
|
|
9
9
|
|
|
10
|
-
**
|
|
10
|
+
**Documentation:** [vitaee.github.io/FastapiObserver](https://vitaee.github.io/FastapiObserver/)
|
|
11
|
+
|
|
12
|
+
**Supported Python versions:** `3.11` and newer (CI-tested on `3.11` to `3.15`)
|
|
11
13
|
|
|
12
14
|
---
|
|
13
15
|
|
|
@@ -16,7 +18,7 @@
|
|
|
16
18
|
|
|
17
19
|
| Component | Supported / Tested |
|
|
18
20
|
|---|---|
|
|
19
|
-
| Python | `3.
|
|
21
|
+
| Python | `>=3.11` (CI matrix: `3.11` to `3.15`) |
|
|
20
22
|
| FastAPI | `>=0.133.0` |
|
|
21
23
|
| Starlette | `>=1.3.1` |
|
|
22
24
|
| pydantic-settings | `>=2.10.1` |
|
|
@@ -155,17 +157,17 @@ To dramatically reduce boilerplate, you can use the `OBS_PROFILE` environment va
|
|
|
155
157
|
|
|
156
158
|
## Documentation Map
|
|
157
159
|
|
|
158
|
-
|
|
159
|
-
- [Security & Presets](
|
|
160
|
-
- [Environment Variables](
|
|
161
|
-
- [Runtime Control](
|
|
162
|
-
- [OpenTelemetry](
|
|
163
|
-
- [Logtail Sink](
|
|
164
|
-
- [Audit Logging](
|
|
165
|
-
- [Database Tracing](
|
|
166
|
-
- [GraphQL Support](
|
|
167
|
-
- [Architecture & Operations](
|
|
168
|
-
- [Production Deployment](
|
|
169
|
-
- [Performance Tuning](
|
|
170
|
-
- [Advanced Operations](
|
|
171
|
-
- [Maintenance & Contributing](
|
|
160
|
+
Full documentation lives at [vitaee.github.io/FastapiObserver](https://vitaee.github.io/FastapiObserver/). Deep dives:
|
|
161
|
+
- [Security & Presets](https://vitaee.github.io/FastapiObserver/security/)
|
|
162
|
+
- [Environment Variables](https://vitaee.github.io/FastapiObserver/configuration/)
|
|
163
|
+
- [Runtime Control](https://vitaee.github.io/FastapiObserver/runtime-control/)
|
|
164
|
+
- [OpenTelemetry](https://vitaee.github.io/FastapiObserver/opentelemetry/)
|
|
165
|
+
- [Logtail Sink](https://vitaee.github.io/FastapiObserver/logtail-sink/)
|
|
166
|
+
- [Audit Logging](https://vitaee.github.io/FastapiObserver/audit-logging/)
|
|
167
|
+
- [Database Tracing](https://vitaee.github.io/FastapiObserver/db-tracing/)
|
|
168
|
+
- [GraphQL Support](https://vitaee.github.io/FastapiObserver/graphql/)
|
|
169
|
+
- [Architecture & Operations](https://vitaee.github.io/FastapiObserver/architecture/)
|
|
170
|
+
- [Production Deployment](https://vitaee.github.io/FastapiObserver/deployment/)
|
|
171
|
+
- [Performance Tuning](https://vitaee.github.io/FastapiObserver/tuning/)
|
|
172
|
+
- [Advanced Operations](https://vitaee.github.io/FastapiObserver/advanced/)
|
|
173
|
+
- [Maintenance & Contributing](https://vitaee.github.io/FastapiObserver/contributing/) (quality gates, release flow, runtime stress harness)
|
|
@@ -7,7 +7,7 @@ name = "fastapi-observer"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "Zero-glue FastAPI observability with security presets and runtime controls"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "Vitaee", email = "opensource@vitaee.dev" },
|
|
13
13
|
]
|
|
@@ -16,7 +16,6 @@ classifiers = [
|
|
|
16
16
|
"Intended Audience :: Developers",
|
|
17
17
|
"Operating System :: OS Independent",
|
|
18
18
|
"Programming Language :: Python :: 3",
|
|
19
|
-
"Programming Language :: Python :: 3.10",
|
|
20
19
|
"Programming Language :: Python :: 3.11",
|
|
21
20
|
"Programming Language :: Python :: 3.12",
|
|
22
21
|
"Programming Language :: Python :: 3.13",
|
|
@@ -38,7 +37,7 @@ dependencies = [
|
|
|
38
37
|
|
|
39
38
|
[project.urls]
|
|
40
39
|
Homepage = "https://github.com/Vitaee/FastapiObserver"
|
|
41
|
-
Documentation = "https://github.
|
|
40
|
+
Documentation = "https://vitaee.github.io/FastapiObserver/"
|
|
42
41
|
Repository = "https://github.com/Vitaee/FastapiObserver.git"
|
|
43
42
|
Issues = "https://github.com/Vitaee/FastapiObserver/issues"
|
|
44
43
|
Funding = "https://buymeacoffee.com/FYbPCSu"
|
|
@@ -87,8 +86,7 @@ all = [
|
|
|
87
86
|
"cryptography>=50.0.0",
|
|
88
87
|
]
|
|
89
88
|
dev = [
|
|
90
|
-
"
|
|
91
|
-
"httpx2>=2.16.0; python_version >= '3.11'",
|
|
89
|
+
"httpx2>=2.16.0",
|
|
92
90
|
"loguru>=0.7.3",
|
|
93
91
|
"mypy>=2.4.1",
|
|
94
92
|
"pip-audit>=2.10.1",
|
|
@@ -109,7 +107,7 @@ version = { attr = "fastapiobserver._version.__version__" }
|
|
|
109
107
|
where = ["src"]
|
|
110
108
|
|
|
111
109
|
[tool.ruff]
|
|
112
|
-
target-version = "
|
|
110
|
+
target-version = "py311"
|
|
113
111
|
line-length = 100
|
|
114
112
|
src = ["src", "tests"]
|
|
115
113
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-observer
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.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
|
|
7
7
|
Project-URL: Homepage, https://github.com/Vitaee/FastapiObserver
|
|
8
|
-
Project-URL: Documentation, https://github.
|
|
8
|
+
Project-URL: Documentation, https://vitaee.github.io/FastapiObserver/
|
|
9
9
|
Project-URL: Repository, https://github.com/Vitaee/FastapiObserver.git
|
|
10
10
|
Project-URL: Issues, https://github.com/Vitaee/FastapiObserver/issues
|
|
11
11
|
Project-URL: Funding, https://buymeacoffee.com/FYbPCSu
|
|
@@ -14,7 +14,6 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -24,7 +23,7 @@ Classifier: Framework :: FastAPI
|
|
|
24
23
|
Classifier: Topic :: System :: Logging
|
|
25
24
|
Classifier: Topic :: System :: Monitoring
|
|
26
25
|
Classifier: Typing :: Typed
|
|
27
|
-
Requires-Python:
|
|
26
|
+
Requires-Python: >=3.11
|
|
28
27
|
Description-Content-Type: text/markdown
|
|
29
28
|
License-File: LICENSE
|
|
30
29
|
Requires-Dist: fastapi>=0.133.0
|
|
@@ -64,8 +63,7 @@ Requires-Dist: opentelemetry-instrumentation-requests>=0.66b1; extra == "all"
|
|
|
64
63
|
Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.66b1; extra == "all"
|
|
65
64
|
Requires-Dist: cryptography>=50.0.0; extra == "all"
|
|
66
65
|
Provides-Extra: dev
|
|
67
|
-
Requires-Dist:
|
|
68
|
-
Requires-Dist: httpx2>=2.16.0; python_version >= "3.11" and extra == "dev"
|
|
66
|
+
Requires-Dist: httpx2>=2.16.0; extra == "dev"
|
|
69
67
|
Requires-Dist: loguru>=0.7.3; extra == "dev"
|
|
70
68
|
Requires-Dist: mypy>=2.4.1; extra == "dev"
|
|
71
69
|
Requires-Dist: pip-audit>=2.10.1; extra == "dev"
|
|
@@ -85,7 +83,9 @@ Dynamic: license-file
|
|
|
85
83
|
|
|
86
84
|
`fastapi-observer` gives you structured JSON logs, request correlation, Prometheus metrics, OpenTelemetry tracing, security redaction presets, and runtime controls in one install step and one function call.
|
|
87
85
|
|
|
88
|
-
**
|
|
86
|
+
**Documentation:** [vitaee.github.io/FastapiObserver](https://vitaee.github.io/FastapiObserver/)
|
|
87
|
+
|
|
88
|
+
**Supported Python versions:** `3.11` and newer (CI-tested on `3.11` to `3.15`)
|
|
89
89
|
|
|
90
90
|
---
|
|
91
91
|
|
|
@@ -94,7 +94,7 @@ Dynamic: license-file
|
|
|
94
94
|
|
|
95
95
|
| Component | Supported / Tested |
|
|
96
96
|
|---|---|
|
|
97
|
-
| Python | `3.
|
|
97
|
+
| Python | `>=3.11` (CI matrix: `3.11` to `3.15`) |
|
|
98
98
|
| FastAPI | `>=0.133.0` |
|
|
99
99
|
| Starlette | `>=1.3.1` |
|
|
100
100
|
| pydantic-settings | `>=2.10.1` |
|
|
@@ -233,17 +233,17 @@ To dramatically reduce boilerplate, you can use the `OBS_PROFILE` environment va
|
|
|
233
233
|
|
|
234
234
|
## Documentation Map
|
|
235
235
|
|
|
236
|
-
|
|
237
|
-
- [Security & Presets](
|
|
238
|
-
- [Environment Variables](
|
|
239
|
-
- [Runtime Control](
|
|
240
|
-
- [OpenTelemetry](
|
|
241
|
-
- [Logtail Sink](
|
|
242
|
-
- [Audit Logging](
|
|
243
|
-
- [Database Tracing](
|
|
244
|
-
- [GraphQL Support](
|
|
245
|
-
- [Architecture & Operations](
|
|
246
|
-
- [Production Deployment](
|
|
247
|
-
- [Performance Tuning](
|
|
248
|
-
- [Advanced Operations](
|
|
249
|
-
- [Maintenance & Contributing](
|
|
236
|
+
Full documentation lives at [vitaee.github.io/FastapiObserver](https://vitaee.github.io/FastapiObserver/). Deep dives:
|
|
237
|
+
- [Security & Presets](https://vitaee.github.io/FastapiObserver/security/)
|
|
238
|
+
- [Environment Variables](https://vitaee.github.io/FastapiObserver/configuration/)
|
|
239
|
+
- [Runtime Control](https://vitaee.github.io/FastapiObserver/runtime-control/)
|
|
240
|
+
- [OpenTelemetry](https://vitaee.github.io/FastapiObserver/opentelemetry/)
|
|
241
|
+
- [Logtail Sink](https://vitaee.github.io/FastapiObserver/logtail-sink/)
|
|
242
|
+
- [Audit Logging](https://vitaee.github.io/FastapiObserver/audit-logging/)
|
|
243
|
+
- [Database Tracing](https://vitaee.github.io/FastapiObserver/db-tracing/)
|
|
244
|
+
- [GraphQL Support](https://vitaee.github.io/FastapiObserver/graphql/)
|
|
245
|
+
- [Architecture & Operations](https://vitaee.github.io/FastapiObserver/architecture/)
|
|
246
|
+
- [Production Deployment](https://vitaee.github.io/FastapiObserver/deployment/)
|
|
247
|
+
- [Performance Tuning](https://vitaee.github.io/FastapiObserver/tuning/)
|
|
248
|
+
- [Advanced Operations](https://vitaee.github.io/FastapiObserver/advanced/)
|
|
249
|
+
- [Maintenance & Contributing](https://vitaee.github.io/FastapiObserver/contributing/) (quality gates, release flow, runtime stress harness)
|
{fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapi_observer.egg-info/requires.txt
RENAMED
|
@@ -20,6 +20,7 @@ cryptography>=50.0.0
|
|
|
20
20
|
cryptography>=50.0.0
|
|
21
21
|
|
|
22
22
|
[dev]
|
|
23
|
+
httpx2>=2.16.0
|
|
23
24
|
loguru>=0.7.3
|
|
24
25
|
mypy>=2.4.1
|
|
25
26
|
pip-audit>=2.10.1
|
|
@@ -29,12 +30,6 @@ ruff>=0.17.3
|
|
|
29
30
|
cyclonedx-bom>=7.6.0
|
|
30
31
|
uvicorn>=0.54.0
|
|
31
32
|
|
|
32
|
-
[dev:python_version < "3.11"]
|
|
33
|
-
httpx>=0.28.1
|
|
34
|
-
|
|
35
|
-
[dev:python_version >= "3.11"]
|
|
36
|
-
httpx2>=2.16.0
|
|
37
|
-
|
|
38
33
|
[fast-json]
|
|
39
34
|
orjson>=3.11.7
|
|
40
35
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.5.0"
|
|
@@ -126,6 +126,13 @@ def install_observability(
|
|
|
126
126
|
metrics_backend,
|
|
127
127
|
)
|
|
128
128
|
|
|
129
|
+
# --- 7. Uvicorn access log ---
|
|
130
|
+
# The middleware's `request.completed` line carries the same fields, so drop
|
|
131
|
+
# uvicorn's duplicate line the same way `uvicorn --no-access-log` does.
|
|
132
|
+
uvicorn_access_logger = logging.getLogger("uvicorn.access")
|
|
133
|
+
uvicorn_access_logger.handlers = []
|
|
134
|
+
uvicorn_access_logger.propagate = False
|
|
135
|
+
|
|
129
136
|
# --- 8. Runtime control plane ---
|
|
130
137
|
if runtime_control_settings and runtime_control_settings.enabled:
|
|
131
138
|
mount_control_plane(app, runtime_control_settings)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from ..plugins import apply_log_filters
|
|
7
|
+
from ..request_context import get_request_id, get_span_id, get_trace_id, get_user_context
|
|
8
|
+
|
|
9
|
+
_LOGGER = logging.getLogger("fastapiobserver.logging")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RequestIdFilter(logging.Filter):
|
|
13
|
+
"""Attach request ID and user context from context vars to every log record.
|
|
14
|
+
|
|
15
|
+
Records are formatted on the queue listener thread, where the request's
|
|
16
|
+
context vars are not visible, so they are captured here.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
20
|
+
if not getattr(record, "request_id", None):
|
|
21
|
+
record.request_id = get_request_id()
|
|
22
|
+
if not getattr(record, "user_context", None):
|
|
23
|
+
record.user_context = get_user_context()
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TraceContextFilter(logging.Filter):
|
|
28
|
+
"""Attach OTel trace_id / span_id to every log record."""
|
|
29
|
+
|
|
30
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
31
|
+
trace_id = get_trace_id()
|
|
32
|
+
span_id = get_span_id()
|
|
33
|
+
|
|
34
|
+
# No span can be active unless something already imported the OTel API.
|
|
35
|
+
# Looking it up avoids a failing import (a sys.path scan) on every record.
|
|
36
|
+
otel_trace = sys.modules.get("opentelemetry.trace")
|
|
37
|
+
if otel_trace is not None:
|
|
38
|
+
try:
|
|
39
|
+
span_context = otel_trace.get_current_span().get_span_context()
|
|
40
|
+
if span_context and span_context.is_valid:
|
|
41
|
+
trace_id = f"{span_context.trace_id:032x}"
|
|
42
|
+
span_id = f"{span_context.span_id:016x}"
|
|
43
|
+
except Exception:
|
|
44
|
+
_LOGGER.debug(
|
|
45
|
+
"trace_context_filter.otel_lookup_failed",
|
|
46
|
+
exc_info=True,
|
|
47
|
+
extra={"_skip_enrichers": True},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if trace_id:
|
|
51
|
+
record.trace_id = trace_id # type: ignore[attr-defined]
|
|
52
|
+
if span_id:
|
|
53
|
+
record.span_id = span_id # type: ignore[attr-defined]
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class PluginLogFilter(logging.Filter):
|
|
58
|
+
"""Apply user-registered log filters with fault isolation."""
|
|
59
|
+
|
|
60
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
61
|
+
return apply_log_filters(record)
|
|
@@ -52,7 +52,7 @@ class StructuredJsonFormatter(logging.Formatter):
|
|
|
52
52
|
|
|
53
53
|
def format(self, record: logging.LogRecord) -> str:
|
|
54
54
|
payload: dict[str, Any] = {
|
|
55
|
-
"timestamp": datetime.
|
|
55
|
+
"timestamp": datetime.fromtimestamp(record.created, timezone.utc).isoformat(),
|
|
56
56
|
"level": record.levelname,
|
|
57
57
|
"logger": record.name,
|
|
58
58
|
"message": record.getMessage(),
|
|
@@ -77,7 +77,7 @@ class StructuredJsonFormatter(logging.Formatter):
|
|
|
77
77
|
if span_id:
|
|
78
78
|
payload["span_id"] = span_id
|
|
79
79
|
|
|
80
|
-
user_context = get_user_context()
|
|
80
|
+
user_context = getattr(record, "user_context", None) or get_user_context()
|
|
81
81
|
if user_context:
|
|
82
82
|
payload["user_context"] = user_context
|
|
83
83
|
|
|
@@ -96,17 +96,22 @@ class StructuredJsonFormatter(logging.Formatter):
|
|
|
96
96
|
else:
|
|
97
97
|
candidate_payload = self._enrich_event(dict(payload))
|
|
98
98
|
if isinstance(candidate_payload, Mapping):
|
|
99
|
-
enriched_payload =
|
|
99
|
+
enriched_payload = _as_dict(candidate_payload)
|
|
100
100
|
else:
|
|
101
101
|
enriched_payload = payload
|
|
102
102
|
sanitized_candidate = self._sanitize_payload(enriched_payload, self.security_policy)
|
|
103
103
|
if isinstance(sanitized_candidate, Mapping):
|
|
104
|
-
sanitized_payload =
|
|
104
|
+
sanitized_payload = _as_dict(sanitized_candidate)
|
|
105
105
|
else:
|
|
106
106
|
sanitized_payload = enriched_payload
|
|
107
107
|
return _json_dumps(sanitized_payload)
|
|
108
108
|
|
|
109
109
|
|
|
110
|
+
def _as_dict(value: Mapping[str, Any]) -> dict[str, Any]:
|
|
111
|
+
# Both steps already return fresh dicts; only copy other mapping types.
|
|
112
|
+
return value if type(value) is dict else dict(value)
|
|
113
|
+
|
|
114
|
+
|
|
110
115
|
def _json_dumps(payload: dict[str, Any]) -> str:
|
|
111
116
|
if orjson is not None:
|
|
112
117
|
return orjson.dumps(payload).decode("utf-8")
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import copy
|
|
3
4
|
import logging
|
|
4
5
|
import logging.handlers
|
|
5
6
|
import queue
|
|
@@ -135,6 +136,16 @@ class OverflowPolicyQueueHandler(logging.handlers.QueueHandler):
|
|
|
135
136
|
self.block_timeout_seconds = block_timeout_seconds
|
|
136
137
|
self.telemetry = telemetry
|
|
137
138
|
|
|
139
|
+
def prepare(self, record: logging.LogRecord) -> logging.LogRecord:
|
|
140
|
+
# The stdlib version formats the record here and drops exc_info, which
|
|
141
|
+
# loses the structured `error` field. The queue stays in-process, so keep
|
|
142
|
+
# exc_info and leave formatting to the listener thread.
|
|
143
|
+
record = copy.copy(record)
|
|
144
|
+
record.message = record.getMessage()
|
|
145
|
+
record.msg = record.message
|
|
146
|
+
record.args = None
|
|
147
|
+
return record
|
|
148
|
+
|
|
138
149
|
def enqueue(self, record: logging.LogRecord) -> None:
|
|
139
150
|
try:
|
|
140
151
|
self.queue.put_nowait(record)
|
|
@@ -3,8 +3,8 @@ from __future__ import annotations
|
|
|
3
3
|
import logging
|
|
4
4
|
|
|
5
5
|
from ..config import ObservabilitySettings
|
|
6
|
-
from ..metrics import MetricsBackend, collapse_dynamic_segments
|
|
7
|
-
from ..plugins import emit_metric_hooks
|
|
6
|
+
from ..metrics import MetricsBackend, NoopMetricsBackend, collapse_dynamic_segments
|
|
7
|
+
from ..plugins import _METRIC_HOOKS, emit_metric_hooks
|
|
8
8
|
from starlette.requests import Request
|
|
9
9
|
from starlette.responses import Response
|
|
10
10
|
from starlette.types import Scope
|
|
@@ -30,6 +30,8 @@ class _MetricsRecorder:
|
|
|
30
30
|
duration_seconds: float,
|
|
31
31
|
scope: Scope | None = None,
|
|
32
32
|
) -> None:
|
|
33
|
+
if isinstance(self.metrics_backend, NoopMetricsBackend):
|
|
34
|
+
return
|
|
33
35
|
collapsed_path = collapse_dynamic_segments(path)
|
|
34
36
|
excluded_urls = self.settings.metrics_exclude_paths
|
|
35
37
|
|
|
@@ -67,6 +69,8 @@ class _MetricsRecorder:
|
|
|
67
69
|
status_code: int,
|
|
68
70
|
duration_seconds: float,
|
|
69
71
|
) -> None:
|
|
72
|
+
if not _METRIC_HOOKS:
|
|
73
|
+
return
|
|
70
74
|
request = Request(scope)
|
|
71
75
|
response = Response(status_code=status_code)
|
|
72
76
|
emit_metric_hooks(request, response, duration_seconds)
|
{fastapi_observer-1.4.0 → fastapi_observer-1.5.0}/src/fastapiobserver/middleware/request_logging.py
RENAMED
|
@@ -60,9 +60,8 @@ class RequestLoggingMiddleware:
|
|
|
60
60
|
method = scope.get("method", "UNKNOWN")
|
|
61
61
|
status_code = 500
|
|
62
62
|
captured_error: Exception | None = None
|
|
63
|
-
request_content_type = _get_header(raw_headers, b"content-type")
|
|
64
63
|
request_body_enabled = self.security_policy.log_request_body and is_body_capturable(
|
|
65
|
-
|
|
64
|
+
_get_header(raw_headers, b"content-type"),
|
|
66
65
|
self.security_policy,
|
|
67
66
|
)
|
|
68
67
|
request_body_capture = _BodyCapture(
|
|
@@ -102,7 +101,11 @@ class RequestLoggingMiddleware:
|
|
|
102
101
|
await send(message)
|
|
103
102
|
|
|
104
103
|
try:
|
|
105
|
-
await self.app(
|
|
104
|
+
await self.app(
|
|
105
|
+
scope,
|
|
106
|
+
receive_wrapper if request_body_enabled else receive,
|
|
107
|
+
send_wrapper,
|
|
108
|
+
)
|
|
106
109
|
except Exception as exc:
|
|
107
110
|
had_error = True
|
|
108
111
|
captured_error = exc
|
|
@@ -137,26 +140,35 @@ class RequestLoggingMiddleware:
|
|
|
137
140
|
had_error: bool,
|
|
138
141
|
) -> None:
|
|
139
142
|
duration_seconds = max(0.0, time.perf_counter() - start)
|
|
140
|
-
error_type = _classify_error(status_code, captured_error)
|
|
141
|
-
safe_event = self.event_builder.build(
|
|
142
|
-
method=method,
|
|
143
|
-
path=path,
|
|
144
|
-
status_code=status_code,
|
|
145
|
-
duration_seconds=duration_seconds,
|
|
146
|
-
client_ip=client_ip,
|
|
147
|
-
request_body=request_body_capture.value,
|
|
148
|
-
response_body=response_body_capture.value,
|
|
149
|
-
error_type=error_type,
|
|
150
|
-
exception=captured_error,
|
|
151
|
-
)
|
|
152
143
|
if had_error:
|
|
153
|
-
|
|
144
|
+
level, message = logging.ERROR, "request.failed"
|
|
154
145
|
elif status_code >= 500:
|
|
155
|
-
|
|
146
|
+
level, message = logging.ERROR, "request.server_error"
|
|
156
147
|
elif status_code >= 400:
|
|
157
|
-
|
|
148
|
+
level, message = logging.WARNING, "request.client_error"
|
|
158
149
|
else:
|
|
159
|
-
|
|
150
|
+
level, message = logging.INFO, "request.completed"
|
|
151
|
+
|
|
152
|
+
# Building and redacting the event is the costliest step; skip it when
|
|
153
|
+
# the record would be dropped by the log level anyway.
|
|
154
|
+
if self.logger.isEnabledFor(level):
|
|
155
|
+
safe_event = self.event_builder.build(
|
|
156
|
+
method=method,
|
|
157
|
+
path=path,
|
|
158
|
+
status_code=status_code,
|
|
159
|
+
duration_seconds=duration_seconds,
|
|
160
|
+
client_ip=client_ip,
|
|
161
|
+
request_body=request_body_capture.value,
|
|
162
|
+
response_body=response_body_capture.value,
|
|
163
|
+
error_type=_classify_error(status_code, captured_error),
|
|
164
|
+
exception=captured_error,
|
|
165
|
+
)
|
|
166
|
+
self.logger.log(
|
|
167
|
+
level,
|
|
168
|
+
message,
|
|
169
|
+
exc_info=captured_error if had_error else None,
|
|
170
|
+
extra={"event": safe_event},
|
|
171
|
+
)
|
|
160
172
|
|
|
161
173
|
# Prefer route template for bounded cardinality
|
|
162
174
|
route_template = _extract_route_template(scope, path)
|
|
@@ -40,9 +40,6 @@ def install_otel(
|
|
|
40
40
|
fastapi_instrumentor_module = import_otel_module(
|
|
41
41
|
"opentelemetry.instrumentation.fastapi"
|
|
42
42
|
)
|
|
43
|
-
logging_instrumentor_module = import_otel_module(
|
|
44
|
-
"opentelemetry.instrumentation.logging"
|
|
45
|
-
)
|
|
46
43
|
|
|
47
44
|
set_trace_sampling_ratio(otel_settings.trace_sampling_ratio)
|
|
48
45
|
current_provider = trace_api.get_tracer_provider()
|
|
@@ -122,17 +119,6 @@ def install_otel(
|
|
|
122
119
|
app, **instrument_kwargs
|
|
123
120
|
)
|
|
124
121
|
|
|
125
|
-
try:
|
|
126
|
-
logging_instrumentor_module.LoggingInstrumentor().instrument(
|
|
127
|
-
set_logging_format=False
|
|
128
|
-
)
|
|
129
|
-
except Exception:
|
|
130
|
-
_LOGGER.debug(
|
|
131
|
-
"otel.logging_instrumentor.failed",
|
|
132
|
-
exc_info=True,
|
|
133
|
-
extra={"_skip_enrichers": True},
|
|
134
|
-
)
|
|
135
|
-
|
|
136
122
|
if provider_owned:
|
|
137
123
|
register_shutdown_hook(
|
|
138
124
|
key=f"otel.tracer_provider.{id(tracer_provider)}",
|
|
@@ -74,6 +74,8 @@ def clear_plugins() -> None:
|
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
def apply_log_enrichers(event: dict[str, object]) -> dict[str, object]:
|
|
77
|
+
if not _LOG_ENRICHERS:
|
|
78
|
+
return event
|
|
77
79
|
with _LOCK:
|
|
78
80
|
enrichers = list(_LOG_ENRICHERS.items())
|
|
79
81
|
|
|
@@ -92,7 +94,7 @@ def apply_log_enrichers(event: dict[str, object]) -> dict[str, object]:
|
|
|
92
94
|
|
|
93
95
|
|
|
94
96
|
def apply_log_filters(record: logging.LogRecord) -> bool:
|
|
95
|
-
if getattr(record, "_skip_log_filters", False):
|
|
97
|
+
if not _LOG_FILTERS or getattr(record, "_skip_log_filters", False):
|
|
96
98
|
return True
|
|
97
99
|
|
|
98
100
|
with _LOCK:
|
|
@@ -35,15 +35,20 @@ def resolve_client_ip(
|
|
|
35
35
|
if not is_trusted_client_ip(client_ip, policy):
|
|
36
36
|
return client_ip
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
# Each proxy appends the address it received the request from, so only the
|
|
39
|
+
# right-hand entries can be trusted; the left ones are whatever the client sent.
|
|
40
|
+
# Walk right to left and return the first hop that is not a trusted proxy.
|
|
41
|
+
# Repeated header lines form one list.
|
|
42
|
+
hops = [
|
|
43
|
+
hop.strip()
|
|
44
|
+
for k, v in headers
|
|
45
|
+
if k.lower() == b"x-forwarded-for"
|
|
46
|
+
for hop in v.decode("latin1").split(",")
|
|
47
|
+
if hop.strip()
|
|
48
|
+
]
|
|
49
|
+
for hop in reversed(hops):
|
|
50
|
+
if not is_trusted_client_ip(hop, policy):
|
|
51
|
+
return hop
|
|
52
|
+
return hops[0] if hops else client_ip
|
|
48
53
|
|
|
49
54
|
__all__ = ["is_trusted_client_ip", "resolve_client_ip"]
|