Tracing a function by annotating its definition.
use DDTrace.Decorators brings in one decorator, trace, which wraps
the decorated function in the span DDTrace.trace/2,3 would have opened
around its body — a child of whatever span the process already has open,
or the root of a new trace when it has none:
defmodule MyApp.Orders do
use DDTrace.Decorators
@decorate trace()
def process(order), do: ...
@decorate trace(service: "billing")
defp charge(order), do: ...
endThe function's return value, signature, and visibility are untouched:
defp stays private, guards still guard, and the decorator adds nothing
a caller can see other than the span.
The span's name
A bare @decorate trace() names the span after the function it
decorates — "MyApp.Orders.process/1" — and the span's resource
defaults to the same, so a decorated function is findable in Datadog
without naming anything. The arity is the definition's: a function
with default arguments is one definition, so f(a, b \\ 1) spans as
f/2 however many arguments a call passed.
name: overrides it:
@decorate trace(name: "orders.process")
def process(order), do: ...Options
Every other option is DDTrace.start_span/2's, with the same meanings —
service:, resource:, type: — and a misspelled one is a
compile-time warning, as it is on the macro. Values are ordinary
expressions evaluated at call time in the module's scope; a module
attribute works, and the decorated function's own arguments are not in
scope.
Tags are not an option, here or on the macro: they are written from inside the body, which is also where the data for them is.
@decorate trace()
def process(order) do
DDTrace.set_tag("order.channel", order.channel)
...
endWhole modules, and multiple clauses
@decorate_all trace() decorates every function defined after it, which
is the whole instrumentation of a worker module:
defmodule MyApp.Worker do
use DDTrace.Decorators
@decorate_all trace()
def perform(job), do: ...
def retry(job), do: ...
endDecorating a multi-clause function traces every clause of that name and arity, under the one name, one span per call — a function is traced as a unit rather than a clause at a time. Decorating the first clause is enough.
rescue, catch, and after
A decorated definition may not carry them:
@decorate trace()
def process(order) do
...
rescue # compile error
e -> ...
endThe rewriting would wrap the body and each handler separately, which
would open a second span for the handler and error-tag the first for an
error you are handling. Put the try inside the body instead, where it
means what it says:
@decorate trace()
def process(order) do
try do
...
rescue
e -> ...
end
endAn error that leaves a decorated function any other way — a raise you do
not handle, a throw, an exit — tags the span and propagates
unchanged, exactly as it does through DDTrace.trace/2,3.
When tracing is off
A decorated function runs and returns its value with the tracer
disabled, or with the application not started, and produces no span. The
decorator is DDTrace.trace/2,3, so it is a no-op wherever that is.