fastapi-observer 0.1.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.1.0/LICENSE +21 -0
- fastapi_observer-0.1.0/PKG-INFO +674 -0
- fastapi_observer-0.1.0/README.md +611 -0
- fastapi_observer-0.1.0/pyproject.toml +99 -0
- fastapi_observer-0.1.0/setup.cfg +4 -0
- fastapi_observer-0.1.0/src/fastapi_observer.egg-info/PKG-INFO +674 -0
- fastapi_observer-0.1.0/src/fastapi_observer.egg-info/SOURCES.txt +43 -0
- fastapi_observer-0.1.0/src/fastapi_observer.egg-info/dependency_links.txt +1 -0
- fastapi_observer-0.1.0/src/fastapi_observer.egg-info/requires.txt +42 -0
- fastapi_observer-0.1.0/src/fastapi_observer.egg-info/top_level.txt +1 -0
- fastapi_observer-0.1.0/src/fastapiobserver/__init__.py +114 -0
- fastapi_observer-0.1.0/src/fastapiobserver/_version.py +1 -0
- fastapi_observer-0.1.0/src/fastapiobserver/config.py +136 -0
- fastapi_observer-0.1.0/src/fastapiobserver/control_plane.py +139 -0
- fastapi_observer-0.1.0/src/fastapiobserver/fastapi.py +135 -0
- fastapi_observer-0.1.0/src/fastapiobserver/logging.py +247 -0
- fastapi_observer-0.1.0/src/fastapiobserver/metrics.py +416 -0
- fastapi_observer-0.1.0/src/fastapiobserver/middleware.py +410 -0
- fastapi_observer-0.1.0/src/fastapiobserver/otel.py +719 -0
- fastapi_observer-0.1.0/src/fastapiobserver/plugins.py +65 -0
- fastapi_observer-0.1.0/src/fastapiobserver/propagation.py +167 -0
- fastapi_observer-0.1.0/src/fastapiobserver/py.typed +1 -0
- fastapi_observer-0.1.0/src/fastapiobserver/request_context.py +59 -0
- fastapi_observer-0.1.0/src/fastapiobserver/security.py +508 -0
- fastapi_observer-0.1.0/src/fastapiobserver/sinks.py +420 -0
- fastapi_observer-0.1.0/src/fastapiobserver/utils.py +28 -0
- fastapi_observer-0.1.0/tests/test_body_media_allowlist.py +85 -0
- fastapi_observer-0.1.0/tests/test_config_settings.py +101 -0
- fastapi_observer-0.1.0/tests/test_excluded_url_precedence.py +101 -0
- fastapi_observer-0.1.0/tests/test_fastapi_install.py +35 -0
- fastapi_observer-0.1.0/tests/test_logging_schema_contract.py +88 -0
- fastapi_observer-0.1.0/tests/test_metrics_optional_dependency.py +68 -0
- fastapi_observer-0.1.0/tests/test_middleware.py +111 -0
- fastapi_observer-0.1.0/tests/test_negotiate_content_type.py +93 -0
- fastapi_observer-0.1.0/tests/test_otel_integration.py +123 -0
- fastapi_observer-0.1.0/tests/test_otel_log_correlation.py +120 -0
- fastapi_observer-0.1.0/tests/test_otel_logs_from_env.py +77 -0
- fastapi_observer-0.1.0/tests/test_otel_logs_pipeline.py +72 -0
- fastapi_observer-0.1.0/tests/test_otlp_export_integration.py +67 -0
- fastapi_observer-0.1.0/tests/test_plugin_isolation.py +61 -0
- fastapi_observer-0.1.0/tests/test_redaction_presets.py +71 -0
- fastapi_observer-0.1.0/tests/test_route_template_cardinality.py +51 -0
- fastapi_observer-0.1.0/tests/test_runtime_control_auth.py +41 -0
- fastapi_observer-0.1.0/tests/test_security_redaction.py +83 -0
- fastapi_observer-0.1.0/tests/test_trust_boundary.py +53 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vitaee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi-observer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Zero-glue FastAPI observability with security presets and runtime controls
|
|
5
|
+
Author-email: Vitaee <opensource@vitaee.dev>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Vitaee/FastapiObserver
|
|
8
|
+
Project-URL: Documentation, https://github.com/Vitaee/FastapiObserver#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/Vitaee/FastapiObserver.git
|
|
10
|
+
Project-URL: Issues, https://github.com/Vitaee/FastapiObserver/issues
|
|
11
|
+
Keywords: fastapi,observability,logging,metrics,opentelemetry
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Framework :: FastAPI
|
|
22
|
+
Classifier: Topic :: System :: Logging
|
|
23
|
+
Classifier: Topic :: System :: Monitoring
|
|
24
|
+
Requires-Python: <3.15,>=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: fastapi>=0.129.0
|
|
28
|
+
Requires-Dist: pydantic-settings>=2.10.1
|
|
29
|
+
Requires-Dist: starlette>=0.52.1
|
|
30
|
+
Provides-Extra: fast-json
|
|
31
|
+
Requires-Dist: orjson>=3.11.7; extra == "fast-json"
|
|
32
|
+
Provides-Extra: prometheus
|
|
33
|
+
Requires-Dist: prometheus-client>=0.24.1; extra == "prometheus"
|
|
34
|
+
Provides-Extra: otel
|
|
35
|
+
Requires-Dist: opentelemetry-api>=1.39.1; extra == "otel"
|
|
36
|
+
Requires-Dist: opentelemetry-sdk>=1.39.1; extra == "otel"
|
|
37
|
+
Requires-Dist: opentelemetry-exporter-otlp>=1.39.1; extra == "otel"
|
|
38
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.60b1; extra == "otel"
|
|
39
|
+
Requires-Dist: opentelemetry-instrumentation-logging>=0.60b1; extra == "otel"
|
|
40
|
+
Provides-Extra: otel-httpx
|
|
41
|
+
Requires-Dist: opentelemetry-instrumentation-httpx>=0.60b1; extra == "otel-httpx"
|
|
42
|
+
Provides-Extra: otel-requests
|
|
43
|
+
Requires-Dist: opentelemetry-instrumentation-requests>=0.60b1; extra == "otel-requests"
|
|
44
|
+
Provides-Extra: all
|
|
45
|
+
Requires-Dist: orjson>=3.11.7; extra == "all"
|
|
46
|
+
Requires-Dist: prometheus-client>=0.24.1; extra == "all"
|
|
47
|
+
Requires-Dist: opentelemetry-api>=1.39.1; extra == "all"
|
|
48
|
+
Requires-Dist: opentelemetry-sdk>=1.39.1; extra == "all"
|
|
49
|
+
Requires-Dist: opentelemetry-exporter-otlp>=1.39.1; extra == "all"
|
|
50
|
+
Requires-Dist: opentelemetry-instrumentation-fastapi>=0.60b1; extra == "all"
|
|
51
|
+
Requires-Dist: opentelemetry-instrumentation-logging>=0.60b1; extra == "all"
|
|
52
|
+
Requires-Dist: opentelemetry-instrumentation-httpx>=0.60b1; extra == "all"
|
|
53
|
+
Requires-Dist: opentelemetry-instrumentation-requests>=0.60b1; extra == "all"
|
|
54
|
+
Provides-Extra: dev
|
|
55
|
+
Requires-Dist: httpx>=0.28.1; extra == "dev"
|
|
56
|
+
Requires-Dist: mypy>=1.19.1; extra == "dev"
|
|
57
|
+
Requires-Dist: pip-audit>=2.10.0; extra == "dev"
|
|
58
|
+
Requires-Dist: pytest>=9.0.2; extra == "dev"
|
|
59
|
+
Requires-Dist: pytest-randomly>=3.16.0; extra == "dev"
|
|
60
|
+
Requires-Dist: ruff>=0.15.1; extra == "dev"
|
|
61
|
+
Requires-Dist: cyclonedx-bom>=7.2.1; extra == "dev"
|
|
62
|
+
Dynamic: license-file
|
|
63
|
+
|
|
64
|
+
# fastapi-observer
|
|
65
|
+
|
|
66
|
+
**Zero-glue observability for FastAPI.**
|
|
67
|
+
|
|
68
|
+
`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.
|
|
69
|
+
|
|
70
|
+
**Supported Python versions:** `3.10` to `3.14`
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Why This Package Exists
|
|
75
|
+
|
|
76
|
+
Most FastAPI services eventually need the same observability plumbing:
|
|
77
|
+
- Structured JSON logging
|
|
78
|
+
- Request and trace correlation
|
|
79
|
+
- Metrics for dashboards and alerts
|
|
80
|
+
- OpenTelemetry setup
|
|
81
|
+
- Redaction/sanitization for sensitive data
|
|
82
|
+
- Runtime controls for incident response
|
|
83
|
+
|
|
84
|
+
Teams usually implement this as custom glue code in every service. That costs engineering time and creates drift between services.
|
|
85
|
+
|
|
86
|
+
`fastapi-observer` replaces this repeated wiring with a consistent, secure-by-default setup.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Read This by Role
|
|
91
|
+
|
|
92
|
+
If you are a new graduate or new to observability:
|
|
93
|
+
1. Start with [Install](#install)
|
|
94
|
+
2. Run [5-Minute Quick Start](#5-minute-quick-start)
|
|
95
|
+
3. Read [Security Defaults and Presets](#security-defaults-and-presets)
|
|
96
|
+
4. Copy an app from [Examples](#examples)
|
|
97
|
+
|
|
98
|
+
If you are a senior engineer, architect, or CTO:
|
|
99
|
+
1. Read [What You Get Immediately](#what-you-get-immediately)
|
|
100
|
+
2. Review [Runtime Control Plane (No Restart)](#runtime-control-plane-no-restart)
|
|
101
|
+
3. Review [OpenTelemetry (Traces + Optional OTLP Logs)](#opentelemetry-traces--optional-otlp-logs)
|
|
102
|
+
4. Use [Production Deployment Pattern](#production-deployment-pattern)
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## What You Get Immediately
|
|
107
|
+
|
|
108
|
+
After one call to `install_observability()`:
|
|
109
|
+
|
|
110
|
+
| Capability | Included | Default |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| Structured JSON logs | Yes | Enabled |
|
|
113
|
+
| Request ID correlation | Yes | Enabled |
|
|
114
|
+
| Trace/span IDs in logs | Yes (with OTel) | Off until OTel enabled |
|
|
115
|
+
| Prometheus `/metrics` | Yes | Off until `metrics_enabled=True` |
|
|
116
|
+
| Sensitive-data redaction | Yes | Enabled |
|
|
117
|
+
| Security presets (`strict`, `pci`, `gdpr`) | Yes | Available |
|
|
118
|
+
| Runtime control endpoint | Yes | Off until enabled |
|
|
119
|
+
| Plugin hooks for enrichment/hooks | Yes | Available |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Install
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Core (logging + metrics + security)
|
|
127
|
+
pip install fastapi-observer
|
|
128
|
+
|
|
129
|
+
# Prometheus metrics support
|
|
130
|
+
pip install "fastapi-observer[prometheus]"
|
|
131
|
+
|
|
132
|
+
# OpenTelemetry tracing/logs support
|
|
133
|
+
pip install "fastapi-observer[otel]"
|
|
134
|
+
|
|
135
|
+
# Everything
|
|
136
|
+
pip install "fastapi-observer[all]"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Import path:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import fastapiobserver
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 5-Minute Quick Start
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from fastapi import FastAPI
|
|
151
|
+
from fastapiobserver import ObservabilitySettings, install_observability
|
|
152
|
+
|
|
153
|
+
app = FastAPI()
|
|
154
|
+
|
|
155
|
+
settings = ObservabilitySettings(
|
|
156
|
+
app_name="orders-api",
|
|
157
|
+
service="orders",
|
|
158
|
+
environment="production",
|
|
159
|
+
version="0.1.0",
|
|
160
|
+
metrics_enabled=True,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
install_observability(app, settings)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@app.get("/orders/{order_id}")
|
|
167
|
+
def get_order(order_id: int) -> dict[str, int]:
|
|
168
|
+
return {"order_id": order_id}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Run:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
uvicorn main:app --reload
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Now you have:
|
|
178
|
+
- Structured request logs on every request
|
|
179
|
+
- Request ID propagation
|
|
180
|
+
- Sanitized event payloads
|
|
181
|
+
- Prometheus metrics at `/metrics`
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Security Defaults and Presets
|
|
186
|
+
|
|
187
|
+
### Default protections
|
|
188
|
+
|
|
189
|
+
| Protection | Default | Why |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| Body logging | `OFF` | Avoid leaking request/response secrets |
|
|
192
|
+
| Sensitive key masking | `ON` | Protect fields like `password`, `token`, `secret` |
|
|
193
|
+
| Sensitive header masking | `ON` | Protect `authorization`, `cookie`, `x-api-key` |
|
|
194
|
+
| Query string in logged path | Excluded | Prevent accidental token leakage |
|
|
195
|
+
| Request ID trust boundary | Trusted CIDRs only | Prevent spoofed correlation IDs |
|
|
196
|
+
|
|
197
|
+
### Presets for regulated environments
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from fastapiobserver import SecurityPolicy
|
|
201
|
+
|
|
202
|
+
# Strictest option: drop sensitive values and keep minimal safe headers
|
|
203
|
+
strict_policy = SecurityPolicy.from_preset("strict")
|
|
204
|
+
|
|
205
|
+
# PCI-focused redaction fields
|
|
206
|
+
pci_policy = SecurityPolicy.from_preset("pci")
|
|
207
|
+
|
|
208
|
+
# GDPR-focused hashed PII fields
|
|
209
|
+
gdpr_policy = SecurityPolicy.from_preset("gdpr")
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Use a preset in installation:
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
install_observability(app, settings, security_policy=SecurityPolicy.from_preset("pci"))
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Allowlist-only logging (audit-style)
|
|
219
|
+
|
|
220
|
+
If your compliance model is "log only approved fields", use allowlists:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from fastapiobserver import SecurityPolicy
|
|
224
|
+
|
|
225
|
+
policy = SecurityPolicy(
|
|
226
|
+
header_allowlist=("x-request-id", "content-type", "user-agent"),
|
|
227
|
+
event_key_allowlist=("method", "path", "status_code"),
|
|
228
|
+
)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Body capture media-type guard
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
policy = SecurityPolicy(
|
|
235
|
+
log_request_body=True,
|
|
236
|
+
body_capture_media_types=("application/json",),
|
|
237
|
+
)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Runtime Control Plane (No Restart)
|
|
243
|
+
|
|
244
|
+
Use runtime controls when you need higher log verbosity or different trace sampling during an incident.
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
export OBSERVABILITY_CONTROL_TOKEN="replace-me"
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
from fastapiobserver import RuntimeControlSettings, install_observability
|
|
252
|
+
|
|
253
|
+
runtime_control = RuntimeControlSettings(enabled=True)
|
|
254
|
+
install_observability(app, settings, runtime_control_settings=runtime_control)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Inspect current runtime values:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
curl -X GET http://localhost:8000/_observability/control \
|
|
261
|
+
-H "Authorization: Bearer replace-me"
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Update runtime values:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
curl -X POST http://localhost:8000/_observability/control \
|
|
268
|
+
-H "Authorization: Bearer replace-me" \
|
|
269
|
+
-H "Content-Type: application/json" \
|
|
270
|
+
-d '{"log_level":"DEBUG","trace_sampling_ratio":0.25}'
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
What changes immediately:
|
|
274
|
+
- Root logger level (and uvicorn loggers)
|
|
275
|
+
- Dynamic OTel trace sampling ratio
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## OpenTelemetry (Traces + Optional OTLP Logs)
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
from fastapiobserver import OTelLogsSettings, OTelSettings, install_observability
|
|
283
|
+
|
|
284
|
+
otel_settings = OTelSettings(
|
|
285
|
+
enabled=True,
|
|
286
|
+
service_name="orders-api",
|
|
287
|
+
service_version="2.0.0",
|
|
288
|
+
environment="production",
|
|
289
|
+
otlp_endpoint="http://localhost:4317",
|
|
290
|
+
protocol="grpc", # or "http/protobuf"
|
|
291
|
+
trace_sampling_ratio=1.0,
|
|
292
|
+
extra_resource_attributes={
|
|
293
|
+
"k8s.namespace": "prod",
|
|
294
|
+
"team": "backend",
|
|
295
|
+
},
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
otel_logs_settings = OTelLogsSettings(
|
|
299
|
+
enabled=True,
|
|
300
|
+
logs_mode="both", # "local_json", "otlp", or "both"
|
|
301
|
+
otlp_endpoint="http://localhost:4317",
|
|
302
|
+
protocol="grpc",
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
install_observability(
|
|
306
|
+
app,
|
|
307
|
+
settings,
|
|
308
|
+
otel_settings=otel_settings,
|
|
309
|
+
otel_logs_settings=otel_logs_settings,
|
|
310
|
+
)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Design details:
|
|
314
|
+
- Reuses an externally configured tracer provider if one already exists.
|
|
315
|
+
- Injects trace IDs into application logs for log-trace correlation.
|
|
316
|
+
- Supports runtime sampling updates through the control plane.
|
|
317
|
+
- Sends OTel logs in OTLP mode with the same sanitization policy.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## What `install_observability()` Wires Up
|
|
322
|
+
|
|
323
|
+
1. Structured logging pipeline (JSON formatter + async queue handler).
|
|
324
|
+
2. Metrics backend and `/metrics` endpoint when metrics are enabled.
|
|
325
|
+
3. OTel tracing setup when OTel is enabled.
|
|
326
|
+
4. Request logging middleware with sanitization and context cleanup.
|
|
327
|
+
5. Runtime control endpoint when runtime control is enabled.
|
|
328
|
+
|
|
329
|
+
Request path lifecycle (high-level):
|
|
330
|
+
|
|
331
|
+
```text
|
|
332
|
+
Request arrives
|
|
333
|
+
-> request ID / trace context resolved
|
|
334
|
+
-> app handler executes
|
|
335
|
+
-> response classified (ok/client_error/server_error/exception)
|
|
336
|
+
-> payload sanitized by policy
|
|
337
|
+
-> log emitted + metrics recorded
|
|
338
|
+
-> context cleared
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Example JSON Log Event
|
|
344
|
+
|
|
345
|
+
```json
|
|
346
|
+
{
|
|
347
|
+
"timestamp": "2026-02-18T10:30:00.000000+00:00",
|
|
348
|
+
"level": "INFO",
|
|
349
|
+
"logger": "fastapiobserver.middleware",
|
|
350
|
+
"message": "request.completed",
|
|
351
|
+
"app_name": "orders-api",
|
|
352
|
+
"service": "orders",
|
|
353
|
+
"environment": "production",
|
|
354
|
+
"version": "0.1.0",
|
|
355
|
+
"log_schema_version": "1.0.0",
|
|
356
|
+
"library": "fastapiobserver",
|
|
357
|
+
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
|
358
|
+
"trace_id": "0af7651916cd43dd8448eb211c80319c",
|
|
359
|
+
"span_id": "b7ad6b7169203331",
|
|
360
|
+
"event": {
|
|
361
|
+
"method": "GET",
|
|
362
|
+
"path": "/orders/42",
|
|
363
|
+
"status_code": 200,
|
|
364
|
+
"duration_ms": 3.456,
|
|
365
|
+
"client_ip": "10.0.0.1",
|
|
366
|
+
"error_type": "ok"
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Production Deployment Pattern
|
|
374
|
+
|
|
375
|
+
Recommended topology:
|
|
376
|
+
- FastAPI services emit logs/metrics/traces.
|
|
377
|
+
- Prometheus scrapes each service's `/metrics`.
|
|
378
|
+
- Traces/logs are shipped through an OpenTelemetry Collector (or Alloy).
|
|
379
|
+
- Grafana/Tempo/Loki read from centralized backends.
|
|
380
|
+
|
|
381
|
+
Minimal collector reference:
|
|
382
|
+
|
|
383
|
+
```yaml
|
|
384
|
+
receivers:
|
|
385
|
+
otlp:
|
|
386
|
+
protocols:
|
|
387
|
+
grpc:
|
|
388
|
+
endpoint: 0.0.0.0:4317
|
|
389
|
+
http:
|
|
390
|
+
endpoint: 0.0.0.0:4318
|
|
391
|
+
|
|
392
|
+
processors:
|
|
393
|
+
memory_limiter:
|
|
394
|
+
limit_mib: 512
|
|
395
|
+
spike_limit_mib: 128
|
|
396
|
+
check_interval: 5s
|
|
397
|
+
batch:
|
|
398
|
+
send_batch_size: 512
|
|
399
|
+
timeout: 5s
|
|
400
|
+
|
|
401
|
+
exporters:
|
|
402
|
+
otlp/tempo:
|
|
403
|
+
endpoint: tempo:4317
|
|
404
|
+
otlp/loki:
|
|
405
|
+
endpoint: loki:3100
|
|
406
|
+
|
|
407
|
+
service:
|
|
408
|
+
pipelines:
|
|
409
|
+
traces:
|
|
410
|
+
receivers: [otlp]
|
|
411
|
+
processors: [memory_limiter, batch]
|
|
412
|
+
exporters: [otlp/tempo]
|
|
413
|
+
logs:
|
|
414
|
+
receivers: [otlp]
|
|
415
|
+
processors: [memory_limiter, batch]
|
|
416
|
+
exporters: [otlp/loki]
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Operational notes for leadership:
|
|
420
|
+
- Use TLS and authenticated exporters in production.
|
|
421
|
+
- Keep high-fidelity traces for errors and slow paths; sample the rest.
|
|
422
|
+
- Keep one policy for sanitization across local and OTLP sinks.
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## Examples
|
|
427
|
+
|
|
428
|
+
The `examples/` directory contains runnable demos:
|
|
429
|
+
|
|
430
|
+
| Example | What it shows |
|
|
431
|
+
|---|---|
|
|
432
|
+
| [`basic_app.py`](examples/basic_app.py) | Minimal setup and request logging |
|
|
433
|
+
| [`security_presets_app.py`](examples/security_presets_app.py) | Preset-based security policy |
|
|
434
|
+
| [`allowlist_app.py`](examples/allowlist_app.py) | Allowlist-only sanitization |
|
|
435
|
+
| [`otel_app.py`](examples/otel_app.py) | OTel tracing and resource attributes |
|
|
436
|
+
| [`full_stack/`](examples/full_stack/) | **Docker Compose stack**: 3 FastAPI services + Grafana + Prometheus + Loki + Tempo |
|
|
437
|
+
|
|
438
|
+
Run an example:
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
uvicorn examples.basic_app:app --reload
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Dashboard Screenshots (Full-Stack Demo)
|
|
445
|
+
|
|
446
|
+
From `examples/full_stack`, these are real Grafana views generated by `fastapi-observer` telemetry:
|
|
447
|
+
|
|
448
|
+
**Overview panels (latency heatmap, route throughput, errors, CPU/memory):**
|
|
449
|
+
|
|
450
|
+

