ForgeOpsTracker.Tracing (forge_ops_tracker v0.13.0)

Copy Markdown View Source

One trace's worth of spans (a request's own call tree), kept in the calling process's own process dictionary: the same isolation boundary ForgeOpsTracker.set_user/1 and ForgeOpsTracker.add_breadcrumb/4 already use, and for the same reason (Phoenix and Oban both run one unit of work in one process). Not something app code normally calls directly; see ForgeOpsTracker.span/4 and ForgeOpsTracker.record_span/5.

Nesting comes from a stack of open span ids: a span opened while another is open becomes its child, and anything else parents under the root. A trace is sent only when its root span took at least Configuration.trace_capture_threshold milliseconds, or the request errored, decided here once the root finishes, so a fast, successful request costs nothing on the wire.

Alongside the spans, the process dictionary holds the request's own context (trace id, the remote parent span id from an incoming traceparent, transaction name, endpoint, and whether it errored), kept whether or not track_tracing is on: the trace id is also what an error event carries so ForgeOps can link it to errors other services reported for the same trace.

Summary

Types

Outgoing request headers, as {name, value} pairs.

Functions

True when the calling process has an open trace.

The calling process's current trace id, or nil outside a trace.

The top-level fields an error event carries for the request it happened in: "trace_id", "transaction_name", "endpoint", each left out when unknown. Falls back to what snapshot_escaped/1 remembered for reason once the request itself is over, and to %{} outside a request.

Ends the calling process's trace, always clearing it along with the request context, and queues it for delivery when the root took at least trace_capture_threshold milliseconds or the request errored. started_at is a DateTime.

Makes one outgoing HTTP call inside the calling process's trace: records it as an "http" span named "<METHOD> <host>" (never the path or query, which could carry an id or a token) and calls fun with a list of {name, value} headers to add to the request, currently a W3C traceparent whose parent id is that span's own id. The headers are [] outside a trace, when propagate_traces is off, or when the host isn't in trace_propagation_targets; outside a trace no span is recorded either, and with track_tracing off the header is still handed over but no span is kept.

Marks the current request errored, so its trace is sent however fast it was.

Records a span you timed yourself under the current one; a no-op outside a trace.

Names the current request (a no-op outside one): transaction_name is the same name its performance sample and root span use, endpoint the HTTP method plus the route pattern.

Remembers the current request's context for reason, an error escaping the request, and marks it errored, so an error reported only after finish_trace/4 has cleared the request (as a Phoenix request's crash is, by the :logger handler, once the router has already emitted :exception) still carries it. The calling process's user and breadcrumbs are untouched by finish_trace/4, so only the request context needs remembering. Cleared by the next start_trace/1 on the same process.

Times fun as a child span of whatever span is open (or of the root), returning its result. Outside a trace it just runs fun. Recorded even if fun raises, throws or exits, which then propagates unchanged.

Starts a fresh trace on the calling process, discarding any earlier one. traceparent is the incoming request's own header value: a usable W3C value continues the caller's trace (same trace id, and the root span's parent is the caller's span), anything else (nil, blank, malformed) starts a new one. The request context is set whenever the client is enabled; spans are only recorded when track_tracing is on too. A no-op when the client isn't enabled.

Types

headers()

@type headers() :: [{String.t(), String.t()}]

Outgoing request headers, as {name, value} pairs.

Functions

active?()

@spec active?() :: boolean()

True when the calling process has an open trace.

current_trace_id()

@spec current_trace_id() :: String.t() | nil

The calling process's current trace id, or nil outside a trace.

error_fields(reason)

@spec error_fields(term()) :: map()

The top-level fields an error event carries for the request it happened in: "trace_id", "transaction_name", "endpoint", each left out when unknown. Falls back to what snapshot_escaped/1 remembered for reason once the request itself is over, and to %{} outside a request.

finish_trace(name, kind, started_at, duration_ms)

@spec finish_trace(String.t(), String.t(), DateTime.t(), number()) :: :ok

Ends the calling process's trace, always clearing it along with the request context, and queues it for delivery when the root took at least trace_capture_threshold milliseconds or the request errored. started_at is a DateTime.

http_span(method, url, data, fun)

@spec http_span(String.t() | atom(), String.t() | URI.t(), map(), (headers() ->
                                                               result)) ::
  result
when result: var

Makes one outgoing HTTP call inside the calling process's trace: records it as an "http" span named "<METHOD> <host>" (never the path or query, which could carry an id or a token) and calls fun with a list of {name, value} headers to add to the request, currently a W3C traceparent whose parent id is that span's own id. The headers are [] outside a trace, when propagate_traces is off, or when the host isn't in trace_propagation_targets; outside a trace no span is recorded either, and with track_tracing off the header is still handed over but no span is kept.

mark_errored()

@spec mark_errored() :: :ok

Marks the current request errored, so its trace is sent however fast it was.

record_span(name, kind, started_at, duration_ms, data \\ %{})

@spec record_span(String.t(), String.t(), DateTime.t(), number(), map()) :: :ok

Records a span you timed yourself under the current one; a no-op outside a trace.

set_request_route(transaction_name, endpoint)

@spec set_request_route(String.t() | nil, String.t() | nil) :: :ok

Names the current request (a no-op outside one): transaction_name is the same name its performance sample and root span use, endpoint the HTTP method plus the route pattern.

snapshot_escaped(reason)

@spec snapshot_escaped(term()) :: :ok

Remembers the current request's context for reason, an error escaping the request, and marks it errored, so an error reported only after finish_trace/4 has cleared the request (as a Phoenix request's crash is, by the :logger handler, once the router has already emitted :exception) still carries it. The calling process's user and breadcrumbs are untouched by finish_trace/4, so only the request context needs remembering. Cleared by the next start_trace/1 on the same process.

span(name, kind, data, fun)

@spec span(String.t(), String.t(), map(), (-> result)) :: result when result: var

Times fun as a child span of whatever span is open (or of the root), returning its result. Outside a trace it just runs fun. Recorded even if fun raises, throws or exits, which then propagates unchanged.

start_trace(traceparent \\ nil)

@spec start_trace(String.t() | nil) :: :ok

Starts a fresh trace on the calling process, discarding any earlier one. traceparent is the incoming request's own header value: a usable W3C value continues the caller's trace (same trace id, and the root span's parent is the caller's span), anything else (nil, blank, malformed) starts a new one. The request context is set whenever the client is enabled; spans are only recorded when track_tracing is on too. A no-op when the client isn't enabled.