ddtrace Python Library

repository·main·Indexed 20 days ago

https://github.com/datadog/dd-trace-py

The official Datadog APM client library for Python, providing instrumentation for distributed tracing, continuous profiling, error tracking, and observability. It includes features such as Dynamic Instrumentation, Exception Replay, and Code Origin for Spans, as well as support for MLFlow tracking server authentication.

Tokens
49.5K
Snippets
143
Records
228
Agent score
70%

What's inside ddtrace

  1. Overview of ddtrace capabilities

    main

    The ddtrace library is the core engine for Datadog's observability features in Python. It enables several key capabilities including:

    • Distributed Tracing: Track requests as they flow through complex distributed systems.
    • Continuous Profiling: Monitor code performance and resource usage in real-time.
    • Error Tracking: Capture and analyze application errors.
    • Test Optimization: Identify slow or problematic tests.
    • Deployment Tracking: Correlate deployments with changes in performance or error rates.
    • Code Hotspots: Connect traces and profiles to identify exactly which lines of code are causing latency.
    • Dynamic Instrumentation: Apply instrumentation to running applications without code changes.
  2. Overview of Datadog Python APM Client (ddtrace)

    main
    ddtrace is the Datadog Python APM client. It is used to profile code and trace requests as they flow across web servers, databases, and microservices. This provides visibility into application bottlenecks and troublesome requests.
  3. View supported library integrations

    main

    The ddtrace library provides automatic instrumentation for a wide variety of Python libraries, frameworks, and services. These integrations allow you to capture traces and metrics for your application's dependencies without manual instrumentation.

    Supported categories include:

    • Web Frameworks: Flask, FastAPI, Django, Sanic, Tornado, Bottle, Pyramid, Starlette, ASGI, WSGI.
    • Databases & Caching: SQLAlchemy, Psycopg, MySQL (mysqlclient, pymysql, mysql-connector), PostgreSQL (asyncpg, aiopg), Redis, Valkey, MongoDB (pymongo), Elasticsearch, Snowflake, SQLite, MariaDB, Vertica.
    • Asynchronous & Networking: asyncio, aiohttp, httpx, requests, urllib, urllib3, grpc, httplib.
    • Task Queues & Messaging: Celery, Dramatiq, RQ, Kafka, Kombu, Google Cloud Pub/Sub.
    • AI & LLM: OpenAI, Anthropic, LangChain, LangGraph, LlamaIndex, LiteLLM, Google GenAI, VertexAI, vLLM, Claude Agent SDK.
    • Cloud Services: AWS (botocore, boto2, AWS Durable Execution SDK), Azure (Functions, CosmosDB, Durable Functions).
    • Testing: pytest, pytest-bdd, pytest-benchmark, unittest.
    • Other: Logging (logging, loguru, structlog), Selenium, Subprocess, Protobuf, etc.
  4. What is an integration in ddtrace?

    main

    An integration is code implemented in the ddtrace.contrib module that builds a tree of ExecutionContext objects representing the runtime state of a third-party Python module. It emits events to indicate interesting moments during that runtime.

    Core Principles:

    • Invisibility: Integrations must be completely invisible to the application code. They should not change the contract between the application and the library (e.g., always re-raise exceptions after catching them).
    • Product Decoupling: Integrations should not reference Datadog-specific product concepts like Tracing Spans or the AppSec WAF directly. Instead, they use the Core API to emit data.
    • Minimal Public API: Avoid exposing a public API unless necessary. Prefer configuration via environment variables. If a public API is required, expose it in ddtrace.contrib.<integration_name>.py.
    • Documentation: Every integration must define a ddtrace.contrib.internal.<integration>.__init__.py module containing a docstring describing the integration and its supported configurations.
  5. Understand the `dd_wrapper` experimental profiling architecture

    main

    dd_wrapper is an experimental component in dd-trace-py that uses libdatadog for collecting and propagating profiling data. It is designed to move profiling work from Python-based collectors to native, off-GIL threads to reduce overhead and improve accuracy.

    Core Components

    • Samples: A collection of data representing a single observation (e.g., allocations, locks, or CPU). All observation types use the same data structure. Samples are interacted with transactionally: they are started, populated with data (frames, wall time, tags), and then flushed.
    • Profile: A wrapper around a collection of Samples. Profiles are periodically flushed to the Datadog backend. To minimize memory overhead, strings like function and file names are interned/stored in a ProfilesDictionary that is reused across Samples and Profiles.
    • Uploader: A specialized class that manages the upload operations of a Profile to the Datadog backend. Because uploads involve HTTP calls that can be sensitive to fork() operations, the uploader is rebuilt fresh to ensure safety.

    Lifecycle of a Sample

    Interacting with a sample follows a strict transactional pattern:

    1. Call ddup_start_sample() to obtain an opaque pointer.
    2. Add data such as frames, wall time, and tags.
    3. Call ddup_flush_sample() to store the data in a ddog_prof_Profile object and release the sample.

    Note: Do not attempt to reuse Samples in application code after they have been flushed.

  6. How Exception Replay captures debug information

    main

    Exception Replay integrates with the tracer to capture debug information automatically when a span is marked with an error and a traceback is available.

    It functions by listening for span.exception core events. When such an event is detected, the SpanExceptionHandler reacts by enqueuing snapshots into the LogsIntakeUploaderV1 for upload.

  7. Understand the dd-trace-py build system architecture

    main

    The build system for dd-trace-py manages the compilation of multiple native extensions, including CMake C++, Cython, and Rust. The process is orchestrated through setup.py and can be triggered via riot generate which executes pip install -e ..

    Build Workflow

    1. Library Download: LibraryDownloader.run() handles dependencies. If INCREMENTAL=1 is set, CleanLibraries.remove_artifacts() is skipped to preserve existing files.
    2. Extension Building: CustomBuildExt.run() manages the compilation of:
      • Rust extensions: via build_rust().
      • C++ wrappers: libdd_wrapper.so via build_libdd_wrapper().
      • Shared dependencies: absl (Abseil) via build_shared_deps().
      • Standard extensions: build_extension() for CMake, Cython, or C extensions.
    3. Incremental Checks:
      • CMakeExtensions skip rebuilding if the .so file is newer than the source files.
      • Cython/C extensions skip rebuilding if the .so is newer than the .pyx or .pxd source files.
    riot generate
      └─ pip install -e .
           ├─ build_py
           └─ build_ext
  8. Manage Tracing Context and Implicit Parenting

    main

    Context management in ddtrace refers to controlling which Span or Context is active within an execution (thread, task, etc.). Only one span or context can be active per execution at a time.

    When using tracer.trace(), new spans are automatically created as children of the currently active context. When a span finishes, its parent becomes the active span again. This allows for implicit parenting using context managers.

    # Here no span is active
    assert tracer.current_span() is None
    
    with tracer.trace("parent") as parent:
        # Here `parent` is active
        assert tracer.current_span() is parent
    
        with tracer.trace("child") as child:
            # Here `child` is active and inherits from `parent`
            assert tracer.current_span() is child
    
        # `parent` is active again
        assert tracer.current_span() is parent
    
    # Here no span is active again
    assert tracer.current_span() is None
  9. Component Overview of the native profiling implementation

    main

    The native profiling implementation in dd-trace-py is composed of several specialized components that interact to provide profiling capabilities. Most components depend on dd_wrapper to access libdatadog resources, ensuring that large native code is not double-shipped, which helps meet the repository's strict size requirements.

    Key components include:

    • dd_wrapper: Provides C interfaces to libdatadog resources.
    • ddup: Provides Python interfaces to dd_wrapper via Cython.
    • stack: A shim layer that wraps echion to align its concepts with the rest of the repository.
    • crashtracker: Provides Python interfaces for crashtracker via Cython.
  10. Understand Abseil shared dependency caching

    main

    Abseil is a shared dependency built once via CustomBuildExt.build_shared_deps() and installed to .download_cache/_cmake_deps/absl_install_<arch>.

    Rebuild Prevention

    • Sentinel File: A .dep_build_info file containing a configuration hash is used by SharedDep.is_built() to prevent unnecessary rebuilds.
    • Cache Keying: scripts/ext_cache.py caches the entire install tree under .ext_cache/shared_deps/absl/<config_hash>/. The config_hash is derived from:
      • version
      • compile mode
      • platform
      • machine arch
      • ARCHFLAGS
  11. How Code Origin for Spans works

    main

    Code Origin for Spans allows for the retrieval of code origin information specifically for entry spans.

    Mechanism:

    1. The system listens for the service_entrypoint.patch core event, which triggers when an integration (e.g., Flask) is about to patch a service entrypoint.
    2. An EntrySpanWrappingContext is used to instrument the function object used as the entrypoint.
    3. This context allows for the extraction of pre-computed and cached code origin information, as well as the capture of snapshots if required.
    4. Captured information is enqueued via LogsIntakeUploaderV1.