021-python-observability 0.3.0__py3-none-any.whl
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.
- 021_python_observability-0.3.0.dist-info/METADATA +348 -0
- 021_python_observability-0.3.0.dist-info/RECORD +11 -0
- 021_python_observability-0.3.0.dist-info/WHEEL +4 -0
- 021_python_observability-0.3.0.dist-info/licenses/LICENSE +202 -0
- observability_021/__init__.py +8 -0
- observability_021/asgi.py +266 -0
- observability_021/config.py +132 -0
- observability_021/dependencies.py +165 -0
- observability_021/logging.py +209 -0
- observability_021/py.typed +0 -0
- observability_021/runtime.py +360 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: 021-python-observability
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: OpenTelemetry metrics, traces and safe JSON logs for FastAPI services
|
|
5
|
+
Project-URL: Repository, https://github.com/from021-io/observability-python
|
|
6
|
+
Project-URL: Changelog, https://github.com/from021-io/observability-python/blob/main/CHANGELOG.md
|
|
7
|
+
Project-URL: Contract, https://github.com/from021-io/observability-python/blob/main/CONTRACT.md
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: asgi,fastapi,observability,opentelemetry,otlp,structlog
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Framework :: FastAPI
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: System :: Logging
|
|
21
|
+
Classifier: Topic :: System :: Monitoring
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Requires-Dist: fastapi<1,>=0.116
|
|
25
|
+
Requires-Dist: httpx<1,>=0.28
|
|
26
|
+
Requires-Dist: opentelemetry-api<2,>=1.44
|
|
27
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.44
|
|
28
|
+
Requires-Dist: opentelemetry-sdk<2,>=1.44
|
|
29
|
+
Requires-Dist: sqlalchemy[asyncio]<3,>=2.0
|
|
30
|
+
Requires-Dist: structlog<27,>=25.1
|
|
31
|
+
Provides-Extra: prometheus
|
|
32
|
+
Requires-Dist: opentelemetry-exporter-prometheus<1,>=0.65b0; extra == 'prometheus'
|
|
33
|
+
Provides-Extra: vercel
|
|
34
|
+
Requires-Dist: vercel<1,>=0.5.8; extra == 'vercel'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# observability-python
|
|
38
|
+
|
|
39
|
+
Repository: `from021-io/observability-python`. Python distribution: `021-python-observability`; import: `observability_021`.
|
|
40
|
+
Tags up to `v0.2.0` use the previous distribution name, `021-observability`.
|
|
41
|
+
|
|
42
|
+
Common API observability for Python 3.12–3.14: FastAPI request metrics and traces,
|
|
43
|
+
JSON stdout logs, explicit HTTPX/SQLAlchemy instrumentation, process signals,
|
|
44
|
+
health/readiness and domain operation metrics. Uses OpenTelemetry over HTTP/protobuf.
|
|
45
|
+
|
|
46
|
+
The package is under development. Consumer migrations are being prepared; live
|
|
47
|
+
Vercel/Grafana verification follows deployment. No paid registry is required to build it.
|
|
48
|
+
|
|
49
|
+
## Versions
|
|
50
|
+
|
|
51
|
+
Every merge to `main` updates a release PR, `chore(release): X.Y.Z`, built by
|
|
52
|
+
release-please from the PR titles (Conventional Commits). Merging it bumps the version,
|
|
53
|
+
writes [CHANGELOG.md](CHANGELOG.md) and tags `vX.Y.Z`. Consumers depend on a tag:
|
|
54
|
+
|
|
55
|
+
```toml
|
|
56
|
+
[tool.uv.sources]
|
|
57
|
+
021-python-observability = { git = "https://github.com/from021-io/observability-python.git", tag = "v0.3.0" } # x-release-please-version
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Once releases are published to PyPI, pin the exact version instead; no Git credential is
|
|
61
|
+
needed to install it:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
uv add "021-python-observability==0.3.0" # x-release-please-version
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Publishing to PyPI
|
|
68
|
+
|
|
69
|
+
`.github/workflows/release.yml` publishes a release with PyPI Trusted Publishing (OIDC, no
|
|
70
|
+
API token). It stays off until a maintainer sets the repository variable `PYPI_PUBLISH` to
|
|
71
|
+
`true`; then every release tag is built, checked with `twine check --strict`, smoke-tested
|
|
72
|
+
and uploaded through the `pypi` environment. A manual run of the workflow publishes an
|
|
73
|
+
existing tag, to TestPyPI (`testpypi` environment) by default. The build refuses a tag
|
|
74
|
+
without a `LICENSE` file or whose `pyproject.toml` version differs from the tag.
|
|
75
|
+
|
|
76
|
+
## Install locally
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
uv sync --all-extras
|
|
80
|
+
uv build
|
|
81
|
+
# In a consumer's environment, use the actual wheel path:
|
|
82
|
+
python -m pip install /path/to/021_python_observability-0.2.0-py3-none-any.whl
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The `vercel` extra supplies the Vercel Python SDK. Install that extra in Vercel consumers;
|
|
86
|
+
normal Python servers do not need it. Selecting Git, a package index or a wheel URL later
|
|
87
|
+
does not change this integration API.
|
|
88
|
+
|
|
89
|
+
## Configure the application once
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from fastapi import FastAPI
|
|
93
|
+
from observability_021 import setup_observability
|
|
94
|
+
|
|
95
|
+
app = FastAPI() # Existing routers, exception handlers, auth and lifespan remain here.
|
|
96
|
+
observability = setup_observability(app, service_name="021-api")
|
|
97
|
+
|
|
98
|
+
@app.get("/items/{item_id}")
|
|
99
|
+
async def get_item(item_id: str):
|
|
100
|
+
return {"id": item_id}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Call setup while constructing the app, before serving requests. The same app object is
|
|
104
|
+
returned to the server: Vercel still sees a FastAPI application. Setup returns the shared
|
|
105
|
+
observability object, also available at `app.state.observability`. Repeated setup on the
|
|
106
|
+
same app returns that object without stacking providers, middleware or log handlers.
|
|
107
|
+
|
|
108
|
+
The integration wraps FastAPI's fully built ASGI middleware stack, including its error
|
|
109
|
+
handler. It observes the response actually sent, including early auth/rate-limit rejections
|
|
110
|
+
and transformed errors. It does not decorate individual handlers. Existing lifespan
|
|
111
|
+
startup/state/shutdown is preserved; providers flush and stop after application teardown.
|
|
112
|
+
|
|
113
|
+
## Where telemetry goes
|
|
114
|
+
|
|
115
|
+
Example deployment variables (URLs and credentials below are placeholders):
|
|
116
|
+
|
|
117
|
+
```dotenv
|
|
118
|
+
OTEL_SERVICE_NAME=021-api
|
|
119
|
+
OTEL_DEPLOYMENT_ENVIRONMENT=staging
|
|
120
|
+
OTEL_SERVICE_VERSION=0.1.0
|
|
121
|
+
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
|
|
122
|
+
OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.example.com/otlp
|
|
123
|
+
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Basic%20REPLACE_WITH_CREDENTIAL
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Use the **ingestion endpoint** of the OTLP gateway/collector, not the Grafana dashboard URL.
|
|
127
|
+
With that base, traces go to `/otlp/v1/traces` and metrics to `/otlp/v1/metrics`.
|
|
128
|
+
The header value follows the OTLP comma-separated `key=value` format with percent encoding.
|
|
129
|
+
Put real credentials in deployment secrets, never in the source repository or dependency URL.
|
|
130
|
+
|
|
131
|
+
Both exporters serialize binary protobuf and default to
|
|
132
|
+
`Content-Type: application/x-protobuf`. Custom headers can override that header;
|
|
133
|
+
keep its default value. A different Content-Type does not change the encoding.
|
|
134
|
+
Use custom headers for gateway authentication and verify receipt of both signals
|
|
135
|
+
against the configured gateway during rollout.
|
|
136
|
+
|
|
137
|
+
Separate destinations are supported and take precedence over the shared base:
|
|
138
|
+
|
|
139
|
+
```dotenv
|
|
140
|
+
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=https://metrics.example.com/v1/metrics
|
|
141
|
+
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://traces.example.com/v1/traces
|
|
142
|
+
OTEL_EXPORTER_OTLP_METRICS_HEADERS=Authorization=Bearer%20METRICS_CREDENTIAL
|
|
143
|
+
OTEL_EXPORTER_OTLP_TRACES_HEADERS=Authorization=Bearer%20TRACES_CREDENTIAL
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Signal-specific endpoints are used **exactly as provided**, including their paths.
|
|
147
|
+
Omit all endpoints for local development without remote export. Logs remain JSON on stdout
|
|
148
|
+
in every environment, so Vercel collects them; the package does not export logs over OTLP.
|
|
149
|
+
|
|
150
|
+
| Setting | Default / meaning |
|
|
151
|
+
| --- | --- |
|
|
152
|
+
| `OTEL_SERVICE_NAME` | Required unless supplied as `service_name=` |
|
|
153
|
+
| `OTEL_DEPLOYMENT_ENVIRONMENT` | Falls back to `VERCEL_ENV`, then `local`; set `staging` explicitly for staging deployments |
|
|
154
|
+
| `OTEL_SERVICE_VERSION` | Falls back to `VERCEL_GIT_COMMIT_SHA`, then `unknown` |
|
|
155
|
+
| `OTEL_EXPORTER_OTLP_TIMEOUT` | 2 seconds; Python exporter timeout and asynchronous flush wait budget |
|
|
156
|
+
| `OTEL_METRIC_EXPORT_INTERVAL` | 60000 milliseconds in persistent processes |
|
|
157
|
+
| `OBS_TRACE_SAMPLE_RATIO` | 1.0; parent-based sampling, metrics are not sampled |
|
|
158
|
+
| `LOG_LEVEL` | INFO |
|
|
159
|
+
| `VERCEL=1` | Enables the Python SDK `wait_until` continuation |
|
|
160
|
+
|
|
161
|
+
A service instance gets a unique `service.instance.id`. Code can alternatively pass an
|
|
162
|
+
explicit `Settings` object; that object does not implicitly load environment variables.
|
|
163
|
+
|
|
164
|
+
In persistent processes, SDK batching/periodic export and normal shutdown deliver signals.
|
|
165
|
+
On Vercel, the request span closes before a post-response `wait_until` flush is registered.
|
|
166
|
+
Flushes run off the event loop; failures produce sanitized diagnostics and do not change
|
|
167
|
+
business responses. The async wait budget does not kill Python worker threads: the HTTP
|
|
168
|
+
exporter also has its own transport timeout. Forced process termination can still lose
|
|
169
|
+
telemetry. Live delivery on Vercel remains a deployment validation, not a unit-test claim.
|
|
170
|
+
|
|
171
|
+
## Dependencies and business operations
|
|
172
|
+
|
|
173
|
+
HTTPX clients opt in at construction, using a fixed dependency name. Use the sync equivalent
|
|
174
|
+
`observability.httpx_transport` for a synchronous `httpx.Client`.
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
import httpx
|
|
178
|
+
|
|
179
|
+
client = httpx.AsyncClient(
|
|
180
|
+
transport=observability.async_httpx_transport(dependency="company-brain"),
|
|
181
|
+
base_url=brain_url,
|
|
182
|
+
)
|
|
183
|
+
# Close the client using the service's existing lifespan.
|
|
184
|
+
observability.instrument_sqlalchemy(engine, dependency="database")
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Pass an existing HTTPX transport through `transport=` if it carries custom TLS, proxy,
|
|
188
|
+
retry or test behavior. Explicit per-host mounts also need their own observed transport.
|
|
189
|
+
SQLAlchemy supports both Engine and AsyncEngine. Neither adapter records URLs, hostnames,
|
|
190
|
+
SQL statements, SQL parameters, exception messages or response bodies. Client spans inject
|
|
191
|
+
W3C trace context; HTTP requests also propagate the validated request ID.
|
|
192
|
+
|
|
193
|
+
Readiness checks remain owned by each service:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from sqlalchemy import text
|
|
197
|
+
|
|
198
|
+
async def database_ready():
|
|
199
|
+
async with engine.connect() as connection:
|
|
200
|
+
await connection.execute(text("SELECT 1"))
|
|
201
|
+
|
|
202
|
+
observability.add_health_routes(app, readiness={"database": database_ready})
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
This explicitly registers `/health` (process alive) and `/ready` (all declared checks
|
|
206
|
+
succeed). Checks must be async and cancellation-cooperative; False means unavailable,
|
|
207
|
+
None/other successful returns mean available. A failure or timeout returns sanitized 503.
|
|
208
|
+
Existing routes are never silently replaced. Migrate monitors and auth exemptions first.
|
|
209
|
+
With no declared checks, readiness represents the process only.
|
|
210
|
+
The JSON contract is `status: ok|unavailable` plus `checks`, not `status: ready`;
|
|
211
|
+
see the exact [probe contract](CONTRACT.md#probes-and-correlation).
|
|
212
|
+
|
|
213
|
+
Generic domain instrumentation is explicit and works around synchronous code or `await`:
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
with observability.operation("brain.ingest"):
|
|
217
|
+
await ingestion_service.run()
|
|
218
|
+
|
|
219
|
+
observability.increment("app.documents.count", operation="ingest", status="ok")
|
|
220
|
+
observability.observe("app.ingest.duration", 12.5, operation="ingest", status="ok")
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Use static metric/operation/dependency names from code, never IDs or user-provided text.
|
|
224
|
+
Operation/result remain separate dimensions. `operation()` reports `ok`, `error` or `cancelled`; explicit helpers also preserve static domain outcomes.
|
|
225
|
+
|
|
226
|
+
## Logging
|
|
227
|
+
|
|
228
|
+
`get_logger(__name__)` provides structlog logging. Use static event names and approved
|
|
229
|
+
structured fields (`operation`, `op`, `action`, `dependency`, `status`, `reason`, `error_type`). Do not
|
|
230
|
+
interpolate messages or pass business payloads into approved fields. Unknown extra fields
|
|
231
|
+
and raw exception objects/messages are not serialized. Foreign stdlib messages are replaced
|
|
232
|
+
with a safe event and logger name; exporter failures get `telemetry.export_failed`.
|
|
233
|
+
|
|
234
|
+
Logs also retain UUID correlation fields: `brain_id`, `job_id`, `file_id`,
|
|
235
|
+
`file_version_id`, `entity_id`, `event_id` and `correlation_id`. Pass a UUID object or
|
|
236
|
+
UUID string; valid values are normalized and included in JSON and searchable `message`.
|
|
237
|
+
Invalid values and unknown fields are omitted. These fields are **never metric labels**.
|
|
238
|
+
|
|
239
|
+
Use `logger.exception("ingestion.failed", brain_id=brain_id, job_id=job_id)` inside an
|
|
240
|
+
exception handler, or `logger.error(..., exc_info=error)` for a captured exception.
|
|
241
|
+
Stdlib `logging.exception` / `logging.error(..., exc_info=True, extra={...})` and
|
|
242
|
+
Uvicorn errors use the same formatter. The actual exception supplies `error_type` and
|
|
243
|
+
an `exception` array, ordered from the current error through its cause/context chain:
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{"type":"ValueError","frames":[{"file":"worker.py","function":"ingest","line":42}],"frames_truncated":false}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Each entry contains only the type and frame locations, never exception messages,
|
|
250
|
+
source lines, locals or absolute directories. At most 8 chained exceptions and the
|
|
251
|
+
32 frames nearest each failure are retained; `exception_truncated` and each entry's
|
|
252
|
+
`frames_truncated` flag indicate omitted diagnostics. Explicit `raise ... from None`
|
|
253
|
+
suppresses the previous context. Exception-group members are not expanded; their group
|
|
254
|
+
type and its own frames are retained. Type/function names are capped at 128 characters
|
|
255
|
+
and filenames at 256. Diagnostic fields are generated from the exception, not accepted
|
|
256
|
+
as caller-supplied JSON. Unhandled ASGI failures include these diagnostics in the single
|
|
257
|
+
`request.completed` log; response codes, exception propagation and span data are unchanged.
|
|
258
|
+
|
|
259
|
+
Each HTTP request emits one `request.completed` line with service identity, a validated
|
|
260
|
+
UUID request ID, trace/span IDs, method, route template, status and duration. Invalid inbound
|
|
261
|
+
request IDs are replaced. IDs appear in logs, not metric labels. Setup owns root logging
|
|
262
|
+
configuration: remove the consumer's previous formatter/configuration during migration.
|
|
263
|
+
|
|
264
|
+
## Contract and checks
|
|
265
|
+
|
|
266
|
+
See [CONTRACT.md](CONTRACT.md) for signal names, units, buckets and measurement boundaries.
|
|
267
|
+
|
|
268
|
+
```sh
|
|
269
|
+
make check
|
|
270
|
+
uv build
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Tests exercise actual ASGI responses, a local OTLP HTTP/protobuf receiver, a distributed
|
|
274
|
+
API-to-API call, SQLAlchemy sync/async operations, exporter failures and package installation.
|
|
275
|
+
No test needs Grafana credentials or calls a deployed service.
|
|
276
|
+
|
|
277
|
+
## Existing domain ports and scrape endpoints
|
|
278
|
+
|
|
279
|
+
For consumers retaining `/metrics`, install `[prometheus]`, pass `prometheus=True` at setup
|
|
280
|
+
and return `obs.prometheus_latest()` from the endpoint. Metrics are read from the same OTel
|
|
281
|
+
provider used by OTLP. Existing dashboards still need the explicit name/label migration in
|
|
282
|
+
CONTRACT.md; the reader does not recreate legacy instrument names.
|
|
283
|
+
|
|
284
|
+
Domain helpers accept static `attributes` from the documented allowlist. Scalar histograms
|
|
285
|
+
can use `unit="1"` (or `"USD"` for already-existing cost observations); duration histograms
|
|
286
|
+
stay in seconds. This preserves consumer domain ports without reimplementing exporters or
|
|
287
|
+
instrument caches.
|
|
288
|
+
|
|
289
|
+
## Grafana synchronization tool
|
|
290
|
+
|
|
291
|
+
[`tools/grafana/sync.py`](tools/grafana/sync.py) is a standalone Python 3.12–3.14
|
|
292
|
+
CLI using only the standard library. Consumers check it out at a release tag
|
|
293
|
+
(`ref: vX.Y.Z`), the same tag their runtime dependency pins. The package does
|
|
294
|
+
not import or run it. Services own dashboard JSON, queries, inventories, secrets
|
|
295
|
+
and workflow triggers; this repository owns the synchronization mechanism and tests.
|
|
296
|
+
|
|
297
|
+
Pass `--manifest /path/to/service/grafana/dashboards.json`: a nonempty JSON array
|
|
298
|
+
of paths relative to that manifest, for example `["overview.json", "usage/funnel.json"]`.
|
|
299
|
+
Paths must stay inside its directory, refer to JSON files and have unique dashboard
|
|
300
|
+
UIDs. No automatic file discovery or remote dashboard deletion is performed.
|
|
301
|
+
|
|
302
|
+
```sh
|
|
303
|
+
python3 tools/grafana/sync.py --manifest /path/to/service/grafana/dashboards.json --validate
|
|
304
|
+
python3 -m unittest discover -s tools/grafana -p 'test_sync.py' -v
|
|
305
|
+
# With the service's Actions configuration, authenticated GETs only:
|
|
306
|
+
python3 tools/grafana/sync.py --manifest /path/to/service/grafana/dashboards.json --dry-run
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Publication requires `GRAFANA_URL`, `GRAFANA_SERVICE_ACCOUNT_TOKEN` and
|
|
310
|
+
`GRAFANA_COMMIT` (the **consumer** checkout SHA). Grafana Cloud additionally needs
|
|
311
|
+
`GRAFANA_NAMESPACE=stacks-<instance ID>`; OSS defaults to `default`.
|
|
312
|
+
`GRAFANA_FOLDER_UID` is required for creation; existing folders are preserved.
|
|
313
|
+
`GRAFANA_DATASOURCES` is an optional JSON mapping of variable names to datasource
|
|
314
|
+
UIDs, required when a new datasource variable has no saved selection.
|
|
315
|
+
Keep these values in consumer deployment configuration. The tool preserves saved
|
|
316
|
+
variable selections, refresh and time range; preflights the entire inventory before
|
|
317
|
+
writing; rejects redirects; sanitizes errors; and verifies every write by reading it back.
|
|
318
|
+
`--dry-run` reports `preflight_verified` and never checks write permission or panel queries.
|
|
319
|
+
|
|
320
|
+
The existing `make check` and package CI matrix run the common tests. Consumer
|
|
321
|
+
workflows validate their own manifests and fetch the same reviewed tool revision using
|
|
322
|
+
their existing read-only `OBSERVABILITY_GITHUB_TOKEN`, with `persist-credentials: false`.
|
|
323
|
+
Move the CI tool pin to a new tag through a PR after its checks pass. A tool-only
|
|
324
|
+
release does not require moving the runtime dependency, but keeping both on the same
|
|
325
|
+
tag is the default.
|
|
326
|
+
|
|
327
|
+
## Consumer rollout checklist
|
|
328
|
+
|
|
329
|
+
- Moving from `021-observability` to `021-python-observability`: rename the dependency
|
|
330
|
+
(with its extras) in `[project] dependencies`, the `[tool.uv.sources]` key or the
|
|
331
|
+
`requirements.txt` line, and regenerate the lock. Imports (`observability_021`) and every
|
|
332
|
+
signal name stay the same.
|
|
333
|
+
- Merge the release PR that release-please opens (`chore(release): X.Y.Z`), then pin both
|
|
334
|
+
consumers to its tag `vX.Y.Z` (`tag = "vX.Y.Z"` in `[tool.uv.sources]`, `ref: vX.Y.Z` for
|
|
335
|
+
the Grafana tool); regenerate API lock/export and rerun both consumer gates.
|
|
336
|
+
- Update readiness JSON assertions and dependency monitors together. In Brain, old
|
|
337
|
+
dependency checks moved from `/health` to `/ready`; liveness alone cannot prove readiness.
|
|
338
|
+
- Migrate existing metric queries, labels, duration units and alert thresholds together;
|
|
339
|
+
if none exist, record that inventory explicitly before rollout.
|
|
340
|
+
- Replace log searches by email/raw path with the response's UUID `X-Request-ID` and
|
|
341
|
+
`trace_id`. Confirm callers accept a replacement ID when they send a non-UUID value.
|
|
342
|
+
- Retain the `vercel` extra in Vercel consumers: `wait_until` keeps export work running
|
|
343
|
+
after the response. While consuming from Git, configure the package PAT separately for
|
|
344
|
+
each deployment environment; a PyPI pin needs none.
|
|
345
|
+
|
|
346
|
+
## License
|
|
347
|
+
|
|
348
|
+
Apache License 2.0. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
observability_021/__init__.py,sha256=0RU4GAtHbN-dyK8tqvEJ9t9e5rxJnRLCUoFN1q7GkuE,283
|
|
2
|
+
observability_021/asgi.py,sha256=2cpHSdmrNindHIY_dYdMwhBXGhefJlCL8VfSEkf4Zb4,10212
|
|
3
|
+
observability_021/config.py,sha256=4LR6nNNdD2-2pso-Z4NRbLwsgiXKa8T4D-8X1bbAcNg,5232
|
|
4
|
+
observability_021/dependencies.py,sha256=4PdfFmLHmIBN3IIMHG41d2ify5H7At9wrCkpZvByZRs,6065
|
|
5
|
+
observability_021/logging.py,sha256=OjJIRKmJJ4-RGafyvHaYKUjKInjscOIHhn5Ficx70Oc,6799
|
|
6
|
+
observability_021/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
observability_021/runtime.py,sha256=DfaQPn5ZbAjAaJ-IYu_RCijnxOqBMDPEgssrdvwTTGc,14547
|
|
8
|
+
021_python_observability-0.3.0.dist-info/METADATA,sha256=BzXNnBB6so7eNlsGsH30nW6AiOJRXrYnc-XxTck5xLE,17743
|
|
9
|
+
021_python_observability-0.3.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
10
|
+
021_python_observability-0.3.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
11
|
+
021_python_observability-0.3.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""021 API observability: configure once, keep handlers unchanged."""
|
|
2
|
+
|
|
3
|
+
from .asgi import setup_observability
|
|
4
|
+
from .config import Settings
|
|
5
|
+
from .logging import get_logger
|
|
6
|
+
from .runtime import Observability
|
|
7
|
+
|
|
8
|
+
__all__ = ["Observability", "Settings", "get_logger", "setup_observability"]
|