# ddtrace: the Python client behind Datadog's APM stack

> ddtrace is the Python client library that powers Datadog's distributed tracing, continuous profiling, error tracking, test optimisation, deployment tracking, code hotspots and dynamic instrumentation. It is a hybrid Python, Rust and C extension project that pins the CPython versions it supports.

**DataDog/dd-trace-py** — Datadog Python APM Client

- Repository: https://github.com/DataDog/dd-trace-py
- Website: https://ddtrace.readthedocs.io/
- Stars: 654 · Forks: 575
- Language: Python
- License: NOASSERTION
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/datadog-dd-trace-py

## One client, eight Datadog products

ddtrace is the Python side of the Datadog observability stack, and the README lists what it powers. Distributed tracing is first, then continuous profiling, error tracking, test optimisation, deployment tracking, code hotspots, which is the feature that links a profile back to the traces that ran while it was captured, and dynamic instrumentation, which is the mechanism for adding instrumentation to a running process without shipping a new build. The library is published on PyPI as `ddtrace`, the documentation is hosted at ddtrace.readthedocs.io, and the primary language is Python. The project is developed by Datadog, which is also the commercial interest behind the service the data goes to, and project metadata lists the licence as a custom one that GitHub cannot classify, with the repository carrying a LICENSE file alongside LICENSE.Apache and LICENSE.BSD3, so the licensing of the client and the licensing of the bundled components are separated.

## Why the build system is a Rust and Cython project

The build configuration in pyproject.toml is where the engineering reality shows. The build backend is setuptools, but the build requirements are `cython`, `cmake` at 3.24.2 or newer and below 3.28, `setuptools-rust` below 2, and `patchelf` on Linux, and the version of Cython is conditional on the interpreter, pinned below 3.3 for Python 3.15 and later with a comment explaining that Cython 3.3 manylinux wheels crash on the current 3.15 interpreter. A Rust extension is built alongside the Python sources, which is what lets the same package ship native components for the profiler and the tracer without a separate binary distribution. The comment in the metadata is candid about why the version ceiling exists, since the project has many sub-components that can break when CPython internals change over versions, with profiling named as the example, and the upper bound is explicit in `requires-python = ">=3.9,<3.15"` with a note to raise it once Python 3.15 support is complete.

## The versions you can actually run it on

The supported interpreter list is worth reading closely, because it tells you what the project considers stable. The classifiers list Python 3.9 through 3.14, and the package metadata marks the development status as Production/Stable with the implementation as CPython only and the Python 3 only classifier. The dependency on the `bytecode` library is versioned per interpreter, with a different lower bound for each Python version from 3.11 to 3.15, which is a direct consequence of the same internal churn, since the bytecode reader has to match what the interpreter emits. The project also keeps a `min_compatible_versions.csv` and a `requirements.csv` in the repository root, and files named `supported_versions.json` and `supported-configurations.json`, which together are the machine readable form of the support matrix. The practical consequence for an adopter is simple: the tracer is pinned to a range of interpreters and will tell you when you are outside it, which is very different from a tracer that silently produces broken spans.

## How the package is assembled, and why the guard rail exists

The setup.py in this repository is not a short file, and the reason is visible in its opening. It imports a long list of standard library modules, and then it tries to import Cython inside a try block, with a comment that order matters and that the import must come after setuptools or it will fail. The error it raises when Cython cannot be imported is unusually informative:

```python
    raise ImportError(
        "Failed to import Cython modules. This can happen under versions of pip older than 18 that don't "
        "support installing build requirements during setup. If you're using pip, make sure it's a "
        "version >=18.\nSee the quickstart documentation for more information:\n"
        "https://ddtrace.readthedocs.io/en/stable/installation_quickstart.html"
    )
```

That message tells you two things worth knowing. The first is that a source install of ddtrace is a compiler toolchain event, not a pip event, since Cython, CMake, a Rust compiler and patchelf all have to be present. The second is that the error path leads to the quickstart documentation, which is the right place to look when a build fails. The rest of setup.py is the machinery for building the Rust extension, driving CMake and assembling the wheel, with a dataclass and a set of module level constants for the build modes.

## The test matrix runs against more databases than you would expect

