DDTrace.SpanContext (dd_trace_ex v0.1.0)

Copy Markdown View Source

A trace, in the form that travels between processes.

DDTrace.current_context/0 takes one of these; parent: on DDTrace.trace/2, DDTrace.with_trace/3 and DDTrace.start_span/2, and DDTrace.with_context/2, put a new span underneath it:

ctx = DDTrace.current_context()
GenServer.cast(worker, {:work, item, ctx})

# in the worker
DDTrace.trace "worker.process", parent: ctx do
  process(item)
end

It is plain data — integers and a string — so it survives everything a term can travel through: a GenServer.cast, a message to another node, a job serialized into a database.

Fields

  • trace_id — the 128-bit trace the new span joins.
  • span_id — the span it becomes a child of.
  • priority — the trace's sampling priority when the snapshot was taken. DDTrace.Priority names the four the tracer itself mints; the field is any integer, because an upstream service's decision is honoured exactly as it arrived even when it is one nobody here has a name for. nil when nothing has decided yet: a context DDTrace.extract/1 read from headers that carried no priority is undecided, and stays so until a span opened under it has to hand the trace on or ship it. A snapshot taken with a span open always has an integer.
  • dm — what decided that priority, as DDTrace.Tags.decision_maker/0 spells it on the wire, or nil where nothing did: a trace someone dropped is not being kept for a reason, and an undecided one is not being kept at all yet.
  • origin — where the trace entered Datadog, when it came from a product that says so ("synthetics", "rum"), or nil. Only DDTrace.extract/1 produces one: minting an origin locally would claim the trace started somewhere it did not.
  • propagation_tags — the _dd.p.* trace tags carried from upstream, minus the two the tracer keeps as fields of their own. Empty for a trace that started here.
  • propagation_error — what went wrong reading or writing the tags header, as DDTrace.Tags.propagation_error/0 spells it, or nil when nothing did.

_dd.p.tid and _dd.p.dm are fields, not entries

Both are _dd.p.* tags on the wire, and neither is ever a member of propagation_tags: the upper half of a 128-bit trace id is part of trace_id, and the decision maker is dm. Extraction parses them out of the inbound header into those fields and injection derives them back, so the map and the fields cannot disagree about what the trace is. What the map holds is the rest — the pairs this service carries but does not read.

A snapshot is a photograph

Nothing that happens afterwards reaches it. A snapshot taken before the originating process changes the trace's sampling decision carries the decision as it was, and a process that opens spans under it ships that one — exactly as a service that received the same facts in headers would.

Summary

Types

t()

@type t() :: %DDTrace.SpanContext{
  dm: String.t() | nil,
  origin: String.t() | nil,
  priority: integer() | nil,
  propagation_error: String.t() | nil,
  propagation_tags: %{optional(String.t()) => String.t()},
  span_id: non_neg_integer(),
  trace_id: non_neg_integer()
}