Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions guides/explanations/fork-differences.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,28 @@ defmodule MyApp.Repo.Migrations.UpgradeProjectionVersionsTimestamps do
end
end
```

### **W3C Trace Context Propagation Middleware**
[PR #38](https://github.com/straw-hat-team/commanded/pull/38)

**Changes:**
- Added `Commanded.Middleware.TraceContextPropagator` middleware for propagating OpenTelemetry trace context
- Captures current span context and stores it in event metadata using W3C Trace Context standard
- Added `opentelemetry_api` as optional dependency

**Usage:**
```elixir
defmodule MyApp.Router do
use Commanded.Commands.Router

middleware Commanded.Middleware.TraceContextPropagator

# ... your command routes
end
```

**Benefits:**
- Enables distributed tracing correlation between command dispatch and event handlers
- Uses standard W3C `traceparent` and `tracestate` headers stored in event metadata
- Event handlers can extract trace context to create span links or parent-child relationships
- Non-invasive - only adds metadata when a span is active
66 changes: 66 additions & 0 deletions lib/commanded/middleware/trace_context_propagator.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
if Code.ensure_loaded?(:otel_propagator_text_map) do
defmodule Commanded.Middleware.TraceContextPropagator do
@moduledoc """
A middleware for propagating W3C Trace Context to Event Handlers.

This middleware captures the current span context and stores it in event metadata
following the [W3C Trace Context](https://www.w3.org/TR/trace-context-2/) specification,
allowing event handlers to create proper span links or parent-child relationships.

## W3C Trace Context

The middleware stores trace context using the standard W3C header names:

* `traceparent` - Contains version, trace-id, parent-id (span-id), and trace-flags
* `tracestate` - Vendor-specific key-value pairs (optional, only set if present)

Example traceparent: `00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01`

## Dependencies

This middleware requires `opentelemetry_api` to be installed:

{:opentelemetry_api, "~> 1.0"}

## Usage

```elixir
defmodule BankRouter do
use Commanded.Commands.Router

middleware Commanded.Middleware.TraceContextPropagator

