View Source OpentelemetryProcessPropagator (Opentelemetry Process Propagator v0.2.0)

OpentelemetryProcessPropagator provides helpers for dealing with context propagation across process boundaries.

context-propagation

Context Propagation

Erlang and Elixir do not have a mechanism for transparently passing context between processes. This requires the user to explicitly pass data between processes. In order to continue a trace across processes, the user must start a new span and pass it to the spawned process.

span_ctx = OpenTelemetry.Tracer.start_span("child")
ctx = OpenTelemetry.Ctx.get_current()

task =
  Task.async(fn ->
    OpenTelemetry.Ctx.attach(ctx)
    OpenTelemetry.Tracer.set_current_span(span_ctx)
    # do work here

    OpenTelemetry.Tracer.end_span(span_ctx)
  end)

_result = Task.await(task)

reverse-propagation

Reverse Propagation

It's not always possible to have full control over traces, such as when using telemetry events emitted from a library you don't control to create a span. In such cases, a mechanism to fetch a context from a calling process is necessary. This is effectively context propagation in reverse.

As an example, Ecto uses the Task module to execute preloads which are each a separate query. Since a task is a spawned process, creating an otel span results in orphan spans. To correctly connect these spans we must find the otel context which spawned the process.

usage

Usage

Example of using fetch_parent_ctx/1 to find a parent context.

OpenTelemetry.with_span :span_started_in_your_app do
  # some span being created in a process spawned by a library
  # you don't control, e.g. Ecto preloads

  Task.async(fn ->
    parent_ctx = OpentelemetryProcessPropagator.fetch_parent_ctx(:"$callers")

    OpenTelemetry.Ctx.attach(parent_ctx)

    attrs = %{some_attr: :from_telemetry_event}

    span =
      OpenTelemetry.Tracer.start_span(:span_created_in_lib, %{attributes: attrs})

    OpenTelemetry.Span.end_span(span)
  end)
end
?with_span(span_started_in_your_app, , fun() ->
  %% some span being created in a process spawned by a library
  %% you don't control

  proc_lib:spawn_link(fun() ->
    Tracer = opentelemetry:get_tracer(test_tracer),
    ParentCtx = opentelemetry_process_propagator:fetch_parent_ctx(),
    otel_ctx:attach(ParentCtx),
    Span = otel_tracer:start_span(Tracer, span_created_in_lib, ),
    otel_tracer:end_span(Span).
  ).
end

Link to this section Summary

Functions

Attempt to fetch an otel context from a give pid.

Attempt to find an otel context in the spawning process.

Attempt to find an otel context in a spawning process within n number of parent processes

Attempt to find an otel context under a given process dictionary key within n number of parent processes. The first context found will be returned.

Link to this section Functions

@spec fetch_ctx(pid()) :: OpenTelemetry.span_ctx() | :undefined

Attempt to fetch an otel context from a give pid.

@spec fetch_parent_ctx() :: OpenTelemetry.span_ctx() | :undefined

Attempt to find an otel context in the spawning process.

This is equivalent to calling fetch_parent_ctx(1, :"$ancestors")

@spec fetch_parent_ctx(non_neg_integer()) :: OpenTelemetry.span_ctx() | :undefined

Attempt to find an otel context in a spawning process within n number of parent processes

Link to this function

fetch_parent_ctx(max_depth, key)

View Source
@spec fetch_parent_ctx(non_neg_integer(), atom()) ::
  OpenTelemetry.span_ctx() | :undefined

Attempt to find an otel context under a given process dictionary key within n number of parent processes. The first context found will be returned.

Processes spawned by proc_lib are stored under :"$ancestors. The Elixir Task module uses the :"$callers key.