Metadata-Version: 2.4
Name: tracebloc-telemetry
Version: 0.1.0
Summary: Contract-conformant telemetry for tracebloc services (RFC-BACKEND-1872 D2)
Author-email: tracebloc <lukas@tracebloc.io>
License-Expression: MIT
Project-URL: Homepage, https://tracebloc.io
Keywords: telemetry,opentelemetry,observability
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: System :: Monitoring
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Provides-Extra: otlp
Requires-Dist: opentelemetry-sdk>=1.24; extra == "otlp"
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.24; extra == "otlp"

# tracebloc-telemetry

Contract-conformant telemetry for tracebloc services.
Implements RFC-BACKEND-1872 D2; the normative rules are
`rfcs/specs/backend-1872-telemetry-contract.md`.

```python
from tracebloc_telemetry import configure, emit

configure(service="backend", component="web")            # once, at startup
emit("api.request.failed", error_type="upstream_timeout")     # per occurrence
```

That is the whole surface, and that is the point of the ticket: a service adopts
telemetry by importing this and nothing else. No exporter construction, no
resource assembly, no field-naming decisions at the call site — those are the
three things every service currently does differently, and the difference is the
defect the epic exists to close.

## Why it raises

The spec marks a dozen rules mechanically checkable. They are checked here, at
emission, and a violation raises. A malformed telemetry record is a programming
error; a warning in a log nobody reads is precisely how the current state was
reached — 28,413 error traces collapsing to 12,254 distinct messages, 4,300+
records with no environment, one service split in half by the case of a
free-text field.

Enforced: the `<domain>.<object>.<outcome>` grammar with its closed vocabularies,
the attribute-key namespace, retired field names, value types, and the error set
a failure record must carry (including the stacktrace — 0.2% carry one today).

## Why the exporter is optional

The OpenTelemetry SDK is an extra (`pip install tracebloc-telemetry[otlp]`).
Without it, every contract rule is still enforced and records go to the standard
library logger.

Two things follow. A service can adopt the contract and test against it before
changing a single runtime dependency — which is what keeps this ticket
independent of the four migrations it unblocks. And `local` and `ci` cannot
export by accident, because they must never reach the hub (spec §3.2).

## What this does NOT do: redaction

It applies none. Values are checked for type and bounded to 2,048 characters;
nothing inspects their content, and a 2,048-character bound does nothing to a
customer cell value, which is short.

This matters because §8.4 *requires* `exception.message` and `exception.stacktrace`
together once either is present — the two fields likeliest to carry a raw value.
backend#1879 found the live version of that (raw customer cell values reaching
central App Insights from prod), and the policy line lives in
`data-ingestors/tracebloc_ingestor/utils/redaction.py`: cell **values** never
appear in errors or logs; column names, file names, counts, dtypes and row
indices may.

Collector-side redaction is backend#1908. Until it lands, keeping customer data
out of an exception message is the **caller's** guarantee, not this package's.

## Where this lives, and where it is going

It lives in `backend/` today and is developed and tested here. It carries its
own `pyproject.toml` so that extraction is a move rather than a rewrite.

**It is not yet consumable by the other three Python services**, which are
separate repositories — that needs a repo of its own and a PyPI publish path
through the release train, and it needs org-admin action to create. Until then
backend#1898 consumes it in-tree and #1899/#1900/#1901 wait. This is a stated
limitation, not an oversight.

## The one thing CI cannot check

The registry in `_registry.py` is a copy of the spec's tables, and the spec is
in a different repository, so nothing in this repo's CI can prove the two still
agree. Keeping them in step is a review rule.

Everything decidable from inside this repo *is* checked: the vocabularies are
closed and internally consistent, every domain/outcome pair forms a legal event
name, and — the one that matters — the environment classification is derived
from the backend's own `_KNOWN_ENVS` dispatcher tuple rather than restated, so
adding an environment there fails this package's tests until someone decides
whether it exports. Vendoring the spec's tables as a generated data file is the
fix for the rest, and is a follow-up.
