fastapi-observer 0.4.0__tar.gz → 1.0.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-0.4.0/src/fastapi_observer.egg-info → fastapi_observer-1.0.0}/PKG-INFO +17 -7
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/README.md +15 -5
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/pyproject.toml +1 -1
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0/src/fastapi_observer.egg-info}/PKG-INFO +17 -7
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapi_observer.egg-info/SOURCES.txt +2 -0
- fastapi_observer-1.0.0/src/fastapiobserver/_version.py +1 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/logging/formatter.py +2 -2
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/middleware/events.py +11 -2
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/middleware/ip.py +17 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/security/policy.py +1 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/security/redaction.py +4 -0
- fastapi_observer-1.0.0/tests/test_dx_parity.py +79 -0
- fastapi_observer-1.0.0/tests/test_security_audit_fixes.py +161 -0
- fastapi_observer-0.4.0/src/fastapiobserver/_version.py +0 -1
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/LICENSE +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/setup.cfg +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapi_observer.egg-info/dependency_links.txt +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapi_observer.egg-info/requires.txt +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapi_observer.egg-info/top_level.txt +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/__init__.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/config.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/control_plane.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/fastapi.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/integrations/__init__.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/integrations/strawberry.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/logging/__init__.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/logging/circuit_breaker.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/logging/filters.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/logging/queueing.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/logging/setup.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/logging/state.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/loguru.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/__init__.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/builder.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/contracts.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/endpoint.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/noop.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/pathing.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/prometheus/__init__.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/prometheus/backend.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/prometheus/client.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/prometheus/collector.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/prometheus/exemplars.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/prometheus/multiprocess.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/registry.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/middleware/__init__.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/middleware/body_capture.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/middleware/context.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/middleware/headers.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/middleware/metrics.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/middleware/request_logging.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/middleware/span_errors.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/otel/__init__.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/otel/lifecycle.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/otel/logs.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/otel/metrics.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/otel/resource.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/otel/settings.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/otel/tracing.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/otel/types.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/plugins.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/propagation.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/py.typed +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/request_context.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/security/__init__.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/security/normalize.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/security/proxies.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/sinks/__init__.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/sinks/builtin.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/sinks/discovery.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/sinks/factory.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/sinks/logtail/__init__.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/sinks/logtail/dlq.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/sinks/logtail/handler.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/sinks/logtail/sink.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/sinks/protocol.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/sinks/registry.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/sinks/stats.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/utils.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_body_media_allowlist.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_config_settings.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_excluded_url_precedence.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_fastapi_install.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_graphql_integrations.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_log_queue_controls.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_logging_schema_contract.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_logtail_dlq.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_loguru_bridge.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_metrics_contracts.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_metrics_optional_dependency.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_middleware.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_negotiate_content_type.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_otel_integration.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_otel_lifecycle.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_otel_log_correlation.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_otel_logs_from_env.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_otel_logs_pipeline.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_otel_metrics_from_env.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_otlp_export_integration.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_plugin_isolation.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_propagation.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_redaction_presets.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_route_template_cardinality.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_runtime_control_auth.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_security_redaction.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_sink_circuit_breaker.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_sinks.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_trust_boundary.py +0 -0
- {fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/tests/test_utils_refactors.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-observer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.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
|
|
@@ -10,7 +10,7 @@ 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
|
|
12
12
|
Keywords: fastapi,observability,logging,metrics,opentelemetry
|
|
13
|
-
Classifier: Development Status ::
|
|
13
|
+
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
|
|
@@ -704,6 +704,9 @@ The library supports configuration from code and env vars. Below are the most re
|
|
|
704
704
|
| `METRICS_EXEMPLARS_ENABLED` | `false` | Enable exemplars where supported |
|
|
705
705
|
| `METRICS_FORMAT` | `negotiate` | `prometheus`, `openmetrics`, or `negotiate` |
|
|
706
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
|
+
|
|
707
710
|
### Security and trust boundary
|
|
708
711
|
|
|
709
712
|
| Variable | Default | Description |
|
|
@@ -1041,8 +1044,8 @@ Repository integration tests include:
|
|
|
1041
1044
|
|
|
1042
1045
|
Reproducible benchmark harness and methodology:
|
|
1043
1046
|
- Guide: [`benchmarks.md`](benchmarks.md)
|
|
1044
|
-
- Apps: `examples/benchmarks/
|
|
1045
|
-
- Runner: `examples/benchmarks/
|
|
1047
|
+
- Apps: `examples/benchmarks/app.py`
|
|
1048
|
+
- Runner: `examples/benchmarks/harness.py`
|
|
1046
1049
|
|
|
1047
1050
|
---
|
|
1048
1051
|
|
|
@@ -1050,10 +1053,11 @@ Reproducible benchmark harness and methodology:
|
|
|
1050
1053
|
|
|
1051
1054
|
- `0.1.x`: secure-by-default core
|
|
1052
1055
|
- `0.2.x`: OTel interoperability, security presets, allowlists
|
|
1053
|
-
- `0.3.x`:
|
|
1054
|
-
- `
|
|
1056
|
+
- `0.3.x`: GraphQL observability, error fingerprinting, and Logtail DLQ durability
|
|
1057
|
+
- `0.4.x`: package modularization, sink/registry hardening, and runtime control token rotation
|
|
1058
|
+
- `1.0.0`: first stable release contract for production deployments
|
|
1055
1059
|
|
|
1056
|
-
Current release version: `0.
|
|
1060
|
+
Current release version: `1.0.0`
|
|
1057
1061
|
|
|
1058
1062
|
## Changelog Policy
|
|
1059
1063
|
|
|
@@ -1063,6 +1067,12 @@ Breaking changes must be listed under a `Breaking Changes` section in `CHANGELOG
|
|
|
1063
1067
|
|
|
1064
1068
|
## Packaging and Publishing (Maintainers)
|
|
1065
1069
|
|
|
1070
|
+
Recommended release command (uses `.env` with `PYPI_TOKEN`):
|
|
1071
|
+
|
|
1072
|
+
```bash
|
|
1073
|
+
scripts/deploy_pypi.sh --tag v1.0.0 --push-tag
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1066
1076
|
### 1) Build distributions
|
|
1067
1077
|
|
|
1068
1078
|
```bash
|
|
@@ -634,6 +634,9 @@ The library supports configuration from code and env vars. Below are the most re
|
|
|
634
634
|
| `METRICS_EXEMPLARS_ENABLED` | `false` | Enable exemplars where supported |
|
|
635
635
|
| `METRICS_FORMAT` | `negotiate` | `prometheus`, `openmetrics`, or `negotiate` |
|
|
636
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
|
+
|
|
637
640
|
### Security and trust boundary
|
|
638
641
|
|
|
639
642
|
| Variable | Default | Description |
|
|
@@ -971,8 +974,8 @@ Repository integration tests include:
|
|
|
971
974
|
|
|
972
975
|
Reproducible benchmark harness and methodology:
|
|
973
976
|
- Guide: [`benchmarks.md`](benchmarks.md)
|
|
974
|
-
- Apps: `examples/benchmarks/
|
|
975
|
-
- Runner: `examples/benchmarks/
|
|
977
|
+
- Apps: `examples/benchmarks/app.py`
|
|
978
|
+
- Runner: `examples/benchmarks/harness.py`
|
|
976
979
|
|
|
977
980
|
---
|
|
978
981
|
|
|
@@ -980,10 +983,11 @@ Reproducible benchmark harness and methodology:
|
|
|
980
983
|
|
|
981
984
|
- `0.1.x`: secure-by-default core
|
|
982
985
|
- `0.2.x`: OTel interoperability, security presets, allowlists
|
|
983
|
-
- `0.3.x`:
|
|
984
|
-
- `
|
|
986
|
+
- `0.3.x`: GraphQL observability, error fingerprinting, and Logtail DLQ durability
|
|
987
|
+
- `0.4.x`: package modularization, sink/registry hardening, and runtime control token rotation
|
|
988
|
+
- `1.0.0`: first stable release contract for production deployments
|
|
985
989
|
|
|
986
|
-
Current release version: `0.
|
|
990
|
+
Current release version: `1.0.0`
|
|
987
991
|
|
|
988
992
|
## Changelog Policy
|
|
989
993
|
|
|
@@ -993,6 +997,12 @@ Breaking changes must be listed under a `Breaking Changes` section in `CHANGELOG
|
|
|
993
997
|
|
|
994
998
|
## Packaging and Publishing (Maintainers)
|
|
995
999
|
|
|
1000
|
+
Recommended release command (uses `.env` with `PYPI_TOKEN`):
|
|
1001
|
+
|
|
1002
|
+
```bash
|
|
1003
|
+
scripts/deploy_pypi.sh --tag v1.0.0 --push-tag
|
|
1004
|
+
```
|
|
1005
|
+
|
|
996
1006
|
### 1) Build distributions
|
|
997
1007
|
|
|
998
1008
|
```bash
|
|
@@ -12,7 +12,7 @@ authors = [
|
|
|
12
12
|
{ name = "Vitaee", email = "opensource@vitaee.dev" },
|
|
13
13
|
]
|
|
14
14
|
classifiers = [
|
|
15
|
-
"Development Status ::
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
16
|
"Intended Audience :: Developers",
|
|
17
17
|
"Operating System :: OS Independent",
|
|
18
18
|
"Programming Language :: Python :: 3",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-observer
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.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
|
|
@@ -10,7 +10,7 @@ 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
|
|
12
12
|
Keywords: fastapi,observability,logging,metrics,opentelemetry
|
|
13
|
-
Classifier: Development Status ::
|
|
13
|
+
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
|
|
@@ -704,6 +704,9 @@ The library supports configuration from code and env vars. Below are the most re
|
|
|
704
704
|
| `METRICS_EXEMPLARS_ENABLED` | `false` | Enable exemplars where supported |
|
|
705
705
|
| `METRICS_FORMAT` | `negotiate` | `prometheus`, `openmetrics`, or `negotiate` |
|
|
706
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
|
+
|
|
707
710
|
### Security and trust boundary
|
|
708
711
|
|
|
709
712
|
| Variable | Default | Description |
|
|
@@ -1041,8 +1044,8 @@ Repository integration tests include:
|
|
|
1041
1044
|
|
|
1042
1045
|
Reproducible benchmark harness and methodology:
|
|
1043
1046
|
- Guide: [`benchmarks.md`](benchmarks.md)
|
|
1044
|
-
- Apps: `examples/benchmarks/
|
|
1045
|
-
- Runner: `examples/benchmarks/
|
|
1047
|
+
- Apps: `examples/benchmarks/app.py`
|
|
1048
|
+
- Runner: `examples/benchmarks/harness.py`
|
|
1046
1049
|
|
|
1047
1050
|
---
|
|
1048
1051
|
|
|
@@ -1050,10 +1053,11 @@ Reproducible benchmark harness and methodology:
|
|
|
1050
1053
|
|
|
1051
1054
|
- `0.1.x`: secure-by-default core
|
|
1052
1055
|
- `0.2.x`: OTel interoperability, security presets, allowlists
|
|
1053
|
-
- `0.3.x`:
|
|
1054
|
-
- `
|
|
1056
|
+
- `0.3.x`: GraphQL observability, error fingerprinting, and Logtail DLQ durability
|
|
1057
|
+
- `0.4.x`: package modularization, sink/registry hardening, and runtime control token rotation
|
|
1058
|
+
- `1.0.0`: first stable release contract for production deployments
|
|
1055
1059
|
|
|
1056
|
-
Current release version: `0.
|
|
1060
|
+
Current release version: `1.0.0`
|
|
1057
1061
|
|
|
1058
1062
|
## Changelog Policy
|
|
1059
1063
|
|
|
@@ -1063,6 +1067,12 @@ Breaking changes must be listed under a `Breaking Changes` section in `CHANGELOG
|
|
|
1063
1067
|
|
|
1064
1068
|
## Packaging and Publishing (Maintainers)
|
|
1065
1069
|
|
|
1070
|
+
Recommended release command (uses `.env` with `PYPI_TOKEN`):
|
|
1071
|
+
|
|
1072
|
+
```bash
|
|
1073
|
+
scripts/deploy_pypi.sh --tag v1.0.0 --push-tag
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1066
1076
|
### 1) Build distributions
|
|
1067
1077
|
|
|
1068
1078
|
```bash
|
|
@@ -74,6 +74,7 @@ src/fastapiobserver/sinks/logtail/handler.py
|
|
|
74
74
|
src/fastapiobserver/sinks/logtail/sink.py
|
|
75
75
|
tests/test_body_media_allowlist.py
|
|
76
76
|
tests/test_config_settings.py
|
|
77
|
+
tests/test_dx_parity.py
|
|
77
78
|
tests/test_excluded_url_precedence.py
|
|
78
79
|
tests/test_fastapi_install.py
|
|
79
80
|
tests/test_graphql_integrations.py
|
|
@@ -97,6 +98,7 @@ tests/test_propagation.py
|
|
|
97
98
|
tests/test_redaction_presets.py
|
|
98
99
|
tests/test_route_template_cardinality.py
|
|
99
100
|
tests/test_runtime_control_auth.py
|
|
101
|
+
tests/test_security_audit_fixes.py
|
|
100
102
|
tests/test_security_redaction.py
|
|
101
103
|
tests/test_sink_circuit_breaker.py
|
|
102
104
|
tests/test_sinks.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
@@ -145,7 +145,7 @@ def _build_structured_error(
|
|
|
145
145
|
def _generate_error_fingerprint(error_type: str, stacktrace: str) -> str:
|
|
146
146
|
"""Hash stack trace after stripping out environment-specific noise."""
|
|
147
147
|
if not stacktrace:
|
|
148
|
-
return hashlib.
|
|
148
|
+
return hashlib.sha256(error_type.encode("utf-8")).hexdigest()[:32]
|
|
149
149
|
|
|
150
150
|
# Strip hexadecimal memory addresses
|
|
151
151
|
sanitized = re.sub(r"0x[0-9a-fA-F]+", "0x<ptr>", stacktrace)
|
|
@@ -154,4 +154,4 @@ def _generate_error_fingerprint(error_type: str, stacktrace: str) -> str:
|
|
|
154
154
|
|
|
155
155
|
# Combine error type and sanitized trace to ensure distinct groupings
|
|
156
156
|
payload = f"{error_type}:{sanitized}"
|
|
157
|
-
return hashlib.
|
|
157
|
+
return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:32]
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import re
|
|
3
4
|
from typing import Any
|
|
4
5
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
|
5
6
|
from starlette.types import Scope
|
|
6
7
|
|
|
7
8
|
from ..security import SecurityPolicy, sanitize_event
|
|
8
9
|
|
|
10
|
+
_CREDENTIAL_RE = re.compile(r"://[^:]+:[^@]+@")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _sanitize_exception_message(msg: str, *, max_length: int = 512) -> str:
|
|
14
|
+
"""Strip credential patterns (e.g. ``://user:pass@``) from exception text."""
|
|
15
|
+
return _CREDENTIAL_RE.sub("://***:***@", msg)[:max_length]
|
|
16
|
+
|
|
9
17
|
class _RequestEventBuilder:
|
|
10
18
|
def __init__(self, policy: SecurityPolicy) -> None:
|
|
11
19
|
self.policy = policy
|
|
@@ -41,7 +49,7 @@ class _RequestEventBuilder:
|
|
|
41
49
|
event["response_body"] = response_body
|
|
42
50
|
if exception is not None:
|
|
43
51
|
event["exception_class"] = exception.__class__.__name__
|
|
44
|
-
event["exception_message"] = str(exception)
|
|
52
|
+
event["exception_message"] = _sanitize_exception_message(str(exception))
|
|
45
53
|
return sanitize_event(event, self.policy)
|
|
46
54
|
|
|
47
55
|
|
|
@@ -78,4 +86,5 @@ def _extract_route_template(scope: Scope, raw_path: str) -> str:
|
|
|
78
86
|
return template
|
|
79
87
|
return raw_path
|
|
80
88
|
|
|
81
|
-
__all__ = ["_RequestEventBuilder", "_classify_error", "_extract_route_template"
|
|
89
|
+
__all__ = ["_RequestEventBuilder", "_classify_error", "_extract_route_template",
|
|
90
|
+
"_sanitize_exception_message"]
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
4
|
+
|
|
3
5
|
from starlette.types import Scope
|
|
4
6
|
|
|
5
7
|
from ..security import (
|
|
@@ -8,6 +10,8 @@ from ..security import (
|
|
|
8
10
|
resolve_client_ip,
|
|
9
11
|
)
|
|
10
12
|
|
|
13
|
+
_LOGGER = logging.getLogger("fastapiobserver.security")
|
|
14
|
+
|
|
11
15
|
|
|
12
16
|
def _extract_scope_client_ip(scope: Scope) -> str | None:
|
|
13
17
|
client = scope.get("client")
|
|
@@ -22,6 +26,19 @@ def _extract_scope_client_ip(scope: Scope) -> str | None:
|
|
|
22
26
|
class _IpResolver:
|
|
23
27
|
def __init__(self, policy: TrustedProxyPolicy) -> None:
|
|
24
28
|
self.policy = policy
|
|
29
|
+
if policy.honor_forwarded_headers:
|
|
30
|
+
_LOGGER.warning(
|
|
31
|
+
"security.forwarded_headers.enabled",
|
|
32
|
+
extra={
|
|
33
|
+
"event": {
|
|
34
|
+
"message": (
|
|
35
|
+
"X-Forwarded-For trust is enabled. "
|
|
36
|
+
"Ensure trusted_cidrs is locked to your proxy IPs only."
|
|
37
|
+
),
|
|
38
|
+
},
|
|
39
|
+
"_skip_enrichers": True,
|
|
40
|
+
},
|
|
41
|
+
)
|
|
25
42
|
|
|
26
43
|
def resolve(
|
|
27
44
|
self,
|
|
@@ -4,6 +4,8 @@ from typing import Any, Mapping
|
|
|
4
4
|
from .normalize import _normalize_key, _normalize_media_type
|
|
5
5
|
from .policy import _DROP, SecurityPolicy
|
|
6
6
|
|
|
7
|
+
MAX_SANITIZE_DEPTH = 32
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
def _redact_value(value: Any, policy: SecurityPolicy) -> Any:
|
|
9
11
|
if policy.redaction_mode == "drop":
|
|
@@ -49,6 +51,8 @@ def _sanitize_mapping(
|
|
|
49
51
|
header_allowlist: set[str] | None,
|
|
50
52
|
event_key_allowlist: set[str] | None,
|
|
51
53
|
) -> dict[str, Any]:
|
|
54
|
+
if depth > MAX_SANITIZE_DEPTH:
|
|
55
|
+
return dict(data)
|
|
52
56
|
out: dict[str, Any] = {}
|
|
53
57
|
for key, value in data.items():
|
|
54
58
|
normalized_key = _normalize_key(str(key))
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from fastapi.testclient import TestClient
|
|
5
|
+
|
|
6
|
+
from examples.time_saved.the_hard_way import app as hard_app
|
|
7
|
+
from examples.time_saved.the_easy_way import app as easy_app
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def hard_client() -> TestClient:
|
|
12
|
+
return TestClient(hard_app)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def easy_client() -> TestClient:
|
|
17
|
+
return TestClient(easy_app)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_dx_parity_metrics_and_logs(
|
|
21
|
+
hard_client: TestClient, easy_client: TestClient, caplog: pytest.LogCaptureFixture
|
|
22
|
+
) -> None:
|
|
23
|
+
caplog.set_level(logging.INFO)
|
|
24
|
+
|
|
25
|
+
# 1. Fire identical requests
|
|
26
|
+
hard_resp = hard_client.get("/hello/world")
|
|
27
|
+
easy_resp = easy_client.get("/hello/world")
|
|
28
|
+
|
|
29
|
+
assert hard_resp.status_code == 200
|
|
30
|
+
assert easy_resp.status_code == 200
|
|
31
|
+
|
|
32
|
+
# 2. Check JSON Logs
|
|
33
|
+
# Note: Because the 'hard' way uses a custom JSONFormatter that dumps
|
|
34
|
+
# strings, we need to inspect the formatted messages or the test log
|
|
35
|
+
# output. Fortunately, caplog captures records directly!
|
|
36
|
+
# Let's inspect the Raw capout
|
|
37
|
+
records = caplog.records
|
|
38
|
+
assert len(records) >= 4, "Both apps should have emitted log records"
|
|
39
|
+
|
|
40
|
+
# Check "hard way" request log
|
|
41
|
+
hard_request_logs = [r for r in records if getattr(r, "request_info", None)]
|
|
42
|
+
assert len(hard_request_logs) == 1
|
|
43
|
+
|
|
44
|
+
# Check "easy way" request log
|
|
45
|
+
easy_request_logs = [
|
|
46
|
+
r for r in records
|
|
47
|
+
if r.name == "fastapiobserver.middleware" and r.message == "request.completed"
|
|
48
|
+
]
|
|
49
|
+
assert len(easy_request_logs) == 1
|
|
50
|
+
|
|
51
|
+
# In JSON rendering, these produce identical keys
|
|
52
|
+
# (trace_id, span_id, request_id, event/request...)
|
|
53
|
+
# The true test of parity is the presence of the contextual fields
|
|
54
|
+
assert hasattr(hard_request_logs[0], "request_info")
|
|
55
|
+
assert hasattr(easy_request_logs[0], "event")
|
|
56
|
+
|
|
57
|
+
# 3. Check Prometheus Metrics Parity
|
|
58
|
+
hard_metrics = hard_client.get("/metrics")
|
|
59
|
+
easy_metrics = easy_client.get("/metrics")
|
|
60
|
+
|
|
61
|
+
assert hard_metrics.status_code == 200
|
|
62
|
+
assert easy_metrics.status_code == 200
|
|
63
|
+
|
|
64
|
+
hard_text = hard_metrics.text
|
|
65
|
+
easy_text = easy_metrics.text
|
|
66
|
+
|
|
67
|
+
# Both should have successfully routed the GET /hello/{name}
|
|
68
|
+
# and prevented cardinality explosion via templating.
|
|
69
|
+
# The 'hard_way' hand-rolled labels looks like:
|
|
70
|
+
# `http_requests_total{method="GET",path="/hello/{name}",status="200"}`
|
|
71
|
+
# The 'easy_way' uses standard fastapiobserver prometheus conventions:
|
|
72
|
+
# `http_server_requests_total{...,path="/hello/{name}",status_code="200"}`
|
|
73
|
+
|
|
74
|
+
assert 'path="/hello/{name}"' in hard_text
|
|
75
|
+
assert 'path="/hello/{name}"' in easy_text
|
|
76
|
+
|
|
77
|
+
assert 'status="200"' in hard_text
|
|
78
|
+
assert 'status_code="200"' in easy_text
|
|
79
|
+
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Tests for security audit fixes.
|
|
2
|
+
|
|
3
|
+
Covers:
|
|
4
|
+
- GDPR preset redacts `client_ip` event key
|
|
5
|
+
- Exception message credential sanitization
|
|
6
|
+
- Recursion depth limit in sanitize_event
|
|
7
|
+
- Forwarded-headers startup warning log
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from fastapiobserver.middleware.events import (
|
|
16
|
+
_RequestEventBuilder,
|
|
17
|
+
_sanitize_exception_message,
|
|
18
|
+
)
|
|
19
|
+
from fastapiobserver.security import SecurityPolicy, sanitize_event
|
|
20
|
+
from fastapiobserver.security.redaction import MAX_SANITIZE_DEPTH
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
# P0: GDPR preset redacts `client_ip`
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
def test_gdpr_preset_redacts_client_ip() -> None:
|
|
28
|
+
policy = SecurityPolicy.from_preset("gdpr")
|
|
29
|
+
event = {"client_ip": "203.0.113.42", "method": "GET"}
|
|
30
|
+
sanitized = sanitize_event(event, policy)
|
|
31
|
+
assert sanitized["client_ip"].startswith("sha256:")
|
|
32
|
+
assert sanitized["method"] == "GET"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_gdpr_preset_redacts_client_ip_inside_nested_dict() -> None:
|
|
36
|
+
policy = SecurityPolicy.from_preset("gdpr")
|
|
37
|
+
event = {"nested": {"client_ip": "10.0.0.1"}}
|
|
38
|
+
sanitized = sanitize_event(event, policy)
|
|
39
|
+
assert sanitized["nested"]["client_ip"].startswith("sha256:")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# P0: Exception message credential sanitization
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
class TestSanitizeExceptionMessage:
|
|
47
|
+
def test_strips_basic_credential_url(self) -> None:
|
|
48
|
+
msg = "Cannot connect to postgres://admin:s3cret@db.example.com:5432/prod"
|
|
49
|
+
result = _sanitize_exception_message(msg)
|
|
50
|
+
assert "s3cret" not in result
|
|
51
|
+
assert "://***:***@" in result
|
|
52
|
+
assert "db.example.com:5432/prod" in result
|
|
53
|
+
|
|
54
|
+
def test_strips_redis_credential_url(self) -> None:
|
|
55
|
+
msg = "redis://default:my_password@redis.local:6379/0"
|
|
56
|
+
result = _sanitize_exception_message(msg)
|
|
57
|
+
assert "my_password" not in result
|
|
58
|
+
assert "://***:***@" in result
|
|
59
|
+
|
|
60
|
+
def test_strips_multiple_credentials(self) -> None:
|
|
61
|
+
msg = (
|
|
62
|
+
"DB: postgres://u1:p1@host1/db "
|
|
63
|
+
"Cache: redis://u2:p2@host2/0"
|
|
64
|
+
)
|
|
65
|
+
result = _sanitize_exception_message(msg)
|
|
66
|
+
assert "p1" not in result
|
|
67
|
+
assert "p2" not in result
|
|
68
|
+
assert result.count("://***:***@") == 2
|
|
69
|
+
|
|
70
|
+
def test_truncates_long_messages(self) -> None:
|
|
71
|
+
msg = "x" * 1000
|
|
72
|
+
result = _sanitize_exception_message(msg, max_length=256)
|
|
73
|
+
assert len(result) == 256
|
|
74
|
+
|
|
75
|
+
def test_preserves_safe_messages(self) -> None:
|
|
76
|
+
msg = "Connection refused on port 8080"
|
|
77
|
+
assert _sanitize_exception_message(msg) == msg
|
|
78
|
+
|
|
79
|
+
def test_event_builder_sanitizes_exception(self) -> None:
|
|
80
|
+
builder = _RequestEventBuilder(SecurityPolicy())
|
|
81
|
+
error = ConnectionError(
|
|
82
|
+
"Failed: postgres://admin:secret123@db.prod.internal:5432/mydb"
|
|
83
|
+
)
|
|
84
|
+
event = builder.build(
|
|
85
|
+
method="GET",
|
|
86
|
+
path="/test",
|
|
87
|
+
status_code=500,
|
|
88
|
+
duration_seconds=0.1,
|
|
89
|
+
client_ip=None,
|
|
90
|
+
request_body=None,
|
|
91
|
+
response_body=None,
|
|
92
|
+
error_type="server_error",
|
|
93
|
+
exception=error,
|
|
94
|
+
)
|
|
95
|
+
assert "secret123" not in event["exception_message"]
|
|
96
|
+
assert "://***:***@" in event["exception_message"]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
# P1: Recursion depth limit
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
def test_sanitize_event_handles_deeply_nested_payload() -> None:
|
|
104
|
+
"""Ensure sanitize_event doesn't RecursionError on extreme nesting."""
|
|
105
|
+
depth = MAX_SANITIZE_DEPTH + 20 # well beyond the limit
|
|
106
|
+
data: dict = {"leaf": "value"}
|
|
107
|
+
for _ in range(depth):
|
|
108
|
+
data = {"nested": data}
|
|
109
|
+
# Must not raise RecursionError
|
|
110
|
+
result = sanitize_event(data, SecurityPolicy())
|
|
111
|
+
assert isinstance(result, dict)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_sanitize_event_still_redacts_within_depth_limit() -> None:
|
|
115
|
+
"""Normal nesting (within limit) still redacts correctly."""
|
|
116
|
+
data: dict = {"password": "secret"}
|
|
117
|
+
for _ in range(5):
|
|
118
|
+
data = {"nested": data}
|
|
119
|
+
result = sanitize_event(data, SecurityPolicy())
|
|
120
|
+
# Walk to the innermost level
|
|
121
|
+
inner = result
|
|
122
|
+
for _ in range(5):
|
|
123
|
+
inner = inner["nested"]
|
|
124
|
+
assert inner["password"] == "***"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
# P1: Forwarded-headers startup warning
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
def test_ip_resolver_warns_when_forwarded_headers_enabled(caplog: pytest.LogCaptureFixture) -> None:
|
|
132
|
+
from fastapiobserver.middleware.ip import _IpResolver
|
|
133
|
+
from fastapiobserver.security import TrustedProxyPolicy
|
|
134
|
+
|
|
135
|
+
policy = TrustedProxyPolicy(
|
|
136
|
+
enabled=True,
|
|
137
|
+
honor_forwarded_headers=True,
|
|
138
|
+
)
|
|
139
|
+
with caplog.at_level(logging.WARNING, logger="fastapiobserver.security"):
|
|
140
|
+
_IpResolver(policy)
|
|
141
|
+
|
|
142
|
+
assert any(
|
|
143
|
+
"security.forwarded_headers.enabled" in record.message
|
|
144
|
+
for record in caplog.records
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_ip_resolver_no_warning_when_forwarded_headers_disabled(
|
|
149
|
+
caplog: pytest.LogCaptureFixture
|
|
150
|
+
) -> None:
|
|
151
|
+
from fastapiobserver.middleware.ip import _IpResolver
|
|
152
|
+
from fastapiobserver.security import TrustedProxyPolicy
|
|
153
|
+
|
|
154
|
+
policy = TrustedProxyPolicy(enabled=True, honor_forwarded_headers=False)
|
|
155
|
+
with caplog.at_level(logging.WARNING, logger="fastapiobserver.security"):
|
|
156
|
+
_IpResolver(policy)
|
|
157
|
+
|
|
158
|
+
assert not any(
|
|
159
|
+
"X-Forwarded-For trust is enabled" in record.message
|
|
160
|
+
for record in caplog.records
|
|
161
|
+
)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.0"
|
|
File without changes
|
|
File without changes
|
{fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapi_observer.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapi_observer.egg-info/requires.txt
RENAMED
|
File without changes
|
{fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapi_observer.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/integrations/__init__.py
RENAMED
|
File without changes
|
{fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/integrations/strawberry.py
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/logging/circuit_breaker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/prometheus/__init__.py
RENAMED
|
File without changes
|
{fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/prometheus/backend.py
RENAMED
|
File without changes
|
{fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/metrics/prometheus/client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/middleware/__init__.py
RENAMED
|
File without changes
|
{fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/middleware/body_capture.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/middleware/request_logging.py
RENAMED
|
File without changes
|
{fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/middleware/span_errors.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/sinks/logtail/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_observer-0.4.0 → fastapi_observer-1.0.0}/src/fastapiobserver/sinks/logtail/handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|