Skip to main content

natsio-otel

OpenTelemetry adapter over natsio's zero-dependency instrumentation seam. Exports metrics for a natsio connection, plus trace-context propagation helpers for message headers. Distribution natsio-otel, imported as natsio.otel. Pre-1.0, no API-stability promises.

import natsio
from natsio.otel import OtelInstrumentation

nc = await natsio.connect(
    "nats://localhost",
    instrumentation=OtelInstrumentation(),  # meter_provider=None -> global
)

OtelInstrumentation(meter_provider=None, *, record_subject=False) implements the core Instrumentation protocol. It records into the given meter provider (or the global one). Every hook is allocation-light and cannot raise.

Metrics

Instrument names follow the OpenTelemetry messaging semantic conventions where the spec covers the concept; everything else is namespaced nats.client.* and marked custom below. All instruments carry messaging.system=nats.

Instrument Type Unit Source hook Notes
messaging.client.sent.messages Counter {message} on_message_published semconv; messaging.operation.type=send
messaging.client.consumed.messages Counter {message} on_message_delivered semconv; messaging.operation.type=receive
nats.client.sent.bytes Counter By on_message_published custom; application payload bytes (no framing)
nats.client.consumed.bytes Counter By on_message_delivered custom; application payload bytes (no framing)
nats.client.sent.message.size Histogram By on_message_published custom; payload-size distribution
nats.client.consumed.message.size Histogram By on_message_delivered custom; payload-size distribution
nats.client.network.sent.bytes Counter By on_bytes_sent custom; whole-flush wire bytes, includes framing
nats.client.network.received.bytes Counter By on_bytes_received custom; wire bytes, includes framing
nats.client.connects Counter {event} on_connect custom
nats.client.reconnects Counter {event} on_reconnect custom
nats.client.disconnects Counter {event} on_disconnect custom; error.type when the drop carried an error
nats.client.closes Counter {event} on_close custom
nats.client.errors Counter {error} on_error custom; keyed by error.type
nats.client.slow_consumer.drops Counter {message} on_slow_consumer custom; one increment per dropped message

Semantic-convention attribute keys (messaging.system, messaging.destination.name, messaging.operation.type, error.type) are hardcoded strings rather than imported from opentelemetry-semantic-conventions, which is not a runtime dependency.

Subject cardinality (record_subject)

The message subject maps to messaging.destination.name, but NATS subjects — especially _INBOX.* reply subjects generated per request — are effectively unbounded and will explode a metric backend's cardinality. So subjects are not recorded by default. Pass record_subject=True only when your subject space is known to be small and static:

OtelInstrumentation(record_subject=True)

This adds one dict allocation per message on the hot path (the default reuses a shared attribute dict and allocates nothing per message).

Trace-context propagation

from natsio.otel import inject, extract
  • inject(headers=None, *, context=None) -> Headers — returns a fresh natsio.Headers (the caller's mapping is never mutated) with traceparent / tracestate (and any other configured propagators) written in, ready to pass to publish.
  • extract(source, *, context=None) -> Context — pulls a trace Context out of a received Msg (or a Headers/mapping, or None).
from opentelemetry import trace
from opentelemetry.trace import SpanKind

tracer = trace.get_tracer("myapp")

# producer
with tracer.start_as_current_span("send order", kind=SpanKind.PRODUCER):
    await nc.publish("orders", body, headers=inject())

# consumer
async def handler(msg):
    ctx = extract(msg)
    with tracer.start_as_current_span("process order", context=ctx, kind=SpanKind.CONSUMER):
        ...

The spans story (honest version)

This extension ships metrics, not automatic spans. That is a limitation of the instrumentation seam as it stands, not a design choice, and the propagation helpers above are the clean subset that is expressible.

The seam is a set of fire-and-forget point-event hooks (on_message_published(subject, size) -> None, etc.). Real producer/consumer spans need three things the hooks cannot provide:

  1. A place to inject context into an outgoing publish. on_message_published fires after the frame is already encoded and buffered, and receives only (subject, size) — never the headers, which it also could not mutate. There is no publish-wrapping seam, so a traceparent cannot be attached automatically. Header injection must be done by the caller via inject().
  2. A scope around message processing. on_message_delivered fires at parse time, before dispatch and before the user callback runs, and carries only (subject, size) — no headers, no reply. It cannot bracket the handler, so a consumer/process span cannot be started as the handler's parent, nor its duration measured. Extraction must be done by the caller via extract().
  3. Operation timing. The semconv histograms messaging.client.operation.duration and messaging.process.duration need paired start/stop (or an elapsed argument). The hooks are instantaneous and unpaired, so these two core metrics are not implemented here.

See CHANGELOG.md and the task's friction report for concrete proposals to the core (header-carrying delivery hook, publish start/complete pair, a handler-wrapping seam).

Requirements

  • natsio, opentelemetry-api>=1.20
  • Tests additionally use opentelemetry-sdk and a nats-server binary.

Part of the natsio extension tier. If adopted into orbit.py the move is mechanical (natsio/otel/ -> orbit/otel/).

Example

A runnable script is at examples/basic.py — start a server with just server, then:

python extensions/natsio-otel/examples/basic.py

Metadata

Release files for natsio-otel 0.1.1

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

Source distribution (sdist)

Source distribution for natsio-otel 0.1.1
File Size Uploaded Provenance
natsio_otel-0.1.1.tar.gz 8.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for natsio-otel 0.1.1
File Interpreter ABI Platform
natsio_otel-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 16.9 kB

Release files / natsio_otel-0.1.1.tar.gz

Download URL natsio_otel-0.1.1.tar.gz
Size 8.1 kB
Tags Source
SHA-256 checksum
How to use checksums
90d0dac75133d528f4c9304fed779b21491d80855b97ac16b501259bc9827468
BLAKE2b-256 checksum
How to use checksums
fd59dbba1448cf08ab7eeffdfca5666fbe6bee27f5a73f4a135c49260a3b18f4
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 22, 2026.

Transparency log

Release files / natsio_otel-0.1.1-py3-none-any.whl

Download URL natsio_otel-0.1.1-py3-none-any.whl
Size 8.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f11e7a8f8516f968c05251978426b93cc04b5c45af87088461c4795ec7d93bb4
BLAKE2b-256 checksum
How to use checksums
70913770b041004cc116aa3dd434370cf5a180d4c884fd5c9f254c6fa8c456e4
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 22, 2026.

Transparency log

Security for natsio-otel 0.1.1

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.

Select from 2 files

Attestation for natsio_otel-0.1.1.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 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.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

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