diff --git a/README.md b/README.md index e552e38f..d3122047 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,24 @@ defmodule MyExternalService do end ``` +When an "Other" Transaction is started automatically but the trace headers arrive some other way, such as in Oban job metadata or a message attribute, connect it to the trace with `NewRelic.accept_distributed_trace_headers/1`: + +```elixir +defmodule MyWorker do + use Oban.Worker + + def enqueue(args) do + Oban.insert(new(args, meta: %{dt_headers: NewRelic.distributed_trace_headers(:other)})) + end + + @impl Oban.Worker + def perform(%Oban.Job{meta: meta}) do + NewRelic.accept_distributed_trace_headers(meta["dt_headers"]) + # ... + end +end +``` + #### Mix Tasks `NewRelic.Instrumented.Mix.Task` To enable the agent and record an Other Transaction during a `Mix.Task`, simply `use NewRelic.Instrumented.Mix.Task`. This will ensure the agent is properly started, records a Transaction, and is shut down. diff --git a/examples/apps/oban_example/README.md b/examples/apps/oban_example/README.md index 34b04aa8..4efb5684 100644 --- a/examples/apps/oban_example/README.md +++ b/examples/apps/oban_example/README.md @@ -1,3 +1,3 @@ # ObanExample -An example app demonstrating auto-instrumentation of Oban +An example app demonstrating auto-instrumentation of Oban, including connecting a job to the Distributed Trace that enqueued it with `NewRelic.accept_distributed_trace_headers/1`. diff --git a/examples/apps/oban_example/lib/oban_example/worker.ex b/examples/apps/oban_example/lib/oban_example/worker.ex index 8a5cad47..05a2fb40 100644 --- a/examples/apps/oban_example/lib/oban_example/worker.ex +++ b/examples/apps/oban_example/lib/oban_example/worker.ex @@ -6,7 +6,9 @@ defmodule ObanExample.Worker do {:error, message} end - def perform(%Oban.Job{args: _args}) do + def perform(%Oban.Job{meta: meta}) do + NewRelic.accept_distributed_trace_headers(meta["dt_headers"]) + Process.sleep(15 + :rand.uniform(50)) :ok end diff --git a/examples/apps/oban_example/test/oban_example_test.exs b/examples/apps/oban_example/test/oban_example_test.exs index d1fc9393..211ecbfd 100644 --- a/examples/apps/oban_example/test/oban_example_test.exs +++ b/examples/apps/oban_example/test/oban_example_test.exs @@ -37,6 +37,32 @@ defmodule ObanExampleTest do assert event[:"oban.job.tags"] == "foo,bar" end + test "connects a job to the Distributed Trace that enqueued it" do + TestHelper.restart_harvest_cycle(Collector.TransactionEvent.HarvestCycle) + + dt_headers = + Task.async(fn -> + NewRelic.start_transaction("Test", "Origin") + NewRelic.distributed_trace_headers(:other) + end) + |> Task.await() + + ObanExample.Worker.new(%{some: "args"}, meta: %{dt_headers: dt_headers}) + |> Oban.insert() + + events = TestHelper.gather_harvest(Collector.TransactionEvent.Harvester) + + origin = TestHelper.find_event(events, "OtherTransaction/Test/Origin") + + job = + TestHelper.find_event(events, "OtherTransaction/Oban/default/ObanExample.Worker/perform") + + assert job[:traceId] == origin[:traceId] + assert job[:parentId] == origin[:guid] + assert job[:"parent.type"] == "App" + assert job[:"parent.transportType"] == "Other" + end + test "instruments a failed job" do TestHelper.restart_harvest_cycle(Collector.Metric.HarvestCycle) TestHelper.restart_harvest_cycle(Collector.TransactionEvent.HarvestCycle) diff --git a/lib/new_relic.ex b/lib/new_relic.ex index 5598f5d0..2ca2cd12 100644 --- a/lib/new_relic.ex +++ b/lib/new_relic.ex @@ -277,11 +277,44 @@ defmodule NewRelic do * Call `distributed_trace_headers` immediately before making the request since calling the function marks the "start" time of the request. + * Returns an empty list (or map) when the agent is not enabled or + when called outside of a Transaction. """ @spec distributed_trace_headers(:http) :: [{key :: String.t(), value :: String.t()}] @spec distributed_trace_headers(:other) :: map() defdelegate distributed_trace_headers(type), to: NewRelic.DistributedTrace + @doc """ + Connect the current "Other" Transaction to an existing Distributed Trace. + + Use this when an "Other" Transaction is started automatically (for example by + the Oban instrumentation) but the trace headers arrive some other way, such as + in job metadata or a message attribute. The headers can be W3C "traceparent" + and "tracestate" headers or another New Relic agent's "newrelic" header. + + ```elixir + # When enqueueing + Oban.insert(MyWorker.new(args, meta: %{dt_headers: NewRelic.distributed_trace_headers(:other)})) + + # When performing + def perform(%Oban.Job{meta: meta}) do + NewRelic.accept_distributed_trace_headers(meta["dt_headers"]) + # ... + end + ``` + + ## Notes + + * Web Transactions read inbound headers automatically, so this is only + needed for "Other" Transactions. + * Call this as early as possible in the Transaction so all Spans are linked. + * Ignored when called outside of a Transaction, when the headers can't be + decoded, when the Transaction already accepted inbound headers, or when the + agent is disabled. + """ + @spec accept_distributed_trace_headers(headers :: map()) :: :ok | :ignore + defdelegate accept_distributed_trace_headers(headers), to: NewRelic.DistributedTrace + @type name :: String.t() | {primary_name :: String.t(), secondary_name :: String.t()} @doc """ diff --git a/lib/new_relic/distributed_trace.ex b/lib/new_relic/distributed_trace.ex index d1da78f8..6b2e19f0 100644 --- a/lib/new_relic/distributed_trace.ex +++ b/lib/new_relic/distributed_trace.ex @@ -12,7 +12,7 @@ defmodule NewRelic.DistributedTrace do def start(type, headers \\ %{}) def start(:http, headers) do - if NewRelic.Config.feature?(:distributed_tracing) do + if NewRelic.Config.enabled?() && NewRelic.Config.feature?(:distributed_tracing) do determine_context(headers) |> track_transaction(transport_type: "HTTP") end @@ -21,7 +21,7 @@ defmodule NewRelic.DistributedTrace do end def start(:other, headers) do - if NewRelic.Config.feature?(:distributed_tracing) do + if NewRelic.Config.enabled?() && NewRelic.Config.feature?(:distributed_tracing) do determine_context(headers) |> track_transaction(transport_type: "Other") end @@ -29,14 +29,36 @@ defmodule NewRelic.DistributedTrace do :ok end + def accept_distributed_trace_headers(headers) do + with true <- NewRelic.Config.enabled?() && NewRelic.Config.feature?(:distributed_tracing), + true <- Transaction.Sidecar.tracking?(), + false <- accepted_inbound_headers?(get_tracing_context()), + %Context{} = context <- extract_context(normalize_headers(headers)) do + track_transaction(context, transport_type: "Other") + :ok + else + _ -> :ignore + end + end + + defp accepted_inbound_headers?(%Context{source: source}) when source != :new, do: true + defp accepted_inbound_headers?(_), do: false + + defp normalize_headers(headers) when is_map(headers), do: headers + + defp normalize_headers(headers) when is_list(headers), + do: Map.new(headers, fn {key, value} -> {to_string(key), value} end) + + defp normalize_headers(_), do: %{} + defp determine_context(headers) do - case accept_distributed_trace_headers(headers) do + case extract_context(headers) do %Context{} = context -> context _ -> generate_new_context() end end - defp accept_distributed_trace_headers(headers) do + defp extract_context(headers) do w3c_headers(headers) || newrelic_header(headers) || :no_payload end @@ -63,25 +85,24 @@ defmodule NewRelic.DistributedTrace do end def distributed_trace_headers(:http) do - case get_tracing_context() do - nil -> - [] - - context -> - context = %{ - context - | span_guid: get_current_span_guid(), - timestamp: System.system_time(:millisecond) - } - - nr_header = NewRelic.DistributedTrace.NewRelicContext.generate(context) - {traceparent, tracestate} = NewRelic.DistributedTrace.W3CTraceContext.generate(context) - - [ - {@nr_header, nr_header}, - {@w3c_traceparent, traceparent}, - {@w3c_tracestate, tracestate} - ] + with true <- NewRelic.Config.enabled?(), + %Context{} = context <- get_tracing_context() do + context = %{ + context + | span_guid: get_current_span_guid(), + timestamp: System.system_time(:millisecond) + } + + nr_header = NewRelic.DistributedTrace.NewRelicContext.generate(context) + {traceparent, tracestate} = NewRelic.DistributedTrace.W3CTraceContext.generate(context) + + [ + {@nr_header, nr_header}, + {@w3c_traceparent, traceparent}, + {@w3c_tracestate, tracestate} + ] + else + _ -> [] end end diff --git a/lib/new_relic/transaction/sidecar.ex b/lib/new_relic/transaction/sidecar.ex index 99c492d5..3ba88bb6 100644 --- a/lib/new_relic/transaction/sidecar.ex +++ b/lib/new_relic/transaction/sidecar.ex @@ -83,13 +83,21 @@ defmodule NewRelic.Transaction.Sidecar do end def trace_context(context) do - :ets.insert(__MODULE__.ContextStore, {{:context, get_sidecar()}, context}) + case get_sidecar() do + sidecar when is_pid(sidecar) -> + :ets.insert(__MODULE__.ContextStore, {{:context, sidecar}, context}) + + _ -> + :no_sidecar + end end def trace_context() do - case :ets.lookup(__MODULE__.ContextStore, {:context, get_sidecar()}) do - [{_, value}] -> value - [] -> nil + with sidecar when is_pid(sidecar) <- get_sidecar(), + [{_, value}] <- :ets.lookup(__MODULE__.ContextStore, {:context, sidecar}) do + value + else + _ -> nil end end diff --git a/mix.lock b/mix.lock index 72ea6189..b74318ea 100644 --- a/mix.lock +++ b/mix.lock @@ -1,10 +1,10 @@ %{ "absinthe": {:hex, :absinthe, "1.11.0", "dee6dfa57f86d52b25d8b0f925585b6b047d4921ac703c1075b6e7b8bc2fc83c", [:mix], [{:dataloader, "~> 1.0.0 or ~> 2.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}, {:opentelemetry_process_propagator, "~> 0.2.1 or ~> 0.3", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "39b3b4b6e3eb405fa98b449feef0dacff81d89bf01c2866cfa513616c5530ba6"}, - "bandit": {:hex, :bandit, "1.6.4", "59cbc8e02f84fcad967bfed6b8a8261821c93a7ec4f835b46d1846b1120a91ec", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "8e156c009a77bb100fd78d5408684d01df1526f549c42614f8f9f27f44f1f7a7"}, - "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"}, - "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, + "bandit": {:hex, :bandit, "1.12.5", "af205a8e550f304caae09a97d29fd3c79a7f337526ea7cd772d2ff11d2f7c800", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.5", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "c5684ca062fa407cac115aec3256383f3e2ec9fdced7904d59cf5a7bb7ed6181"}, + "castore": {:hex, :castore, "1.0.21", "0a0e8330dc267a40a3b7ad86d39302764bb71758172904e6a59d5ad6443ce307", [:mix], [], "hexpm", "e42e22723e25dbd46876d056a03f685513d6e98f6b5e555dc551321decd76c5c"}, + "cowboy": {:hex, :cowboy, "2.19.0", "78b9d92d25a23e56d7341040b67946c04067cd77de966e2372865f673a8e6f59", [:make, :rebar3], [{:cowlib, ">= 2.20.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "986dae81f99fcb78ef2d8efc21d738cee189410840eeaf32eca84ada81dcf6d4"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, - "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, + "cowlib": {:hex, :cowlib, "2.20.0", "bb525377ba634cd6d68bac7bff5d98571f738806139d335daca827717c5dc172", [:make, :rebar3], [], "hexpm", "7d41a0dd2c093041ff3779ac5fe8a1585a68ec7cb2dd1de0536bdd2452fd7ba1"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.4.1", "6c0fbede12fb122ba685e9ab41c6a40c129e322b3aa192f9e072e61f3a6ffaf2", [:mix], [], "hexpm", "7e618897933a8455f19a727d7c5e50a2c071a544b700e5e724298ecb4340187f"}, "earmark_parser": {:hex, :earmark_parser, "1.4.42", "f23d856f41919f17cd06a493923a722d87a2d684f143a1e663c04a2b93100682", [:mix], [], "hexpm", "6915b6ca369b5f7346636a2f41c6a6d78b5af419d61a611079189233358b8b8b"}, @@ -12,27 +12,27 @@ "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, "ex_doc": {:hex, :ex_doc, "0.36.1", "4197d034f93e0b89ec79fac56e226107824adcce8d2dd0a26f5ed3a95efc36b1", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d7d26a7cf965dacadcd48f9fa7b5953d7d0cfa3b44fa7a65514427da44eafd89"}, "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, - "hpax": {:hex, :hpax, "1.0.2", "762df951b0c399ff67cc57c3995ec3cf46d696e41f0bba17da0518d94acd4aac", [:mix], [], "hexpm", "2f09b4c1074e0abd846747329eaa26d535be0eb3d189fa69d812bfb8bfefd32f"}, - "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "hpax": {:hex, :hpax, "1.0.4", "777de5d433b0fbdc7c418159c8055910faa8047ffdb3d6b31098d2a46cd7685c", [:mix], [], "hexpm", "afc7cb142ebcc2d01ce7816190b98ce5dd49e799111b24249f3443d730f377ca"}, + "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"}, "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.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, - "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, - "mint": {:hex, :mint, "1.6.2", "af6d97a4051eee4f05b5500671d47c3a67dac7386045d87a904126fd4bbcea2e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5ee441dffc1892f1ae59127f74afe8fd82fda6587794278d924e4d90ea3d63f9"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mint": {:hex, :mint, "1.10.0", "85af3353bfc504f5bdfe494bd92b8490f87a306dc659ee1ad0af435107e898dc", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "8b16fb72aaa7531d206a1f05e4cc85509ba531ccec7a17a22736c9c95cbb24d1"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "oban": {:hex, :oban, "2.18.3", "1608c04f8856c108555c379f2f56bc0759149d35fa9d3b825cb8a6769f8ae926", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "36ca6ca84ef6518f9c2c759ea88efd438a3c81d667ba23b02b062a0aa785475e"}, - "phoenix": {:hex, :phoenix, "1.7.18", "5310c21443514be44ed93c422e15870aef254cf1b3619e4f91538e7529d2b2e4", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1797fcc82108442a66f2c77a643a62980f342bfeb63d6c9a515ab8294870004e"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix": {:hex, :phoenix, "1.8.9", "a63ed0962ed5b903b146dab0ae8eb8387fe478f8171a5e26d56a165f35996fe1", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "3477e2dd5a4f61820341169031bdfe21275f659923bea9c5c0ea2aa1c3fcc046"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, - "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"}, - "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, - "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, + "plug": {:hex, :plug, "1.20.3", "56c480c633ec2ce10140e236e15233bf576e1d323887d7c96711bd02ab5160db", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "be266aee1b8536ef6409d58cf39a3121319f0ec47cfa1b24024485aa0e76ad76"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.9.0", "87e21e0d9054ced99c36d128f49e3ea2cd8b745fffb97de50bff99706087af4f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2002bafba4f3a45b55a58e68d70211b153a7ed18d37edb1ceb6e96e7a92c422e"}, + "plug_crypto": {:hex, :plug_crypto, "2.2.0", "144014737daaf485407f5ed77daeaad74d651b216a28c87543f8cc7043f8efc8", [:mix], [], "hexpm", "83a95744ab1c75876542b6fab135fcc176280e0f301a111c1f757fddcec95d2c"}, + "ranch": {:hex, :ranch, "2.3.0", "7de7b041a9a6a5091a3aa5898d66c0564be671d87db4f9d63b1b5ee775b097df", [:make, :rebar3], [], "hexpm", "6168ec49409d982f7cfbd83dd083144f6cbe67caa4036551d2f0a3ad67c9d023"}, "redix": {:hex, :redix, "1.5.2", "ab854435a663f01ce7b7847f42f5da067eea7a3a10c0a9d560fa52038fd7ab48", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:nimble_options, "~> 0.5.0 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "78538d184231a5d6912f20567d76a49d1be7d3fca0e1aaaa20f4df8e1142dcb8"}, "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"}, - "thousand_island": {:hex, :thousand_island, "1.3.9", "095db3e2650819443e33237891271943fad3b7f9ba341073947581362582ab5a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25ab4c07badadf7f87adb4ab414e0ed374e5f19e72503aa85132caa25776e54f"}, + "thousand_island": {:hex, :thousand_island, "1.5.0", "f50a213cac97262b6d5ebb85745aa2c00fec1413191e6e66834788d45425cecb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "708923d40523e43cf99041ab37a0d4b0ec426ac6438fa3716ab23d919eaeb412"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, - "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, + "websock_adapter": {:hex, :websock_adapter, "0.6.0", "73db5ab8aaefd1a876a97ce3e6afc96562625de69ef17a4e04426e034849d0b8", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "50021a85bce8f203b086705d9e0c5415e2c7eb05d319111b0428fe71f9934617"}, } diff --git a/test/distributed_trace_test.exs b/test/distributed_trace_test.exs index c7fe03a6..85a06fbc 100644 --- a/test/distributed_trace_test.exs +++ b/test/distributed_trace_test.exs @@ -202,6 +202,128 @@ defmodule DistributedTraceTest do |> Task.await() end + test "does not generate outbound headers when the agent is disabled" do + TestHelper.run_with(:nr_config, harvest_enabled: false) + + Task.async(fn -> + NewRelic.start_transaction("Category", "Name") + + assert [] == NewRelic.distributed_trace_headers(:http) + assert %{} == NewRelic.distributed_trace_headers(:other) + end) + |> Task.await() + end + + test "does not track inbound headers when the agent is disabled" do + TestHelper.run_with(:nr_config, harvest_enabled: false) + headers = %{@dt_header => generate_inbound_payload(:browser)} + + Task.async(fn -> + NewRelic.start_transaction("Category", "Name", headers) + + assert nil == DistributedTrace.get_tracing_context() + end) + |> Task.await() + end + + test "trace context started outside a transaction does not leak to other processes" do + headers = %{@dt_header => generate_inbound_payload(:browser)} + + Task.async(fn -> DistributedTrace.start(:other, headers) end) + |> Task.await() + + Task.async(fn -> + assert nil == DistributedTrace.get_tracing_context() + assert [] == NewRelic.distributed_trace_headers(:http) + end) + |> Task.await() + end + + test "accept inbound DT headers inside an existing Other transaction" do + headers = %{@dt_header => generate_inbound_payload(:browser)} + + Task.async(fn -> + NewRelic.start_transaction("Category", "Name") + assert :ok = NewRelic.accept_distributed_trace_headers(headers) + + outbound = NewRelic.distributed_trace_headers(:other) + context = DistributedTrace.NewRelicContext.decode(Map.get(outbound, @dt_header)) + + assert context.trace_id == "d6b4ba0c3a712ca" + end) + |> Task.await() + end + + test "accept DT headers in the list format returned for :http" do + outbound = + Task.async(fn -> + NewRelic.start_transaction("Category", "Origin") + NewRelic.distributed_trace_headers(:http) + end) + |> Task.await() + + {_, traceparent} = List.keyfind(outbound, "traceparent", 0) + [_, trace_id, _, _] = String.split(traceparent, "-") + + Task.async(fn -> + NewRelic.start_transaction("Category", "Destination") + assert :ok = NewRelic.accept_distributed_trace_headers(outbound) + + assert %DistributedTrace.Context{trace_id: ^trace_id} = DistributedTrace.get_tracing_context() + end) + |> Task.await() + end + + test "accepting DT headers is ignored outside a transaction" do + headers = %{@dt_header => generate_inbound_payload(:browser)} + + assert :ignore = NewRelic.accept_distributed_trace_headers(headers) + assert nil == DistributedTrace.get_tracing_context() + end + + test "accepting DT headers is ignored once inbound headers were already accepted" do + headers = %{@dt_header => generate_inbound_payload(:browser)} + + w3c_headers = %{ + "traceparent" => "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", + "tracestate" => "190@nr=0-0-190-2827902-b7ad6b7169203331-e8b91a159289ff74-1-0.789-1563574856827" + } + + Task.async(fn -> + NewRelic.start_transaction("Category", "Name", headers) + assert :ignore = NewRelic.accept_distributed_trace_headers(w3c_headers) + + assert %DistributedTrace.Context{trace_id: "d6b4ba0c3a712ca"} = DistributedTrace.get_tracing_context() + end) + |> Task.await() + end + + test "accepting undecodable DT headers keeps the existing context" do + Task.async(fn -> + NewRelic.start_transaction("Category", "Name") + %DistributedTrace.Context{trace_id: trace_id} = DistributedTrace.get_tracing_context() + + assert :ignore = NewRelic.accept_distributed_trace_headers(%{@dt_header => "garbage"}) + assert :ignore = NewRelic.accept_distributed_trace_headers(nil) + + assert %DistributedTrace.Context{trace_id: ^trace_id} = DistributedTrace.get_tracing_context() + end) + |> Task.await() + end + + test "accepting DT headers is ignored when the agent is disabled" do + TestHelper.run_with(:nr_config, harvest_enabled: false) + headers = %{@dt_header => generate_inbound_payload(:browser)} + + Task.async(fn -> + NewRelic.start_transaction("Category", "Name") + + assert :ignore = NewRelic.accept_distributed_trace_headers(headers) + assert nil == DistributedTrace.get_tracing_context() + end) + |> Task.await() + end + test "Start an Other transaction with inbound DT headers" do headers = %{@dt_header => generate_inbound_payload(:browser)}