dispatch [OpenAccount, DepositMoney],
to: BankAccount,
identity: :account_number
end
```
"""

@behaviour Commanded.Middleware

alias Commanded.Middleware.Pipeline

@doc false
def before_dispatch(%Pipeline{} = pipeline) do
case :otel_propagator_text_map.inject([]) do
[] ->
pipeline

headers ->
pipeline
|> maybe_assign("traceparent", List.keyfind(headers, "traceparent", 0))
|> maybe_assign("tracestate", List.keyfind(headers, "tracestate", 0))
Comment thread
yordis marked this conversation as resolved.
end
end

defp maybe_assign(pipeline, _key, nil), do: pipeline

defp maybe_assign(pipeline, key, {_, value}),
do: Pipeline.assign_metadata(pipeline, key, value)

def after_dispatch(pipeline), do: pipeline

def after_failure(pipeline), do: pipeline
end
end
15 changes: 13 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ defmodule Commanded.Mixfile do
{:eventstore, "~> 1.4", optional: true},
{:ecto, "~> 3.11", optional: true},
{:ecto_sql, "~> 3.11", optional: true},
{:opentelemetry_api, "~> 1.0", optional: true},

# Build and test tools
{:benchfella, "~> 0.3", only: :bench},
Expand All @@ -85,7 +86,9 @@ defmodule Commanded.Mixfile do
{:ex_doc, ">= 0.0.0", only: :dev},
{:local_cluster, "~> 2.1", only: :test, runtime: false},
{:mix_test_watch, "~> 1.1", only: :dev},
{:mox, "~> 1.0", only: [:bench, :test]}
{:mox, "~> 1.0", only: [:bench, :test]},
{:opentelemetry, "~> 1.0", only: :test},
{:opentelemetry_exporter, "~> 1.0", only: :test}
]
end

Expand Down Expand Up @@ -234,7 +237,15 @@ defmodule Commanded.Mixfile do
defp dialyzer do
[
ignore_warnings: ".dialyzer_ignore.exs",
plt_add_apps: [:ex_unit, :jason, :mix, :phoenix_pubsub, :ecto, :ecto_sql],
plt_add_apps: [
:ex_unit,
:jason,
:mix,
:phoenix_pubsub,
:ecto,
:ecto_sql,
:opentelemetry_api
],
plt_add_deps: :app_tree,
plt_file: {:no_warn, "priv/plts/commanded.plt"}
]
Expand Down
14 changes: 14 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
%{
"acceptor_pool": {:hex, :acceptor_pool, "1.0.1", "d88c2e8a0be9216cf513fbcd3e5a4beb36bee3ff4168e85d6152c6f899359cdb", [:rebar3], [], "hexpm", "f172f3d74513e8edd445c257d596fc84dbdd56d2c6fa287434269648ae5a421e"},
"backoff": {:hex, :backoff, "1.1.6", "83b72ed2108ba1ee8f7d1c22e0b4a00cfe3593a67dbc792799e8cce9f42f796b", [:rebar3], [], "hexpm", "cf0cfff8995fb20562f822e5cc47d8ccf664c5ecdc26a684cbe85c225f9d7c39"},
"benchfella": {:hex, :benchfella, "0.3.5", "b2122c234117b3f91ed7b43b6e915e19e1ab216971154acd0a80ce0e9b8c05f5", [:mix], [], "hexpm", "23f27cbc482cbac03fc8926441eb60a5e111759c17642bac005c3225f5eb809d"},
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"},
"credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"},
"ctx": {:hex, :ctx, "0.6.0", "8ff88b70e6400c4df90142e7f130625b82086077a45364a78d208ed3ed53c7fe", [:rebar3], [], "hexpm", "a14ed2d1b67723dbebbe423b28d7615eb0bdcba6ff28f2d1f1b0a7e1d4aa5fc2"},
"db_connection": {:hex, :db_connection, "2.8.0", "64fd82cfa6d8e25ec6660cea73e92a4cbc6a18b31343910427b702838c4b33b2", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "008399dae5eee1bf5caa6e86d204dcb44242c82b1ed5e22c881f2c34da201b15"},
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
"dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"},
Expand All @@ -16,18 +19,29 @@
"fsm": {:hex, :fsm, "0.3.1", "087aa9b02779a84320dc7a2d8464452b5308e29877921b2bde81cdba32a12390", [:mix], [], "hexpm", "fbf0d53f89e9082b326b0b5828b94b4c549ff9d1452bbfd00b4d1ac082208e96"},
"gen_stage": {:hex, :gen_stage, "1.3.0", "d3e102a781e30de7bc14f5bacf31c4563f5390733d6d811dd020421803cae3eb", [:mix], [], "hexpm", "02b4263f8937a5039db34637879c3332dd5eb909b463493a0e7ab996dfcf19fa"},
"global_flags": {:hex, :global_flags, "1.0.0", "ee6b864979a1fb38d1fbc67838565644baf632212bce864adca21042df036433", [:rebar3], [], "hexpm", "85d944cecd0f8f96b20ce70b5b16ebccedfcd25e744376b131e89ce61ba93176"},
"gproc": {:hex, :gproc, "0.9.1", "f1df0364423539cf0b80e8201c8b1839e229e5f9b3ccb944c5834626998f5b8c", [:rebar3], [], "hexpm", "905088e32e72127ed9466f0bac0d8e65704ca5e73ee5a62cb073c3117916d507"},
"grpcbox": {:hex, :grpcbox, "0.17.1", "6e040ab3ef16fe699ffb513b0ef8e2e896da7b18931a1ef817143037c454bcce", [:rebar3], [{:acceptor_pool, "~> 1.0.0", [hex: :acceptor_pool, repo: "hexpm", optional: false]}, {:chatterbox, "~> 0.15.1", [hex: :ts_chatterbox, repo: "hexpm", optional: false]}, {:ctx, "~> 0.6.0", [hex: :ctx, repo: "hexpm", optional: false]}, {:gproc, "~> 0.9.1", [hex: :gproc, repo: "hexpm", optional: false]}], "hexpm", "4a3b5d7111daabc569dc9cbd9b202a3237d81c80bf97212fbc676832cb0ceb17"},
"hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"local_cluster": {:hex, :local_cluster, "2.1.0", "1c847d69a927ef5a62db13236f93146e8a42377a9c9a5bb4cac3372cba69d683", [:mix], [{:global_flags, "~> 1.0", [hex: :global_flags, repo: "hexpm", optional: false]}], "hexpm", "dc1c3abb6fef00198dd53c855b39ea80c55b3a8059d8d9f17d50da46b1e3b858"},
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
"makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
"mix_test_watch": {:hex, :mix_test_watch, "1.3.0", "2ffc9f72b0d1f4ecf0ce97b044e0e3c607c3b4dc21d6228365e8bc7c2856dc77", [:mix], [{:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "f9e5edca976857ffac78632e635750d158df14ee2d6185a15013844af7570ffe"},
"mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_ownership": {:hex, :nimble_ownership, "1.0.1", "f69fae0cdd451b1614364013544e66e4f5d25f36a2056a9698b793305c5aa3a6", [:mix], [], "hexpm", "3825e461025464f519f3f3e4a1f9b68c47dc151369611629ad08b636b73bb22d"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
"opentelemetry": {:hex, :opentelemetry, "1.7.0", "20d0f12d3d1c398d3670fd44fd1a7c495dd748ab3e5b692a7906662e2fb1a38a", [:rebar3], [{:opentelemetry_api, "~> 1.5.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}], "hexpm", "a9173b058c4549bf824cbc2f1d2fa2adc5cdedc22aa3f0f826951187bbd53131"},
"opentelemetry_api": {:hex, :opentelemetry_api, "1.5.0", "1a676f3e3340cab81c763e939a42e11a70c22863f645aa06aafefc689b5550cf", [:mix, :rebar3], [], "hexpm", "f53ec8a1337ae4a487d43ac89da4bd3a3c99ddf576655d071deed8b56a2d5dda"},
"opentelemetry_exporter": {:hex, :opentelemetry_exporter, "1.10.0", "972e142392dbfa679ec959914664adefea38399e4f56ceba5c473e1cabdbad79", [:rebar3], [{:grpcbox, ">= 0.0.0", [hex: :grpcbox, repo: "hexpm", optional: false]}, {:opentelemetry, "~> 1.7.0", [hex: :opentelemetry, repo: "hexpm", optional: false]}, {:opentelemetry_api, "~> 1.5.0", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:tls_certificate_check, "~> 1.18", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "33a116ed7304cb91783f779dec02478f887c87988077bfd72840f760b8d4b952"},
"opentelemetry_semantic_conventions": {:hex, :opentelemetry_semantic_conventions, "1.27.0", "acd0194a94a1e57d63da982ee9f4a9f88834ae0b31b0bd850815fe9be4bbb45f", [:mix, :rebar3], [], "hexpm", "9681ccaa24fd3d810b4461581717661fd85ff7019b082c2dff89c7d5b1fc2864"},
"opentelemetry_telemetry": {:hex, :opentelemetry_telemetry, "1.1.2", "410ab4d76b0921f42dbccbe5a7c831b8125282850be649ee1f70050d3961118a", [:mix, :rebar3], [{:opentelemetry_api, "~> 1.3", [hex: :opentelemetry_api, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "641ab469deb181957ac6d59bce6e1321d5fe2a56df444fc9c19afcad623ab253"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"telemetry_registry": {:hex, :telemetry_registry, "0.3.2", "701576890320be6428189bff963e865e8f23e0ff3615eade8f78662be0fc003c", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7ed191eb1d115a3034af8e1e35e4e63d5348851d556646d46ca3d1b4e16bab9"},
"tls_certificate_check": {:hex, :tls_certificate_check, "1.31.0", "9a910b54d8cb96cc810cabf4c0129f21360f82022b20180849f1442a25ccbb04", [:rebar3], [{:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "9d2b41b128d5507bd8ad93e1a998e06d0ab2f9a772af343f4c00bf76c6be1532"},
"uniq": {:hex, :uniq, "0.6.1", "369660ecbc19051be526df3aa85dc393af5f61f45209bce2fa6d7adb051ae03c", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "6426c34d677054b3056947125b22e0daafd10367b85f349e24ac60f44effb916"},
}
151 changes: 151 additions & 0 deletions test/opentelemetry/trace_context_propagator_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
defmodule Commanded.Middleware.TraceContextPropagatorTest do
use ExUnit.Case, async: true

alias Commanded.Middleware.Commands.Fail
alias Commanded.Middleware.Pipeline
alias Commanded.Middleware.TraceContextPropagator

require OpenTelemetry.Tracer, as: Tracer

@trace_id "0af7651916cd43dd8448eb211c80319c"
@span_id "b7ad6b7169203331"
@traceparent "00-#{@trace_id}-#{@span_id}-01"
@tracestate "vendor1=value1,vendor2=value2"

describe "before_dispatch/1" do
test "captures traceparent when span context is active" do
set_trace_context(@traceparent)

pipeline = %Pipeline{command: %Fail{}, metadata: %{}}

result = TraceContextPropagator.before_dispatch(pipeline)

assert result.metadata == %{"traceparent" => @traceparent}
end

test "does not modify metadata when no span is active" do
pipeline = %Pipeline{command: %Fail{}, metadata: %{}}

result = TraceContextPropagator.before_dispatch(pipeline)

assert result.metadata == %{}
end

test "preserves existing metadata" do
set_trace_context(@traceparent)

pipeline = %Pipeline{
command: %Fail{},
metadata: %{"user_id" => "123", "tenant" => "acme"}
}

result = TraceContextPropagator.before_dispatch(pipeline)

assert result.metadata == %{
"user_id" => "123",
"tenant" => "acme",
"traceparent" => @traceparent
}
end

test "does not set tracestate when span has no tracestate" do
set_trace_context(@traceparent)

pipeline = %Pipeline{command: %Fail{}, metadata: %{}}

result = TraceContextPropagator.before_dispatch(pipeline)

assert result.metadata == %{"traceparent" => @traceparent}
end

test "captures tracestate when present in span context" do
set_trace_context(@traceparent, @tracestate)

pipeline = %Pipeline{command: %Fail{}, metadata: %{}}

result = TraceContextPropagator.before_dispatch(pipeline)

assert result.metadata == %{
"traceparent" => @traceparent,
"tracestate" => @tracestate
}
end

test "child span inherits trace_id and tracestate from parent" do
set_trace_context(@traceparent, "vendor=parentvalue")

Tracer.with_span "child.span" do
pipeline = %Pipeline{command: %Fail{}, metadata: %{}}

result = TraceContextPropagator.before_dispatch(pipeline)

# Extract child's span_id (dynamic)
"00-" <> @trace_id <> "-" <> rest = result.metadata["traceparent"]
[child_span_id, "01"] = String.split(rest, "-")

assert result.metadata == %{
"traceparent" => "00-#{@trace_id}-#{child_span_id}-01",
"tracestate" => "vendor=parentvalue"
}

# Span ID should be different from parent's
refute child_span_id == @span_id
end
end

test "does not modify pipeline command" do
set_trace_context(@traceparent)

command = %Fail{}
pipeline = %Pipeline{command: command, metadata: %{}}

result = TraceContextPropagator.before_dispatch(pipeline)

assert result.command == command
assert result.metadata == %{"traceparent" => @traceparent}
end

test "does not modify pipeline assigns" do
set_trace_context(@traceparent)

pipeline = %Pipeline{
command: %Fail{},
metadata: %{},
assigns: %{existing: "value"}
}

result = TraceContextPropagator.before_dispatch(pipeline)

assert result.assigns == %{existing: "value"}
assert result.metadata == %{"traceparent" => @traceparent}
end
end

describe "after_dispatch/1" do
test "returns pipeline unchanged" do
pipeline = %Pipeline{command: %Fail{}, metadata: %{"test" => "value"}}

assert TraceContextPropagator.after_dispatch(pipeline) == pipeline
end
end

describe "after_failure/1" do
test "returns pipeline unchanged" do
pipeline = %Pipeline{command: %Fail{}, metadata: %{"test" => "value"}}

assert TraceContextPropagator.after_failure(pipeline) == pipeline
end
end

defp set_trace_context(traceparent) when is_binary(traceparent) do
:otel_propagator_text_map.extract([{"traceparent", traceparent}])
end

defp set_trace_context(traceparent, tracestate)
when is_binary(traceparent) and is_binary(tracestate) do
:otel_propagator_text_map.extract([
{"traceparent", traceparent},
{"tracestate", tracestate}
])
end
end