# dd_trace_ex

A Datadog APM tracing library for Elixir.

`dd_trace_ex` (top-level module `DDTrace`) is written to match what
Datadog's official dd-trace libraries do. It sends traces to the Datadog
agent in the same format they do, so features like 128-bit trace IDs,
numeric metrics, tracing across services, sampling and manual keep or drop
all behave the way Datadog users expect. dd-trace-js and dd-trace-go were
the reference for how things should work on the wire. Tracing context
lives in the process and follows work into other processes, the API is
small and built around a single `trace` call, and Phoenix, Ecto and Req
are supported through `:telemetry`. It is still early, but the core is in
place.

> **Status: `0.1.0` release candidate.** This is a first public release, not
> a mature library: it covers a documented subset of Datadog tracing (see
> [Current limitations](#current-limitations)), and the API may still change
> before `1.0`.

## Installation

Add `dd_trace_ex` to your dependencies:

```elixir
# mix.exs
defp deps do
  [
    {:dd_trace_ex, "~> 0.1.0"}
  ]
end
```

Run `mix deps.get`, then configure your service and agent connection:

```elixir
# config/runtime.exs
config :dd_trace_ex,
  service: "my_app",
  agent_url: "http://localhost:8126"
```

The tracer starts automatically with your application. You need a Datadog
Agent with APM enabled to receive the traces. See the [setup guide](guides/setup.md)
for environment variables and integration setup.

## Trace application code

Wrap work in `DDTrace.trace`. Tags and metrics describe the span being traced:

```elixir
require DDTrace

DDTrace.trace "orders.import" do
  DDTrace.set_tag("order.channel", "web")
  DDTrace.set_metric("orders.count", length(orders))

  MyApp.Orders.import(orders)
end
```

The block returns its result. Nested calls create child spans. Exceptions
that escape the block are recorded on the span and re-raised.

For a function you want to trace on every call, use a decorator:

```elixir
defmodule MyApp.Orders do
  use DDTrace.Decorators

  @decorate trace(name: "orders.import")
  def import(orders) do
    DDTrace.set_metric("orders.count", length(orders))
    # ...
  end
end
```

`DDTrace.with_trace/3` takes a function when that fits better than a block.

## Integrations

Enable Phoenix and Ecto tracing at application startup, before starting
your endpoint and repo:

```elixir
DDTrace.Integrations.Phoenix.setup()
DDTrace.Integrations.Ecto.setup(repo: MyApp.Repo)
```

Phoenix request spans include the route, HTTP status, and incoming trace
context. Ecto query spans include SQL and timing information; bind
parameter values are excluded.

For Req, attach tracing when building the client:

```elixir
req =
  Req.new(base_url: "https://inventory.example.com")
  |> DDTrace.Integrations.Req.attach()

Req.get!(req, url: "/items")
```

Requests through that client produce spans and carry Datadog trace headers
to the receiving service.

## Trace work across processes

Use `DDTrace.Task` to carry the current trace into tasks. Create spans
inside the task for the work you want to measure:

```elixir
require DDTrace

DDTrace.trace "orders.import" do
  orders
  |> DDTrace.Task.async_stream(fn order ->
    DDTrace.trace "order.process" do
      MyApp.Orders.process(order)
    end
  end)
  |> Enum.to_list()
end
```

For messages, GenServers, and other process boundaries, capture the context
with `DDTrace.current_context/0` and pass it to the receiving process. Use
`parent:` when opening its span.

## Sampling and logs

The tracer uses sampling rates supplied by the agent. You can also
configure sampling rules or a fixed rate, and override the decision with
`DDTrace.keep_trace/0` or `DDTrace.drop_trace/0`. Dropped traces still reach
the agent; dropping controls retention.

While a span is open, trace and span IDs are available in `Logger`
metadata. Include these fields in your JSON logs for Datadog log
correlation.

See the [setup guide](guides/setup.md) for configuration and logging examples.

## Testing

Capture spans in ExUnit without running an agent:

```elixir
defmodule MyApp.OrdersTest do
  use ExUnit.Case, async: true
  use DDTrace.Test

  test "imports are traced" do
    MyApp.Orders.import([])

    assert_span "orders.import"
  end
end
```

## Current limitations

- Agent connections support HTTP only; HTTPS and Unix sockets are not supported.
- Sampling applies to whole traces. Keeping individual spans from sampled-out
  traces is not supported.
- Sampled-out traces are still sent to the agent. The library does not compute
  APM statistics locally.

## License

MIT. See [LICENSE](LICENSE).

`dd_trace_ex` is an independent project. It is not affiliated with,
endorsed by, or sponsored by Datadog, Inc. "Datadog" and "dd-trace" are
trademarks of Datadog, Inc., used here only to describe what this library
interoperates with.
