Repository: newrelic/elixir_agent (elixir)

Issue:
Long poll requests
<!--
Thanks for your interest in the Elixir Agent

A quick note: This is an open source project, not a New Relic product.

* We want to improve and grow the Agent in ways that can benefit the majority of users
* Features that are specific to just one app are unlikely to make it in
* Instrumentation particular to a library or framework are probably a better fit for their own package which depends on this Agent

-->

**Is your feature request related to a problem? Please describe.**
Currently we are stuck with 1.22.6 version of https://github.com/newrelic/elixir_agent because in its later versions it somehow started tracking time for long poll fallback for the WS connections and that skewed the main graph.

![img](https://user-images.githubusercontent.com/142624/141424981-ae92a3ca-0e14-40c6-917e-6fa02294c55d.png)

**Describe the solution you'd like**
This is a tough one. I mean the long polling requests do take very long time compared to usual requests, and that's totally normal. But the graphs look bad if we plot long polling requests alongside normal requests. 
So ignoring long poll requests looks best to me.

But maybe there is a better solution that I'm just not aware of.

**Describe alternatives you've considered**
Pinning the hex package version worked for me with Phoenix 1.5, with Phoenix 1.6 I had some issues so I might be forced to fork the repo to lax the dependency requirements.


Pull request description:
Support ignoring web transaction paths via configuration
This PR enables Web Transactions to be ignored by path using configuration

Common use cases would be ignoring health checks, or ignoring Phoenix `longpoll` requests

Closes #386 

Patch:
diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml
index b8c996c..1ff868d 100644
--- a/.github/workflows/elixir.yml
+++ b/.github/workflows/elixir.yml
@@ -17,18 +17,16 @@ jobs:
     strategy:
       matrix:
         include:
-          - elixir: '1.15'
+          - elixir: '1.18'
+            otp: '27'
+          - elixir: '1.17'
+            otp: '27'
+          - elixir: '1.16'
             otp: '26'
           - elixir: '1.15'
-            otp: '25'
-          - elixir: '1.14'
             otp: '26'
-          - elixir: '1.13'
+          - elixir: '1.14'
             otp: '25'
-          - elixir: '1.12'
-            otp: '24'
-          - elixir: '1.11'
-            otp: '23'
 
     steps:
     - name: Checkout
@@ -84,14 +82,14 @@ jobs:
     strategy:
       matrix:
         include:
-          - elixir: '1.15'
+          - elixir: '1.18'
+            otp: '27'
+          - elixir: '1.17'
+            otp: '27'
+          - elixir: '1.16'
             otp: '26'
-          - elixir: '1.14'
+          - elixir: '1.15'
             otp: '26'
-          - elixir: '1.13'
-            otp: '25'
-          - elixir: '1.12'
-            otp: '24'
 
     defaults:
       run:
diff --git a/lib/new_relic.ex b/lib/new_relic.ex
index 7f56bd6..a894415 100644
--- a/lib/new_relic.ex
+++ b/lib/new_relic.ex
@@ -9,7 +9,7 @@ defmodule NewRelic do
   The first segment will be treated as the Transaction namespace,
   and commonly contains the name of the framework.
 
-  **Notes:**
+  ## Notes
   * At least 2 segments are required to light up the Transactions UI in APM
 
   In the following example, you will see `/custom/transaction/name`
@@ -43,7 +43,7 @@ defmodule NewRelic do
     # "list.length" => 3
   ```
 
-  **Notes:**
+  ## Notes
   * Nested Lists and Maps are truncated at 10 items since there are a limited number
   of attributes that can be reported on Transaction events
   """
@@ -59,23 +59,26 @@ defmodule NewRelic do
   (ie: Not a "Web" Transaction). The first argument will be considered
   the "category", the second is the "name".
 
-  Examples:
+  ## Examples
 
   ```elixir
   NewRelic.start_transaction("GenStage", "MyConsumer/EventType")
   NewRelic.start_transaction("Task", "TaskName")
   ```
 
-  **Notes:**
+  > #### Warning {: .error}
+  >
+  > * You can't start a new transaction within an existing one. Any process
+  > spawned inside a transaction belongs to that transaction.
+  > * Do _not_ use this for processes that live a very long time, doing so
+  > will risk a memory leak tracking attributes in the transaction!
+
+  ## Notes
 
   * Don't use this to track Web Transactions - Plug based HTTP servers
   are auto-instrumented based on `telemetry` events.
-  * Do _not_ use this for processes that live a very long time, doing so
-  will risk a memory leak tracking attributes in the transaction!
-  * You can't start a new transaction within an existing one. Any process
-  spawned inside a transaction belongs to that transaction.
   * If multiple transactions are started in the same Process, you must
-  call `NewRelic.stop_transaction()` to mark the end of the transaction.
+  call `NewRelic.stop_transaction/0` to mark the end of the transaction.
   """
   @spec start_transaction(String.t(), String.t()) :: :ok
   defdelegate start_transaction(category, name), to: NewRelic.OtherTransaction
@@ -84,7 +87,7 @@ defmodule NewRelic do
   Stop an "Other" Transaction.
 
   If multiple transactions are started in the same Process, you must
-  call `NewRelic.stop_transaction()` to mark the end of the transaction.
+  call `NewRelic.stop_transaction/0` to mark the end of the transaction.
   """
   @spec stop_transaction() :: :ok
   defdelegate stop_transaction(), to: NewRelic.OtherTransaction
@@ -93,10 +96,10 @@ defmodule NewRelic do
   Define an "Other" transaction with the given block. The return value of
   the block is returned.
 
-  See `start_transaction` and `stop_transaction` for more details about
+  See `start_transaction/2` and `stop_transaction/0` for more details about
   Transactions.
 
-  Example:
+  ## Example
 
   ```elixir
   defmodule Worker do
@@ -122,6 +125,8 @@ defmodule NewRelic do
   @doc """
   Call within a transaction to prevent it from reporting.
 
+  ## Example
+
   ```elixir
   def index(conn, _) do
     NewRelic.ignore_transaction()
@@ -139,20 +144,20 @@ defmodule NewRelic do
   @doc """
   Advanced:
   Return a Transaction reference that can be used to manually connect a
-  process to a Transaction with `NewRelic.connect_to_transaction()`
+  process to a Transaction with `NewRelic.connect_to_transaction/1`
   """
   defdelegate get_transaction(), to: NewRelic.Transaction.Reporter
 
   @doc """
   Advanced:
   Call to manually connect the current process to a Transaction. Pass in a reference
-  returned by `NewRelic.get_transaction()`
+  returned by `NewRelic.get_transaction/0`
 
   Only use this when there is no auto-discoverable connection (ex: the process was
   spawned without links or the logic is within a message handling callback).
 
   This connection will persist until the process exits or
-  `NewRelic.disconnect_from_transaction()` is called.
+  `NewRelic.disconnect_from_transaction/0` is called.
   """
   defdelegate connect_to_transaction(ref), to: NewRelic.Transaction.Reporter
 
@@ -166,9 +171,22 @@ defmodule NewRelic do
   Store information about the type of work the current span is doing.
 
   Options:
-  - `:generic, custom: attributes`
-  - `:http, url: url, method: method, component: component`
-  - `:datastore, statement: statement, instance: instance, address: address, hostname: hostname, component: component`
+  - `:type`
+    - `:generic` - Pass custom attributes
+    - `:http` - Pass attributes `:url`, `:method`, `:component`
+    - `:datastore` - Pass attrributes `:statement`, `:instance`, `:address`, `:hostname`,
+      `:component`
+
+  ## Examples
+
+  ```elixir
+  NewRelic.set_span(:generic, some: "attribute")
+
+  NewRelic.set_span(:http, url: "https://elixir-lang.org", method: "GET", component: "HTTPoison")
+
+  NewRelic.set_span(:datastore, statement: statement, instance: instance, address: address,
+  hostname: hostname, component: component)
+  ```
   """
   defdelegate set_span(type, attributes), to: NewRelic.DistributedTrace
 
@@ -182,14 +200,17 @@ defmodule NewRelic do
   HTTPoison.get(url, ["x-api-key": "secret"] ++ NewRelic.distributed_trace_headers(:http))
   ```
 
-  **Notes:**
+  ## Notes
 
-  * Call `NewRelic.distributed_trace_headers` immediately before making the
+  * Call `distributed_trace_headers` immediately before making the
   request since calling the function marks the "start" time of the request.
   """
   defdelegate distributed_trace_headers(type), to: NewRelic.DistributedTrace
 
-  @deprecated "Use distributed_trace_headers/1 instead"
+  @doc """
+  See: `NewRelic.distributed_trace_headers/1`
+  """
+  @deprecated "Use distributed_trace_headers instead"
   defdelegate create_distributed_trace_payload(type),
     to: NewRelic.DistributedTrace,
     as: :distributed_trace_headers
@@ -222,6 +243,8 @@ defmodule NewRelic do
   @doc """
   Report a Custom event to NRDB.
 
+  ## Example
+
   ```elixir
   NewRelic.report_custom_event("EventType", %{"foo" => "bar"})
   ```
@@ -233,6 +256,8 @@ defmodule NewRelic do
   Report a Dimensional Metric.
   Valid types: `:count`, `:gauge`, and `:summary`.
 
+  ## Example
+
   ```elixir
   NewRelic.report_dimensional_metric(:count, "my_metric_name", 1, %{some: "attributes"})
   ```
@@ -243,6 +268,8 @@ defmodule NewRelic do
   @doc """
   Report a Custom metric.
 
+  ## Example
+
   ```elixir
   NewRelic.report_custom_metric("My/Metric", 123)
   ```
@@ -253,6 +280,8 @@ defmodule NewRelic do
   @doc """
   Increment a Custom metric.
 
+  ## Example
+
   ```elixir
   NewRelic.increment_custom_metric("My/Metric")
   ```
diff --git a/lib/new_relic/config.ex b/lib/new_relic/config.ex
index 1819619..9efe40a 100644
--- a/lib/new_relic/config.ex
+++ b/lib/new_relic/config.ex
@@ -96,6 +96,27 @@ defmodule NewRelic.Config do
   def labels,
     do: get(:labels)
 
+  @doc """
+  An optional list of paths that will be used to ignore Web Transactions.
+
+  Individual items can be Strings or Regexes.
+
+  Common use cases include:
+  * Ignore health checks: `"/health"`
+  * Ignore Phoenix longpoll http requests: `"/live/longpoll"`
+
+  Example:
+  ```
+  config :new_relic_agent,
+    ignore_paths: [
+      "/health",
+      ~r/longpoll/
+    ]
+  ```
+  """
+  def ignore_paths,
+    do: get(:ignore_paths)
+
   @doc """
   Some Agent features can be toggled via configuration.
 
@@ -115,6 +136,8 @@ defmodule NewRelic.Config do
 
   * `:plug_instrumentation_enabled` (default `true`)
     * Controls all Plug instrumentation
+  * `:distributed_tracing` (default `true`)
+    * Controls Distributed Tracking
   * `:ecto_instrumentation_enabled` (default `true`)
     * Controls all Ecto instrumentation
   * `:redix_instrumentation_enabled` (default `true`)
@@ -139,6 +162,10 @@ defmodule NewRelic.Config do
     get(:features, :db_query_collection)
   end
 
+  def feature?(:distributed_tracing) do
+    get(:features, :distributed_tracing)
+  end
+
   def feature?(:plug_instrumentation) do
     get(:features, :plug_instrumentation)
   end
diff --git a/lib/new_relic/distributed_trace.ex b/lib/new_relic/distributed_trace.ex
index a80f91a..8a2cb0b 100644
--- a/lib/new_relic/distributed_trace.ex
+++ b/lib/new_relic/distributed_trace.ex
@@ -9,9 +9,20 @@ defmodule NewRelic.DistributedTrace do
   alias NewRelic.Harvest.Collector.AgentRun
   alias NewRelic.Transaction
 
+  def start(type, headers \\ %{})
+
   def start(:http, headers) do
-    determine_context(:http, headers)
-    |> track_transaction(transport_type: "HTTP")
+    if NewRelic.Config.feature?(:distributed_tracing) do
+      determine_context(:http, headers)
+      |> track_transaction(transport_type: "HTTP")
+    end
+  end
+
+  def start(:other, _) do
+    if NewRelic.Config.feature?(:distributed_tracing) do
+      generate_new_context()
+      |> track_transaction(transport_type: "Other")
+    end
   end
 
   defp determine_context(:http, headers) do
diff --git a/lib/new_relic/harvest/harvest_cycle.ex b/lib/new_relic/harvest/harvest_cycle.ex
index d90a589..a27818d 100644
--- a/lib/new_relic/harvest/harvest_cycle.ex
+++ b/lib/new_relic/harvest/harvest_cycle.ex
@@ -140,7 +140,10 @@ defmodule NewRelic.Harvest.HarvestCycle do
 
   defp stop_harvest_cycle(timer), do: timer && Process.cancel_timer(timer)
 
-  defp trigger_harvest_cycle(%{lookup_module: lookup_module, harvest_cycle_key: harvest_cycle_key}) do
+  defp trigger_harvest_cycle(%{
+         lookup_module: lookup_module,
+         harvest_cycle_key: harvest_cycle_key
+       }) do
     harvest_cycle = lookup_module.lookup(harvest_cycle_key) || 60_000
     Process.send_after(self(), :harvest_cycle, harvest_cycle)
   end
diff --git a/lib/new_relic/init.ex b/lib/new_relic/init.ex
index 81882b8..6f1a618 100644
--- a/lib/new_relic/init.ex
+++ b/lib/new_relic/init.ex
@@ -39,6 +39,7 @@ defmodule NewRelic.Init do
       region_prefix: region_prefix,
       automatic_attributes: determine_automatic_attributes(),
       labels: determine_config(:labels) |> parse_labels(),
+      ignore_paths: determine_config(:ignore_paths, []),
       telemetry_hosts: telemetry_hosts,
       trace_mode: determine_trace_mode()
     })
@@ -71,6 +72,11 @@ defmodule NewRelic.Init do
             "NEW_RELIC_DB_QUERY_COLLECTION_ENABLED",
             :db_query_collection_enabled
           ),
+      distributed_tracing:
+        determine_feature(
+          "NEW_RELIC_DISTRIBUTED_TRACING_ENABLED",
+          :distributed_tracing_enabled
+        ),
       ecto_instrumentation:
         determine_feature(
           "NEW_RELIC_ECTO_INSTRUMENTATION_ENABLED",
diff --git a/lib/new_relic/other_transaction.ex b/lib/new_relic/other_transaction.ex
index c2a5534..9ab043c 100644
--- a/lib/new_relic/other_transaction.ex
+++ b/lib/new_relic/other_transaction.ex
@@ -3,9 +3,7 @@ defmodule NewRelic.OtherTransaction do
 
   def start_transaction(category, name) do
     NewRelic.Transaction.Reporter.start_transaction(:other)
-
-    NewRelic.DistributedTrace.generate_new_context()
-    |> NewRelic.DistributedTrace.track_transaction(transport_type: "Other")
+    NewRelic.DistributedTrace.start(:other)
 
     NewRelic.add_attributes(
       pid: inspect(self()),
diff --git a/lib/new_relic/sampler/beam.ex b/lib/new_relic/sampler/beam.ex
index 528124a..487874a 100644
--- a/lib/new_relic/sampler/beam.ex
+++ b/lib/new_relic/sampler/beam.ex
@@ -96,10 +96,12 @@ defmodule NewRelic.Sampler.Beam do
   end
 
   defp delta(:util, previous, current) do
-    {active, total} =
+    {_, {active, total}} =
       Enum.zip(previous, current)
-      |> Enum.map(fn {{n, a0, t0}, {n, a1, t1}} -> {n, a1 - a0, t1 - t0} end)
-      |> Enum.reduce({0, 0}, fn {_n, d_a, d_t}, {acc_a, acc_t} -> {acc_a + d_a, acc_t + d_t} end)
+      |> Enum.map_reduce({0, 0}, fn
+        {{n, a0, t0}, {n, a1, t1}}, {acc_a, acc_t} ->
+          {{n, a1 - a0, t1 - t0}, {acc_a + (a1 - a0), acc_t + (t1 - t0)}}
+      end)
 
     safe_div(active, total)
   end
diff --git a/lib/new_relic/sampler/process.ex b/lib/new_relic/sampler/process.ex
index 2d7a7a7..d9c7564 100644
--- a/lib/new_relic/sampler/process.ex
+++ b/lib/new_relic/sampler/process.ex
@@ -69,21 +69,26 @@ defmodule NewRelic.Sampler.Process do
     {current_sample, stats}
   end
 
-  @kb 1024
   def take_sample(pid) do
     # http://erlang.org/doc/man/erlang.html#process_info-2
     info = Process.info(pid, [:message_queue_len, :memory, :reductions, :registered_name])
 
     %{
       pid: inspect(pid),
-      memory_kb: info[:memory] / @kb,
+      memory_kb: kb(info[:memory]),
       message_queue_length: info[:message_queue_len],
       name: parse(:name, info[:registered_name]) || inspect(pid),
       reductions: info[:reductions]
     }
   end
 
-  defp delta(previous, current), do: %{reductions: current.reductions - previous.reductions}
+  @kb 1024
+  defp kb(nil), do: nil
+  defp kb(bytes), do: bytes / @kb
+
+  defp delta(%{reductions: nil}, _), do: nil
+  defp delta(_, %{reductions: nil}), do: nil
+  defp delta(%{reductions: prev}, %{reductions: curr}), do: %{reductions: curr - prev}
 
   defp parse(:name, []), do: nil
   defp parse(:name, name), do: inspect(name)
diff --git a/lib/new_relic/span/reporter.ex b/lib/new_relic/span/reporter.ex
index 5a59d08..83f0a37 100644
--- a/lib/new_relic/span/reporter.ex
+++ b/lib/new_relic/span/reporter.ex
@@ -2,7 +2,9 @@ defmodule NewRelic.Span.Reporter do
   @moduledoc false
 
   def report_span(span) do
-    case NewRelic.Config.feature(:infinite_tracing) do
+    case NewRelic.Config.feature?(:distributed_tracing) &&
+           NewRelic.Config.feature(:infinite_tracing) do
+      false -> :ignore
       :sampling -> NewRelic.Harvest.Collector.SpanEvent.Harvester.report_span(span)
       :infinite -> NewRelic.Harvest.TelemetrySdk.Spans.Harvester.report_span(span)
     end
diff --git a/lib/new_relic/telemetry/plug.ex b/lib/new_relic/telemetry/plug.ex
index 3d10288..dc66946 100644
--- a/lib/new_relic/telemetry/plug.ex
+++ b/lib/new_relic/telemetry/plug.ex
@@ -98,14 +98,15 @@ defmodule NewRelic.Telemetry.Plug do
       ) do
     measurements = Map.put_new(measurements, :system_time, System.system_time())
 
-    Transaction.Reporter.start_transaction(:web)
-    headers = get_headers(meta, server)
+    with :collect <- Transaction.Reporter.start_transaction(:web, path(meta, server)) do
+      headers = get_headers(meta, server)
 
-    if NewRelic.Config.enabled?(),
-      do: DistributedTrace.start(:http, headers)
+      if NewRelic.Config.enabled?(),
+        do: DistributedTrace.start(:http, headers)
 
-    add_start_attrs(meta, measurements, headers, server)
-    maybe_report_queueing(headers)
+      add_start_attrs(meta, measurements, headers, server)
+      maybe_report_queueing(headers)
+    end
   end
 
   def handle_event(
@@ -268,6 +269,10 @@ defmodule NewRelic.Telemetry.Plug do
     end
   end
 
+  defp path(%{req: %{path: path}}, :cowboy), do: path
+  defp path(%{conn: %{request_path: path}}, :bandit), do: path
+  defp path(_, _), do: "unknown"
+
   defp get_headers(%{conn: conn}, :bandit) do
     Map.new(conn.req_headers)
   end
diff --git a/lib/new_relic/tracer.ex b/lib/new_relic/tracer.ex
index 09aa4fd..7a01503 100644
--- a/lib/new_relic/tracer.ex
+++ b/lib/new_relic/tracer.ex
@@ -3,19 +3,21 @@ defmodule NewRelic.Tracer do
   Function Tracing
 
   To enable function tracing in a particular module, `use NewRelic.Tracer`,
-  and annotate the functions you want to `@trace`.
+  and annotate the functions you want to trace with `@trace`.
 
   Traced functions will report as:
   - Segments in Transaction Traces
   - Span Events in Distributed Traces
   - Special custom attributes on Transaction Events
 
-  #### Notes:
-
-  * Traced functions will *not* be tail-call-recursive. **Don't use this for recursive functions**.
+  > #### Warning {: .error}
+  >
+  > Traced functions will *not* be tail-call-recursive. **Don't use this for recursive functions**.
 
   #### Example
 
+  Trace as a function:
+
   ```elixir
   defmodule MyModule do
     use NewRelic.Tracer
@@ -31,7 +33,7 @@ defmodule NewRelic.Tracer do
 
   To categorize External Service calls you must give the trace annotation a category.
 
-  You may also call `NewRelic.set_span` to provide better naming for metrics & spans, and additonally annotate the outgoing HTTP headers with the Distributed Tracing context to track calls across services.
+  You may also call `NewRelic.set_span/2` to provide better naming for metrics & spans, and additonally annotate the outgoing HTTP headers with the Distributed Tracing context to track calls across services.
 
   ```elixir
   defmodule MyExternalService do
diff --git a/lib/new_relic/transaction/reporter.ex b/lib/new_relic/transaction/reporter.ex
index 345cf05..98189b9 100644
--- a/lib/new_relic/transaction/reporter.ex
+++ b/lib/new_relic/transaction/reporter.ex
@@ -26,9 +26,15 @@ defmodule NewRelic.Transaction.Reporter do
     Transaction.Sidecar.add(custom_name: custom_name)
   end
 
-  def start_transaction(:web) do
-    Transaction.ErlangTrace.trace()
-    Transaction.Sidecar.track(:web)
+  def start_transaction(:web, path) do
+    if NewRelic.Util.path_match?(path, NewRelic.Config.ignore_paths()) do
+      ignore_transaction()
+      :ignore
+    else
+      Transaction.ErlangTrace.trace()
+      Transaction.Sidecar.track(:web)
+      :collect
+    end
   end
 
   def start_transaction(:other) do
diff --git a/lib/new_relic/transaction/sidecar.ex b/lib/new_relic/transaction/sidecar.ex
index 64d6f51..16f09c1 100644
--- a/lib/new_relic/transaction/sidecar.ex
+++ b/lib/new_relic/transaction/sidecar.ex
@@ -13,7 +13,7 @@ defmodule NewRelic.Transaction.Sidecar do
 
   def track(type) do
     # We use `GenServer.start` to avoid a bi-directional link
-    # and guarentee that we never crash the Transaction process
+    # and guarantee that we never crash the Transaction process
     # even in the case of an unexpected bug. Additionally, this
     # blocks the Transaction process the smallest amount possible
     {:ok, sidecar} = GenServer.start(__MODULE__, {self(), type})
diff --git a/lib/new_relic/util.ex b/lib/new_relic/util.ex
index a34a02a..138942c 100644
--- a/lib/new_relic/util.ex
+++ b/lib/new_relic/util.ex
@@ -78,7 +78,7 @@ defmodule NewRelic.Util do
       {key, value} when is_bitstring(value) ->
         case String.valid?(value) do
           true -> [{key, value}]
-          false -> [bad_value(key, value)]
+          false -> [{key, "[BINARY_VALUE]"}]
         end
 
       {key, value} when is_reference(value) when is_pid(value) when is_port(value) ->
@@ -179,6 +179,16 @@ defmodule NewRelic.Util do
     with {:ok, name} <- :inet.gethostname(), do: to_string(name)
   end
 
+  def path_match?(_path, []), do: false
+
+  def path_match?(path, path_set) do
+    Enum.any?(path_set, fn
+      string when is_binary(string) -> path == string
+      %Regex{} = regex -> path =~ regex
+      _ -> false
+    end)
+  end
+
   def uuid4() do
     "#{u(4)}-#{u(2)}-4a#{u(1)}-#{u(2)}-#{u(6)}"
   end
diff --git a/mix.lock b/mix.lock
index dd68eb7..e4c29ac 100644
--- a/mix.lock
+++ b/mix.lock
@@ -5,14 +5,14 @@
   "cowlib": {:hex, :cowlib, "2.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"},
   "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
   "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
-  "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
+  "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
   "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"},
   "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 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", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"},
-  "ex_doc": {:hex, :ex_doc, "0.31.0", "06eb1dfd787445d9cab9a45088405593dd3bb7fe99e097eaa71f37ba80c7a676", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5350cafa6b7f77bdd107aa2199fe277acf29d739aba5aee7e865fc680c62a110"},
+  "ex_doc": {:hex, :ex_doc, "0.35.1", "de804c590d3df2d9d5b8aec77d758b00c814b356119b3d4455e4b8a8687aecaf", [:mix], [{:earmark_parser, "~> 1.4.39", [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", "2121c6402c8d44b05622677b761371a759143b958c6c19f6558ff64d0aed40df"},
   "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
-  "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"},
-  "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [: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", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"},
-  "makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"},
+  "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.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
   "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
   "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},


Test patch:
diff --git a/config/test.exs b/config/test.exs
index c76f6b0..64822d1 100644
--- a/config/test.exs
+++ b/config/test.exs
@@ -5,4 +5,5 @@ config :logger, level: :warning
 config :new_relic_agent,
   app_name: "ElixirAgentTest",
   automatic_attributes: [test_attribute: "test_value"],
-  log: "memory"
+  ignore_paths: ["/ignore/this", ~r(ignore/these/*.)],
+  log: "Logger"
diff --git a/test/distributed_trace_test.exs b/test/distributed_trace_test.exs
index 30c7c6d..dee4a95 100644
--- a/test/distributed_trace_test.exs
+++ b/test/distributed_trace_test.exs
@@ -15,8 +15,10 @@ defmodule DistributedTraceTest do
     plug(:dispatch)
 
     get "/" do
-      [{_, outbound_payload} | _] = NewRelic.distributed_trace_headers(:http)
-      send_resp(conn, 200, outbound_payload)
+      case NewRelic.distributed_trace_headers(:http) do
+        [{_, outbound_payload} | _] -> send_resp(conn, 200, outbound_payload)
+        [] -> send_resp(conn, 200, "nothin")
+      end
     end
 
     get "/w3c" do
@@ -66,6 +68,35 @@ defmodule DistributedTraceTest do
     :ok
   end
 
+  test "can disable Distributed Tracing" do
+    reset_config =
+      TestHelper.update(:nr_features,
+        distributed_tracing: false
+      )
+
+    TestHelper.restart_harvest_cycle(Collector.TransactionEvent.HarvestCycle)
+
+    TestHelper.request(
+      TestPlugApp,
+      conn(:get, "/")
+      |> put_req_header(@dt_header, generate_inbound_payload(:app))
+    )
+
+    [[_, attrs] | _] = TestHelper.gather_harvest(Collector.TransactionEvent.Harvester)
+
+    assert attrs[:name]
+    refute attrs[:error_reason]
+
+    refute attrs[:"parent.app"]
+    refute attrs[:parentId]
+    refute attrs[:parentSpanId]
+    refute attrs[:traceId]
+
+    TestHelper.pause_harvest_cycle(Collector.TransactionEvent.HarvestCycle)
+
+    reset_config.()
+  end
+
   test "Annotate Transaction event with DT attrs" do
     TestHelper.restart_harvest_cycle(Collector.TransactionEvent.HarvestCycle)
 
@@ -135,7 +166,7 @@ defmodule DistributedTraceTest do
     TestHelper.pause_harvest_cycle(Collector.Metric.HarvestCycle)
   end
 
-  test "propigate the context through connected Elixir processes" do
+  test "propagate the context through connected Elixir processes" do
     response =
       TestHelper.request(
         TestPlugApp,
diff --git a/test/transaction_test.exs b/test/transaction_test.exs
index e7effef..e62a0a5 100644
--- a/test/transaction_test.exs
+++ b/test/transaction_test.exs
@@ -169,6 +169,14 @@ defmodule TransactionTest do
       send_resp(conn, 200, "ignored")
     end
 
+    get "/ignore/this" do
+      send_resp(conn, 200, "ignore me!")
+    end
+
+    get "/ignore/these/too" do
+      send_resp(conn, 200, "ignore me!")
+    end
+
     get "/total_time" do
       Process.sleep(10)
 
@@ -207,7 +215,6 @@ defmodule TransactionTest do
            end)
   end
 
-  @bad "[BAD_VALUE]"
   test "Attribute coercion" do
     TestHelper.restart_harvest_cycle(Collector.TransactionEvent.HarvestCycle)
 
@@ -233,11 +240,13 @@ defmodule TransactionTest do
     assert {:ok, _} = Date.from_iso8601(event[:date])
     assert {:ok, _} = Time.from_iso8601(event[:time])
 
+    # Binary values
+    assert event[:binary] == "[BINARY_VALUE]"
+
     # Bad values
-    assert event[:binary] == @bad
-    assert event[:tuple] == @bad
-    assert event[:function] == @bad
-    assert event[:struct] == @bad
+    assert event[:tuple] == "[BAD_VALUE]"
+    assert event[:function] == "[BAD_VALUE]"
+    assert event[:struct] == "[BAD_VALUE]"
 
     # Don't report nil values
     refute Map.has_key?(event, :nilValue)
@@ -411,6 +420,17 @@ defmodule TransactionTest do
     assert events == []
   end
 
+  test "Allow a transaction to be ignored via configuration" do
+    TestHelper.restart_harvest_cycle(Collector.TransactionEvent.HarvestCycle)
+    Task.Supervisor.start_link(name: TestTaskSup)
+
+    assert %{status_code: 200} = TestHelper.request(TestPlugApp, conn(:get, "/ignore/this"))
+    assert %{status_code: 200} = TestHelper.request(TestPlugApp, conn(:get, "/ignore/these/too"))
+
+    events = TestHelper.gather_harvest(Collector.TransactionEvent.Harvester)
+    assert events == []
+  end
+
   test "Calculate total time" do
     TestHelper.restart_harvest_cycle(Collector.TransactionEvent.HarvestCycle)