Skip to main content

edutap.observability_settings

Error reporting, tracing and structured logging, wired the same way in every eduTAP service — and one decision written down: what a service may say about a person.

Why this exists

Because the options are the point, not the code. Three lines would start Sentry. Which options those three lines carry decides whether a bearer token or a person's identifier leaves the process, and each of the options below was chosen against a measurement rather than against a backend's recommendation. Separated from the sentry_sdk.init() call that applies them, those measurements are worth nothing — which is why this is a package and not a paragraph in a README.

Because a person's identifier is not an opaque handle. At a university a person_uid resolves to a human being for far more people than hold directory administration rights; at the LMU it is the LMU identifier with @lmu.de appended. Pseudonymised it remains personal data. What a deployment decides here is not whether it is personal data but who may see it.

Which artefact is running

Set EDUTAP_RELEASE (or the per-service prefix a consumer uses) to the image tag of what is deployed:

EDUTAP_RELEASE: "2026-08-28_1859"

It reaches Sentry as release, and an error tracker needs it to answer the one question that follows every fix: is this still happening in what we shipped? Bugsink builds "resolved in the next release" on the same field.

Unset, the service_version passed to install_observability is used instead -- better than nothing, and honest about what it is: for a service that is typically a placeholder like 1.0.0.dev0 which stays the same across a hundred deployments.

Transport chatter is not a defect

Kafka clients log at ERROR for every failed connection attempt while a broker is unreachable -- and they retry, and they recover. Sentry's LoggingIntegration turns each of those records into an event. install_observability therefore silences the loggers listed in NOISY_LOGGERS for the tracker.

The trade-off is deliberate: a genuinely permanent broker outage produces no event in the tracker either. It produces log lines, it produces metrics, and the consumer's own failures report as before. An error tracker is for defects; an outage is for monitoring, and mixing the two is how a tracker becomes unread.

Usage

from edutap.observability_settings import install_observability, person_label

install_observability(service_name="lmu_edutap_worker")   # first thing in main()

log.warning("no view for person", person=person_label(uid, settings))

Call it before the service resolves the settings it needs to run, so that a process refusing to start is reported rather than silently absent. Nothing here can fail for want of a value.

FastAPI services

from edutap.observability_settings import install_observability, instrument_fastapi_safely

install_observability(service_name="edutap.image_service")
instrument_fastapi_safely(app)

Needs the fastapi extra. It exists because logfire.instrument_fastapi() exports the request path: a service whose identifier sits in its URL exports that identifier on every span, whatever person_uid_mode says. In omit and pseudonym the path attributes are replaced by the route template and captured endpoint arguments are dropped; in plain the call is a straight pass-through, because a deployment that asked to see identifiers gets to see them.

Configuration

One prefix for the whole estate, EDUTAP_. These fields are defined by an eduTAP package, and another university deploying them should not have to learn an LMU name.

Variable Default Meaning
EDUTAP_ENVIRONMENT production Labels every event and every span. Unset must not masquerade as development.
EDUTAP_TELEMETRY_ENABLED true The deliberate off switch for tracing, metrics and log export.
EDUTAP_LOG_LEVEL INFO A closed set; a misspelled level fails at startup.
EDUTAP_SENTRY_DSN unset Unset means no error tracker. One project per service.
EDUTAP_PSEUDONYM_SALT unset The HMAC key behind the person pseudonym. Without it there is no pseudonym at all.
EDUTAP_PERSON_UID_MODE pseudonym pseudonym · plain · omit
EDUTAP_EXPORT_LOG_RECORDS true Each structlog event also leaves as an OTel log record. Effective only with telemetry on and an endpoint set.
OTEL_EXPORTER_OTLP_ENDPOINT unset Not an EDUTAP_ field — see below.

Why the endpoint is not one of ours

A Sentry DSN names a project, so it belongs to the service and every service gets its own. An OTLP endpoint names a receiver, normally one per host or cluster, and which service sent a span rides in the resource attributes rather than in the address. Every OpenTelemetry SDK already reads OTEL_EXPORTER_OTLP_ENDPOINT by itself; a field under EDUTAP_ would be a second name for the same value.

A shared prefix means shared *names*, not shared *values*. `EDUTAP_SENTRY_DSN` differs
per service by being set in that service's own compose `environment:` block. Only
genuinely stack-wide values belong in the shared `.env`.

