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.
@@ -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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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"]