lnpl-otel
OTLP TraceExporter for lnpl's
TraceExporter SPI (linkly#144):
turns a completed workflow Trace into an OTel span tree and ships it to any
OTLP collector (gRPC or HTTP/protobuf), verified against a real
otel-collector in CI via Testcontainers. Registered under the
lnpl.exporters entry-point group as otlp.
Install
pip install lnpl-otel
This also installs a matching lnpl from PyPI. To try unreleased changes,
install from main instead:
pip install git+https://github.com/choiyounggi/lnpl-otel@main.
Usage
Once installed, lnpl discovers this package's exporter automatically
through the lnpl.exporters entry-point:
lnpl serve --trace-exporter otlp
Configuration
Configuration is entirely through the standard OTel SDK environment
variables — --trace-exporter otlp takes no scheme:arg suffix (the
lnpl.exporters SPI is unparameterized, unlike lnpl.caches/lnpl.drivers).
| Variable | Required | Default | Notes |
|---|---|---|---|
OTEL_SERVICE_NAME |
Yes | — | Unset or empty (""/whitespace-only) makes the exporter factory raise lnpl.wsgi.ExporterError at startup — lnpl serve exits with rc 2 rather than launching. A missing service name is a failed launch, not a failed request. |
OTEL_EXPORTER_OTLP_ENDPOINT |
No | SDK default (http://localhost:4317) |
Read natively by the OTel SDK — this package never passes an endpoint explicitly. |
OTEL_EXPORTER_OTLP_PROTOCOL |
No | grpc |
grpc or http/protobuf. Any other value raises ExporterError at startup. |
Every other standard OTEL_EXPORTER_OTLP_* variable (headers, compression,
timeout, TLS certs, …) is honored too, since the SDK's own OTLP exporter
classes read them directly.
What gets exported
See docs/semconv-mapping.md for the full,
line-cited span/attribute mapping. Summary:
- linkly's
interp.Spantree becomes an OTel span tree — rootSpanKind.SERVER,NetworkCallstepsSpanKind.CLIENT, everything elseSpanKind.INTERNAL. span.attrsare promoted under thelnpl.*namespace (lnpl.attempts,lnpl.attr.<key>,lnpl.correlation_id,lnpl.span.kind).- Timestamps are synthesized, not real: linkly's
Spanonly ever records aduration_ms(its clock is monotonic, never epoch), so this exporter invents wall-clock start/end times by anchoring the root span's end totime.time_ns()at export time and stacking children sequentially from there. Treat exported timestamps as approximate shape, not ground truth. trace_dict["metrics"]and["logs"]are not exported — linkly already exposes metrics via its own/metricsPrometheus surface, and this package is trace-only.tracestateis never exported either, but for a different reason: linkly never puts it into_dict()in the first place (vendor-extension/PII risk).- Trace identity is preserved end-to-end: when
trace_dictcarriestrace_id/span_id(an inboundtraceparentlinkly adopted, or one it minted itself), the root OTel span gets exactly those ids — this is what lets a linkly request join a distributed trace instead of starting a new disconnected one. An untrusted inbound context (trace_dict["links"]) becomes an OTelLinkon the root span instead of the trace's own identity.
Dropped spans
OtlpExporter batches through a BatchSpanProcessor
(max_queue_size=2048, schedule_delay_millis=5000,
max_export_batch_size=512, export_timeout_millis=30000 — all overridable
via constructor kwargs, not env, since they are this package's own
operational tuning, not linkly's). If the queue saturates, the SDK drops the
newest span and logs a warning — standard OTel BatchSpanProcessor
behavior, not something this package can prevent. What this package adds is
visibility: OtlpExporter.dropped_spans is a running count of drops,
so an operator can alert on it instead of only noticing gaps after the fact.
Lifecycle
TraceExporter has no shutdown() hook — lnpl serve never calls one. This
package registers atexit.register(provider.shutdown) itself so any
still-batched spans are flushed when the process exits. OtlpExporter.force_flush(timeout_millis=30000)
is also exposed publicly, primarily for tests that need to observe an export
synchronously.
Local testing
Requires Docker (used by Testcontainers to spin up a real otel-collector).
python3.13 -m venv .venv
.venv/bin/pip install -e ".[test]"
OTEL_SERVICE_NAME=test-service TESTCONTAINERS_RYUK_DISABLED=true \
.venv/bin/python -m unittest discover -s tests -v
On macOS with Docker Desktop, Testcontainers' Ryuk reaper container can fail
to start with a socket-mount error. If you hit that, disable Ryuk for the
run (CI's ubuntu-latest runners don't need this) — the command above
already does.
Supported lnpl versions
This package depends on lnpl>=0.9,<0.10 from PyPI, so any lnpl 0.9.x
release works.
The linkly-sync workflow checks two things on every PR:
latest-releasefails when PyPI has a newer lnpl that this range excludes (for example 0.10.0). Then widen the upper bound, run the suite, and release.latest-mainruns the suite against linkly'smain, so a break shows up before linkly releases it.
Releasing
- Set
versioninpyproject.tomland merge that tomain. - Tag the merge commit
v<version>and push the tag. release.ymlruns the suite, builds the sdist and wheel, and publishes them to PyPI through Trusted Publishing. The PyPI publisher is: projectlnpl-otel, ownerchoiyounggi, repositorylnpl-otel, workflowrelease.yml, environmentpypi.
PyPI never accepts the same version twice. If the publish job fails for an
outside reason (PyPI outage, publisher not registered), re-run only that job
with gh run rerun <run-id> --failed. If the cause is in this repo, fix it on
main and release the next patch version. Never delete or re-push a tag.
Metadata
Release files for lnpl-otel 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| lnpl_otel-0.1.0.tar.gz | 16.8 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lnpl_otel-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 27.4 kB
Release files / lnpl_otel-0.1.0.tar.gz
| Download URL | lnpl_otel-0.1.0.tar.gz |
|---|---|
| Size | 16.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9eafc4684e50b75aea697eb0dfdf9bfb8f06659f5d5391a1c7b9908ff972497f
|
|
BLAKE2b-256 checksum How to use checksums |
0b9061868b26ae68d903e6ac77c5e506e2d31f8bb7383559d306f38193c43fc7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 23, 2026.
Transparency logRelease files / lnpl_otel-0.1.0-py3-none-any.whl
| Download URL | lnpl_otel-0.1.0-py3-none-any.whl |
|---|---|
| Size | 10.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ed3bd43afd94a223078b6b1af55e57aa9b2a7cbce0f5270f056ae609a21122a2
|
|
BLAKE2b-256 checksum How to use checksums |
7488a23507c64fea7fff7e6d539355ecea9cbc797983556923b472bbd4db3ff9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 23, 2026.
Transparency logSecurity for lnpl-otel 0.1.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
All 2 files in this release have been uploaded with provenance metadata.
Attestation for lnpl_otel-0.1.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 Oct 23, 2026.
Transparency logA 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.