This guide walks a new application from zero to traces in Datadog: add
the dependency, point the tracer at an agent, name the service, and turn
on the integrations. Every setting named here is described in full in
DDTrace.Config — that module is the reference; this page is the path
through it.
Install
Add the dependency:
# mix.exs
defp deps do
[
{:dd_trace_ex, "~> 0.1.0"}
]
endThat is the whole installation. dd_trace_ex is an OTP application that
starts with your release: there is no tracer module to define, no child
to add to your supervision tree, and no use line in your application.
Code that calls the trace/2 macro needs a require DDTrace (or an
import); everything else on the module is plain functions.
Point it at an agent
Spans travel to a Datadog agent,
which forwards them to Datadog. The tracer assumes the standard address,
http://localhost:8126, so a host with a local agent needs nothing
configured.
When the agent lives elsewhere — another container, a sidecar, a node port — say where, either way:
config :dd_trace_ex, agent_url: "http://datadog-agent:8126"or through the environment the agent's own images already speak:
DD_TRACE_AGENT_URL, or DD_AGENT_HOST and DD_TRACE_AGENT_PORT.
Only http:// URLs are supported.
An unreachable agent never harms the application: submissions fail, the failure is logged, and the request that produced the spans is unaffected.
Name the service
Three values identify everything the tracer sends — the service's name, its deployment environment, and its version. Datadog calls this unified service tagging.
# config/runtime.exs
config :dd_trace_ex, service: "storefront"The same three arrive as DD_SERVICE, DD_ENV and DD_VERSION with no
config at all — every setting resolves from application config first,
then its DD_* environment variable, then its default. Application
config wins, matching the other Datadog tracers. Naming the service in
config and leaving env and version to the environment is the usual
split: the name is the application's to state, and the other two are
facts about the deployment.
A tracer configured with no service ships spans as
unnamed-elixir-service: visible in the UI, and there to be recognized
as a tracer nobody configured.
Verify
The tracer logs its resolved settings in one line at boot:
[info] dd_trace_ex started, config: %{service: "storefront", env: "prod", ...}The same facts are available on demand from DDTrace.info/0. Then open
a span and watch it arrive:
require DDTrace
DDTrace.trace "smoke.test" do
:ok
endSpans buffer and flush on a cadence, so give it a few seconds before looking for the trace in Datadog's UI.
Trace Phoenix requests
One call at startup, before the endpoint:
# lib/my_app/application.ex
def start(_type, _args) do
DDTrace.Integrations.Phoenix.setup()
children = [MyAppWeb.Endpoint, ...]
Supervisor.start_link(children, strategy: :one_for_one, name: MyApp.Supervisor)
endEvery request becomes a "phoenix.request" span, resourced by its route
pattern, carrying the standard http.* tags, and joined to the caller's
trace when the request arrived with x-datadog-* headers.
DDTrace.Integrations.Phoenix documents the options: a filter: for
health checks, a request: hook for custom tags, and what the endpoint
has to have (the Plug.Telemetry line every generated endpoint already
contains).
Trace Ecto queries
One call per repo, also at startup:
DDTrace.Integrations.Ecto.setup(repo: MyApp.Repo)Every query becomes a span with the SQL as its resource, the
connection's db.* tags, and Ecto's queue/query/decode breakdown as
numeric metrics, landing under whatever trace the query ran inside.
DDTrace.Integrations.Ecto documents the options and the decisions
behind them — most notably that bind parameters never leave the
database.
Trace outbound Req requests
One call where the client is built:
req =
Req.new(base_url: "https://api.github.com")
|> DDTrace.Integrations.Req.attach()
Req.get!(req, url: "/repos/elixir-lang/elixir")Every request through an attached client becomes an "http.request"
span under whatever trace it ran inside, with the x-datadog-* headers
injected before it goes out, so the service on the other end continues
the trace. A client you do not attach is not traced.
DDTrace.Integrations.Req documents the options and what counts as an
error — the 4xx window, the inverse of the server's.
Trace your own functions
The trace/2 block macro is the primitive; the decorator is the same
span without restructuring the function:
defmodule MyApp.Orders do
use DDTrace.Decorators
@decorate trace()
def process(order) do
DDTrace.set_tag("order.channel", order.channel)
# ...
end
endSee DDTrace for the block macro, the mutators, and crossing processes;
DDTrace.Decorators for decorating whole modules.
Continue traces across services
Outbound, an attached Req client injects the trace's headers by itself. Any other HTTP client carries the trace the same way, by hand:
headers = DDTrace.inject([{"content-type", "application/json"}])
Finch.build(:post, url, headers, body) |> Finch.request(MyApp.Finch)Inbound, the Phoenix integration extracts automatically. A non-HTTP
entry point extracts by hand with DDTrace.extract/1 and opens its span
with parent:.
Correlate logs with traces
While a span is open, the process's Logger metadata carries the
dd.trace_id family of keys, which Datadog's UI uses to link a log line
to its trace. Any JSON logger that emits metadata gets this for free. A
metadata: allowlist has to spell the keys as the quoted atoms they are:
config :logger, :default_formatter,
metadata: [:request_id, :"dd.trace_id", :"dd.span_id", :"dd.service"]metadata: :all needs no list. DDTrace.Config has the full story,
including turning injection off.
Assert on spans in tests
use DDTrace.Test in a test module captures every span the test
finishes — no agent involved — and imports assert_span and friends:
use ExUnit.Case, async: true
use DDTrace.Test
test "processing an order is traced" do
MyApp.Orders.process(order)
assert_span "order.process", resource: "ImportOrders"
endDDTrace.Test documents the matchers and how spans find their test
across processes.
Every setting
The table in DDTrace.Config lists every setting, its DD_* variable,
and its default — tracing on/off, the agent address, log injection, the
x-datadog-tags budget, which HTTP statuses count as errors, and the
shutdown flush budget. Bad configuration degrades the tracer; it never
stops the host application from booting.