|
|
451
|
+
|
|
452
|
+
**Percentiles, request rate, and structured JSON logs in Loki:**
|
|
453
|
+
|
|
454
|
+

|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Environment Variables
|
|
459
|
+
|
|
460
|
+
The library supports configuration from code and env vars. Below are the most relevant env vars by area.
|
|
461
|
+
|
|
462
|
+
### Identity and logging
|
|
463
|
+
|
|
464
|
+
| Variable | Default | Description |
|
|
465
|
+
|---|---|---|
|
|
466
|
+
| `APP_NAME` | `app` | Namespace for app-level identity |
|
|
467
|
+
| `SERVICE_NAME` | `api` | Service label for logs/metrics |
|
|
468
|
+
| `ENVIRONMENT` | `development` | Environment label |
|
|
469
|
+
| `APP_VERSION` | `0.0.0` | Service version |
|
|
470
|
+
| `LOG_LEVEL` | `INFO` | Root log level |
|
|
471
|
+
| `LOG_DIR` | - | Optional file log directory |
|
|
472
|
+
| `REQUEST_ID_HEADER` | `x-request-id` | Incoming request ID header |
|
|
473
|
+
| `RESPONSE_REQUEST_ID_HEADER` | `x-request-id` | Response request ID header |
|
|
474
|
+
|
|
475
|
+
### Metrics
|
|
476
|
+
|
|
477
|
+
| Variable | Default | Description |
|
|
478
|
+
|---|---|---|
|
|
479
|
+
| `METRICS_ENABLED` | `false` | Enable metrics backend |
|
|
480
|
+
| `METRICS_PATH` | `/metrics` | Metrics endpoint path |
|
|
481
|
+
| `METRICS_EXCLUDE_PATHS` | `/metrics,/health,/healthz,/docs,/openapi.json` | Skip metrics for noisy endpoints |
|
|
482
|
+
| `METRICS_EXEMPLARS_ENABLED` | `false` | Enable exemplars where supported |
|
|
483
|
+
| `METRICS_FORMAT` | `negotiate` | `prometheus`, `openmetrics`, or `negotiate` |
|
|
484
|
+
|
|
485
|
+
### Security and trust boundary
|
|
486
|
+
|
|
487
|
+
| Variable | Default | Description |
|
|
488
|
+
|---|---|---|
|
|
489
|
+
| `OBS_REDACTION_PRESET` | - | `strict`, `pci`, `gdpr` |
|
|
490
|
+
| `OBS_REDACTED_FIELDS` | built-in list | CSV keys to redact |
|
|
491
|
+
| `OBS_REDACTED_HEADERS` | built-in list | CSV headers to redact |
|
|
492
|
+
| `OBS_REDACTION_MODE` | `mask` | `mask`, `hash`, `drop` |
|
|
493
|
+
| `OBS_MASK_TEXT` | `***` | Mask replacement text |
|
|
494
|
+
| `OBS_LOG_REQUEST_BODY` | `false` | Enable request body logging |
|
|
495
|
+
| `OBS_LOG_RESPONSE_BODY` | `false` | Enable response body logging |
|
|
496
|
+
| `OBS_MAX_BODY_LENGTH` | `256` | Max captured body bytes |
|
|
497
|
+
| `OBS_HEADER_ALLOWLIST` | - | CSV headers allowed in logs |
|
|
498
|
+
| `OBS_EVENT_KEY_ALLOWLIST` | - | CSV event keys allowed in logs |
|
|
499
|
+
| `OBS_BODY_CAPTURE_MEDIA_TYPES` | - | CSV allowed media types for body capture |
|
|
500
|
+
| `OBS_TRUSTED_PROXY_ENABLED` | `true` | Enable trusted-proxy policy |
|
|
501
|
+
| `OBS_TRUSTED_CIDRS` | RFC1918 + loopback | CSV trusted CIDRs |
|
|
502
|
+
| `OBS_HONOR_FORWARDED_HEADERS` | `false` | Trust forwarded headers |
|
|
503
|
+
|
|
504
|
+
Notes:
|
|
505
|
+
- `OBS_HEADER_ALLOWLIST`, `OBS_EVENT_KEY_ALLOWLIST`, and `OBS_BODY_CAPTURE_MEDIA_TYPES` accept `none`, `null`, or `unset` to clear values.
|
|
506
|
+
|
|
507
|
+
### OpenTelemetry tracing/log export
|
|
508
|
+
|
|
509
|
+
| Variable | Default | Description |
|
|
510
|
+
|---|---|---|
|
|
511
|
+
| `OTEL_ENABLED` | `false` | Enable tracing instrumentation |
|
|
512
|
+
| `OTEL_SERVICE_NAME` | `SERVICE_NAME` | OTel service name override |
|
|
513
|
+
| `OTEL_SERVICE_VERSION` | `APP_VERSION` | OTel service version override |
|
|
514
|
+
| `OTEL_ENVIRONMENT` | `ENVIRONMENT` | OTel environment override |
|
|
515
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | - | OTLP endpoint |
|
|
516
|
+
| `OTEL_EXPORTER_OTLP_PROTOCOL` | `grpc` | `grpc` or `http/protobuf` |
|
|
517
|
+
| `OTEL_TRACE_SAMPLING_RATIO` | `1.0` | Initial trace sampling ratio |
|
|
518
|
+
| `OTEL_EXTRA_RESOURCE_ATTRIBUTES` | - | CSV `key=value` pairs |
|
|
519
|
+
| `OTEL_EXCLUDED_URLS` | auto-derived | CSV excluded paths for tracing |
|
|
520
|
+
| `OTEL_LOGS_ENABLED` | `false` | Enable OTLP log export |
|
|
521
|
+
| `OTEL_LOGS_MODE` | `local_json` | `local_json`, `otlp`, `both` |
|
|
522
|
+
| `OTEL_LOGS_ENDPOINT` | - | OTLP logs endpoint |
|
|
523
|
+
| `OTEL_LOGS_PROTOCOL` | `grpc` | `grpc` or `http/protobuf` |
|
|
524
|
+
|
|
525
|
+
### Runtime control plane
|
|
526
|
+
|
|
527
|
+
| Variable | Default | Description |
|
|
528
|
+
|---|---|---|
|
|
529
|
+
| `OBS_RUNTIME_CONTROL_ENABLED` | `false` | Enable runtime control endpoint |
|
|
530
|
+
| `OBS_RUNTIME_CONTROL_PATH` | `/_observability/control` | Control endpoint path |
|
|
531
|
+
| `OBS_RUNTIME_CONTROL_TOKEN_ENV_VAR` | `OBSERVABILITY_CONTROL_TOKEN` | Name of env var containing bearer token |
|
|
532
|
+
| `OBSERVABILITY_CONTROL_TOKEN` | - | Bearer token value used for auth |
|
|
533
|
+
|
|
534
|
+
### Optional Logtail sink
|
|
535
|
+
|
|
536
|
+
| Variable | Default | Description |
|
|
537
|
+
|---|---|---|
|
|
538
|
+
| `LOGTAIL_ENABLED` | `false` | Enable Better Stack Logtail sink |
|
|
539
|
+
| `LOGTAIL_SOURCE_TOKEN` | - | Logtail source token |
|
|
540
|
+
| `LOGTAIL_BATCH_SIZE` | `50` | Batch size for shipping |
|
|
541
|
+
| `LOGTAIL_FLUSH_INTERVAL` | `2.0` | Flush interval (seconds) |
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
## Advanced Operations
|
|
546
|
+
|
|
547
|
+
### Middleware ordering for body capture
|
|
548
|
+
|
|
549
|
+
If body capture is enabled, install observability before other middleware:
|
|
550
|
+
|
|
551
|
+
```python
|
|
552
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
553
|
+
from fastapiobserver import SecurityPolicy, install_observability
|
|
554
|
+
|
|
555
|
+
install_observability(app, settings, security_policy=SecurityPolicy(log_request_body=True))
|
|
556
|
+
app.add_middleware(CORSMiddleware, allow_origins=["*"])
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Multi-worker Gunicorn with Prometheus
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
export PROMETHEUS_MULTIPROC_DIR=/tmp/prometheus-metrics
|
|
563
|
+
rm -rf "$PROMETHEUS_MULTIPROC_DIR"
|
|
564
|
+
mkdir -p "$PROMETHEUS_MULTIPROC_DIR"
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
`gunicorn.conf.py`:
|
|
568
|
+
|
|
569
|
+
```python
|
|
570
|
+
from fastapiobserver import mark_prometheus_process_dead
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def child_exit(server, worker):
|
|
574
|
+
mark_prometheus_process_dead(worker.pid)
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
## Plugin Hooks
|
|
580
|
+
|
|
581
|
+
Extend behavior without editing package internals:
|
|
582
|
+
|
|
583
|
+
```python
|
|
584
|
+
from fastapiobserver import register_log_enricher, register_metric_hook
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def add_git_sha(payload: dict) -> dict:
|
|
588
|
+
payload["git_sha"] = "abc123"
|
|
589
|
+
return payload
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def track_slow_requests(request, response, duration):
|
|
593
|
+
if duration > 1.0:
|
|
594
|
+
print(f"slow request: {request.url.path} {duration:.2f}s")
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
register_log_enricher("git_sha", add_git_sha)
|
|
598
|
+
register_metric_hook("slow_requests", track_slow_requests)
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
Plugin failures are isolated and do not crash request handling.
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## OTel Test Coverage
|
|
606
|
+
|
|
607
|
+
Repository integration tests include:
|
|
608
|
+
- `tests/test_otel_log_correlation.py`: verifies trace/span IDs in logs map to real spans.
|
|
609
|
+
- `tests/test_otlp_export_integration.py`: validates OTLP HTTP export with local collector fixtures.
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
613
|
+
## Release Tracks
|
|
614
|
+
|
|
615
|
+
- `0.1.x`: secure-by-default core
|
|
616
|
+
- `0.2.x`: OTel interoperability, security presets, allowlists
|
|
617
|
+
- `1.0.0`: dynamic runtime controls and plugin stability
|
|
618
|
+
|
|
619
|
+
Current release version: `0.1.0`
|
|
620
|
+
|
|
621
|
+
## Changelog Policy
|
|
622
|
+
|
|
623
|
+
Breaking changes must be listed under a `Breaking Changes` section in `CHANGELOG.md`.
|
|
624
|
+
|
|
625
|
+
---
|
|
626
|
+
|
|
627
|
+
## Packaging and Publishing (Maintainers)
|
|
628
|
+
|
|
629
|
+
### 1) Build distributions
|
|
630
|
+
|
|
631
|
+
```bash
|
|
632
|
+
python -m pip install --upgrade pip build
|
|
633
|
+
python -m build
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### 2) Upload to TestPyPI
|
|
637
|
+
|
|
638
|
+
```bash
|
|
639
|
+
python -m pip install --upgrade twine
|
|
640
|
+
python -m twine upload --repository testpypi dist/*
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### 3) Validate install from TestPyPI
|
|
644
|
+
|
|
645
|
+
```bash
|
|
646
|
+
python -m pip install \
|
|
647
|
+
--extra-index-url https://test.pypi.org/simple/ \
|
|
648
|
+
fastapi-observer
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
### 4) Upload to production PyPI
|
|
652
|
+
|
|
653
|
+
```bash
|
|
654
|
+
python -m twine upload dist/*
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
---
|
|
658
|
+
|
|
659
|
+
## Local Git Hook (Recommended)
|
|
660
|
+
|
|
661
|
+
```bash
|
|
662
|
+
git config core.hooksPath .githooks
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
The pre-push hook runs:
|
|
666
|
+
- `uv run ruff check`
|
|
667
|
+
- `uv run mypy src`
|
|
668
|
+
- `uv run pytest -q`
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
|
|
672
|
+
## Roadmap Tracking
|
|
673
|
+
|
|
674
|
+
See [NEXT_STEPS.md](NEXT_STEPS.md) for the active `0.2.0` roadmap and release checklist.
|