Metadata-Version: 2.4
Name: fastotel
Version: 0.6.0
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Rust
Classifier: Topic :: System :: Monitoring
Classifier: Typing :: Typed
Requires-Dist: opentelemetry-api>=1.16
Requires-Dist: opentelemetry-sdk>=1.16
License-File: LICENSE
Summary: A Rust-backed drop-in for the OpenTelemetry Python SDK that takes tracing overhead off the request path
Keywords: opentelemetry,otel,otlp,tracing,observability,rust,pyo3
Author: Dmitriy Troyan
License-Expression: MIT
Requires-Python: >=3.11
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Changelog, https://github.com/troyan-dy/fastotel/blob/master/CHANGELOG.md
Project-URL: Homepage, https://github.com/troyan-dy/fastotel
Project-URL: Issues, https://github.com/troyan-dy/fastotel/issues
Project-URL: Repository, https://github.com/troyan-dy/fastotel

# fastotel

[![PyPI](https://img.shields.io/pypi/v/fastotel)](https://pypi.org/project/fastotel/)
[![Python](https://img.shields.io/pypi/pyversions/fastotel)](https://pypi.org/project/fastotel/)
[![CI](https://github.com/troyan-dy/fastotel/actions/workflows/ci.yml/badge.svg)](https://github.com/troyan-dy/fastotel/actions/workflows/ci.yml)
[![License](https://img.shields.io/pypi/l/fastotel)](https://github.com/troyan-dy/fastotel/blob/master/LICENSE)

A Rust-backed drop-in for the OpenTelemetry Python SDK that takes tracing overhead off the request path.

> **Status: pre-alpha.** `OTLPSpanProcessor` sends every field of a span over OTLP/HTTP, encoded as
> `OTLPSpanExporter` encodes it, and batches as `BatchSpanProcessor` does, but with no retries, gzip or
> `OTEL_EXPORTER_OTLP_*` configuration yet, and a process forked after the first span exports nothing from the
> child. The road to 1.0 is in
> [#5](https://github.com/troyan-dy/fastotel/issues/5).

## Why

Instrumentation packages (FastAPI, httpx, SQLAlchemy, aiokafka, …) depend only on `opentelemetry-api`.
fastotel replaces parts of `opentelemetry-sdk` behind that API, so existing instrumentation keeps working.

In the pure-Python SDK, the `BatchSpanProcessor` worker thread encodes OTLP protobuf, gzips and sends spans
while holding the GIL, so it competes for CPU with request handling. In Rust, batching, encoding, compression
and export run on a thread that never takes the GIL.

## Installation

```bash
pip install fastotel
```

Wheels are built for Linux (glibc and musl), macOS and Windows, x86_64 and arm64:

| Python | Wheel | Tested in CI |
| --- | --- | --- |
| CPython 3.11, 3.12, 3.13, 3.14 | one `abi3` wheel per platform, which also covers later versions | yes |
| CPython 3.14t, free-threaded | `cp314t`; the GIL stays off after import | yes |
| CPython 3.15 (pre-release) | the `abi3` wheel | yes |
| CPython 3.15t (pre-release) | none yet: builds from the sdist; a wheel ships with 3.15.0 | yes, from source |

CPython 3.10 reached end of life on 2026-10-01 and is not supported. Free-threaded 3.13t is not supported
either: it was experimental, and PyO3 builds free-threaded extensions from 3.14 on.

## Usage

fastotel replaces `BatchSpanProcessor` with `OTLPSpanExporter`; the SDK keeps creating spans:

```python
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from fastotel import OTLPSpanProcessor

provider = TracerProvider()
provider.add_span_processor(OTLPSpanProcessor(endpoint="http://localhost:4318/v1/traces"))
trace.set_tracer_provider(provider)
```

`endpoint` is the URL spans are posted to, `/v1/traces` included, as for `OTLPSpanExporter`; the one above is the
default. `on_end` copies the span into Rust and returns; a native thread, started by the first span, batches,
encodes and sends spans without taking the GIL. As with `BatchSpanProcessor`, only sampled spans are exported. The
requests decode to what `OTLPSpanExporter` sends for the same spans; where they differ is in
[ADR 0003](https://github.com/troyan-dy/fastotel/blob/master/docs/adr/0003-span-encoding.md).

### Batching

The arguments and variables of `BatchSpanProcessor`, with its defaults, parsing and checks: an argument overrides
its variable, a variable that is not an integer gives the default and an error log on the `fastotel` logger, and a
value out of range raises `ValueError` from the constructor.

| Argument | Variable | Default | What it does |
| --- | --- | --- | --- |
| `max_queue_size` | `OTEL_BSP_MAX_QUEUE_SIZE` | 2048 | spans waiting for export; a span ended while the queue is full is dropped and counted (the count is public with #14), and `on_end` never blocks |
| `max_export_batch_size` | `OTEL_BSP_MAX_EXPORT_BATCH_SIZE` | 512 | a batch leaves as soon as it is full; at most `max_queue_size` |
| `schedule_delay_millis` | `OTEL_BSP_SCHEDULE_DELAY` | 5000 | otherwise, what is queued leaves this long after the previous export |
| `export_timeout_millis` | `OTEL_BSP_EXPORT_TIMEOUT` | 30000 | how long `shutdown()` waits for the last export |

Where this differs from `BatchSpanProcessor` (a full queue drops the newest span rather than the oldest, among
others) is in [ADR 0004](https://github.com/troyan-dy/fastotel/blob/master/docs/adr/0004-batching-and-the-queue.md).

## Development

Needs [uv](https://docs.astral.sh/uv/) and a Rust toolchain ([rustup](https://rustup.rs/)).

```bash
make install   # build the extension, install the dev dependencies
make test      # run the tests, those of the Rust export pipeline (crates/export) too
make lint      # ruff, mypy, cargo fmt, clippy
make bench     # what the stock SDK costs an application, on a GIL and a free-threaded build (~30 min)
```

Every change that reaches `master` is a release: a pull request bumps the version in `pyproject.toml`
(`uv version --bump patch`) and adds its section to `CHANGELOG.md`.

## License

[MIT](https://github.com/troyan-dy/fastotel/blob/master/LICENSE)