The docker-compose files in the repository show what the project's own test suite depends on, and the list is a good proxy for what the integrations cover. The base file is extended by the main compose file, and the services are Elasticsearch on port 9200, OpenSearch on 9201 mapped to 9200 in the container, Cassandra on 9042, Consul on 8500, Postgres on 5432, MariaDB on 3306, MySQL on 3306, Redis on 6379, Kafka on 29092, and a Redis cluster. That set is not a testing convenience, it is the surface area of the library, since each of those is a service the tracer or the profiler might interact with or that the project's integration tests exercise. There are separate compose files for GPU and for podman, and a `docker/` directory, and the CI setup has both a `.gitlab-ci.yml` and a `.gitlab/` directory alongside a `ci/` directory, so the project runs on more than one CI system.

## The repository as a map of the product

The top level of the repository tells you how much is in here. There is a `ddtrace/` directory, a `src/` directory for the native sources, a `lib-injection/` directory which is the mechanism behind dynamic instrumentation, a `hooks/` directory, a `docker/` directory, and a `benchmarks/` directory. There is also a `docs/` directory, a `releasenotes/` directory, a `templates/` directory, and several configuration files that are unusual for a Python project: `supported-configurations.json`, `supported_versions.json`, `min_compatible_versions.csv`, `requirements.csv`, `code-coverage.datadog.yml`, `static-analysis.datadog.yml` and `repository.datadog.yml`. The `.claude/` and `.cursor/` directories and the AGENTS.md file indicate that the project has been set up for use with coding assistants, and the `sgconfig.yml` file with its `.sg/` directory points to a tool for code review, while `mypy.ini` and the `.clang-format` file cover type checking and formatting for the two languages involved. It is a large tree, and the size is a fair proxy for the scope.

## Where this is the wrong choice

The limits follow from what the library does. The first is the coupling to the interpreter. The profiler and the dynamic instrumentation features hook into CPython internals, which is why the project pins interpreter versions so tightly, why the bytecode dependency is versioned per Python release, and why a Cython pin exists for 3.15. That work is what makes the profiler possible, and it is also what makes the client fragile in a way a pure Python tracer would not be. The second is the commercial direction. This is the official client for a proprietary SaaS, the repository is maintained by the vendor, and the metadata records a custom licence rather than a standard one, so if your requirement is an open, vendor neutral tracing standard, this is the wrong client even though it is open source. The third is deployment shape. The library is a long lived process agent with a trace agent to talk to, which is fine for a service and awkward for a short lived function, where startup cost and the agent dependency are disproportionate. The fourth is the release numbering, where the recent tags include a release candidate for the next minor, and the metadata is already on a later version again, so pinning to a specific version rather than tracking the branch is the sensible habit.

## Conclusion

ddtrace is the right choice if your services already run on Datadog and you want tracing, profiling and error tracking to arrive in the same place, since building that yourself is a project of its own. It is the wrong choice if you want a vendor neutral tracer, or if you cannot accept a client that hooks CPython internals and breaks when the interpreter changes, and it is a poor fit for a serverless function where the tracing overhead and the agent dependency outweigh the data. Before you adopt it, check the supported Python versions against your runtime, since the project pins a range and marks incompatibility explicitly, and read the versioning documentation for the maintenance and bug fix policy. The last push was on 2026-10-22 with version 4.16.0 released on 2026-10-16.

## FAQ

### What does the ddtrace Python library do?

It is the Datadog APM client library, and the README says it powers distributed tracing, continuous profiling, error tracking, test optimisation, deployment tracking, code hotspots, dynamic instrumentation and more. It is published on PyPI as `ddtrace` and documented at ddtrace.readthedocs.io.

### Which Python versions does ddtrace support?

The project metadata requires Python 3.9 or newer and below 3.15, with classifiers for 3.9 through 3.14, and there is a note to raise that upper bound once Python 3.15 support is complete. The `bytecode` dependency is versioned separately for each interpreter version because of CPython internal changes.

### Why does installing ddtrace from source require Cython and CMake?

The package ships native components, so the build requirements include Cython, CMake, setuptools-rust and patchelf on Linux. If Cython cannot be imported, setup.py raises an ImportError that explains the pip version requirement and points to the quickstart documentation.

### What services does the ddtrace test suite depend on?

The docker-compose configuration brings up Elasticsearch, OpenSearch, Cassandra, Consul, Postgres, MariaDB, MySQL, Redis, Kafka and a Redis cluster, each bound to a local port, with separate compose files for GPU and podman setups.

## Sources

- [DataDog/dd-trace-py on GitHub](https://github.com/DataDog/dd-trace-py)
- [Issues](https://github.com/DataDog/dd-trace-py/issues)
- [Project website](https://ddtrace.readthedocs.io/)
- [README](https://github.com/DataDog/dd-trace-py/blob/main/README.md)
- [Releases](https://github.com/DataDog/dd-trace-py/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/datadog-dd-trace-py