The three modes for a person

Mode What travels When it is right
pseudonym a keyed, 12-character label The default. Correlation survives — forty errors about one person still read as one person — identification does not.
plain the person_uid itself Where the error tracker is read by exactly the people who may read the directory anyway. A configured decision, not an accident.
omit nothing Where the tracker is read more widely than the directory.

pseudonym without EDUTAP_PSEUDONYM_SALT yields nothing, never the raw value: a deployment that asked for pseudonyms and forgot the key has to lose the datum rather than publish it. An empty salt counts as no salt — compose writes ${VAR:-}, which sets a variable to the empty string, and an HMAC under an empty key is a plain digest of a small, enumerable value space and reversible by anyone who can hash the directory.

What the three backends do

Sentry takes errors. An OTLP collector takes traces, metrics and log records. structlog produces the events that reach both, and two bridges carry them to the collector:

  • A log record per event, on the logs signal. This is what a log backend receives, with service name, environment, severity and the trace id of the span the event was written in. The body is the event as a string; every other field is an attribute, and anything that is not a scalar arrives as JSON. An exception arrives as exception.type, exception.message and exception.stacktrace.
  • A zero-duration span per event, on the traces signal, made by logfire.StructlogProcessor. It keeps a trace showing what was logged inside it. Despite its name, this bridge does not produce log records.

Log records are only made with telemetry on and OTEL_EXPORTER_OTLP_ENDPOINT set. Every line still goes to stdout as JSON, so docker service logs keeps working. A deployment that also ships container logs to the same backend holds each line twice, distinguished by service_name; EDUTAP_EXPORT_LOG_RECORDS=false turns the records off where that is not wanted.

The records leave only because logfire attaches an OTLP log exporter to the logger
provider it registers. That is logfire's internal wiring, not a documented contract.
`tests/test_log_records.py` sends real OTLP to a loopback receiver, so a logfire
upgrade that changes it fails the suite instead of losing the logs silently.

Nothing travels two paths: Sentry's own tracing stays off (traces_sample_rate=0), because the spans already go to the collector and Bugsink — the tracker this estate runs — states that it does not support traces.

`send_to_logfire=False` is not a detail. The library defaults it to `True`, so leaving
it unset would ship spans to a hosted third party the first time a token happened to
be present.

While no collector exists, the console stands in. Measured against logfire 4.40: with send_to_logfire=False and no OTEL_EXPORTER_OTLP_ENDPOINT, no exporter is installed at all, so an instrumented service would be indistinguishable from an uninstrumented one — which is how instrumentation reaches production broken. Once the endpoint is set, the console stands down.

Development

make venv
make lint
make test-local

tox runs the suite across every supported Python version.

Design records

Under docs/superpowers/specs/.

Metadata

Release files for edutap.observability-settings 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for edutap.observability-settings 0.2.0
File Size Uploaded Provenance Actions
edutap_observability_settings-0.2.0.tar.gz 50.0 kB With provenance Details for edutap_observability_settings-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for edutap.observability-settings 0.2.0
File Interpreter ABI Platform Actions
edutap_observability_settings-0.2.0-py3-none-any.whl Python 3 none any Details for edutap_observability_settings-0.2.0-py3-none-any.whl

Total release size: 72.4 kB

Release files / edutap_observability_settings-0.2.0.tar.gz

Download URL edutap_observability_settings-0.2.0.tar.gz
Size 50.0 kB
Tags Source
SHA-256 checksum
How to use checksums
e4b6c459a7207dcc7abaf2636bec836163269b77fbfa8d161477cbab6055347d
BLAKE2b-256 checksum
How to use checksums
5f03ad1cb06f1bf86ad905e86e765dd14fd126f5db50cb0987dcc9466dd7a465
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release files / edutap_observability_settings-0.2.0-py3-none-any.whl

Download URL edutap_observability_settings-0.2.0-py3-none-any.whl
Size 22.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c4687abf0b1a90c7554e3dc5c59e0e6a422b71982778d83838b5f121c79b29bf
BLAKE2b-256 checksum
How to use checksums
9c6a12ab2d3c2f323ee3c7e423618d0dd2c7c80275ab82941d1d0ae7db8e3d26
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Security for edutap.observability-settings 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for edutap_observability_settings-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page