Repository: honeybadger-io/honeybadger-elixir
Pull request #605 (2025-04-04 13:09:41)

Allow Ecto params w/source whitelisting
This change allows for adding the query params to the Ecto event via the config, which is disabled by default.

It also now allows for either a boolean to enable for all sources, or a list of sources that acts as a whitelist. I also added the same whitelist behaviour for the `stacktrace`.


Patch:
diff --git a/lib/honeybadger/insights/ecto.ex b/lib/honeybadger/insights/ecto.ex
index c31b38d..ecd781e 100644
--- a/lib/honeybadger/insights/ecto.ex
+++ b/lib/honeybadger/insights/ecto.ex
@@ -27,7 +27,18 @@ defmodule Honeybadger.Insights.Ecto do
           #   config :my_app, MyApp.Repo,
           #     stacktrace: true
           #
-          include_stacktrace: true,
+          # Can be a boolean to enable for all or a list of sources to enable.
+          include_stacktrace: true
+
+          # Alternative source whitelist example:
+          # include_stacktrace: ["source_a", "source_b"],
+
+          # Format & include the query parameters with each query. Can be a
+          # boolean to enable for all or a list of sources to enable.
+          include_params: true
+
+          # Alternative source whitelist example:
+          # include_params:["source_a", "source_b"],
 
           # A list of table/source names to exclude
           excluded_sources: [
@@ -71,13 +82,42 @@ defmodule Honeybadger.Insights.Ecto do
     |> Map.take([:query, :decode_time, :query_time, :queue_time, :source])
     |> Map.update!(:query, &obfuscate(&1, meta.repo.__adapter__()))
     |> include_stacktrace(meta)
+    |> include_params(meta)
   end
 
-  defp include_stacktrace(data, %{stacktrace: stacktrace}) do
-    if get_insights_config(:include_stacktrace, false) do
-      Map.put(data, :stacktrace, format_stacktrace(stacktrace))
-    else
-      data
+  defp include_params(data, %{params: params, source: source}) do
+    case get_insights_config(:include_params, false) do
+      false ->
+        data
+
+      true ->
+        Map.put(data, :params, params)
+
+      sources when is_list(sources) ->
+        if source in sources do
+          Map.put(data, :params, params)
+        else
+          data
+        end
+    end
+  end
+
+  defp include_params(data, _), do: data
+
+  defp include_stacktrace(data, %{stacktrace: stacktrace, source: source}) do
+    case get_insights_config(:include_stacktrace, false) do
+      false ->
+        data
+
+      true ->
+        Map.put(data, :stacktrace, format_stacktrace(stacktrace))
+
+      sources when is_list(sources) ->
+        if source in sources do
+          Map.put(data, :stacktrace, format_stacktrace(stacktrace))
+        else
+          data
+        end
     end
   end
 


Test patch:
diff --git a/test/honeybadger/insights/ecto_test.exs b/test/honeybadger/insights/ecto_test.exs
index ac56ce8..2347dda 100644
--- a/test/honeybadger/insights/ecto_test.exs
+++ b/test/honeybadger/insights/ecto_test.exs
@@ -77,6 +77,103 @@ defmodule Honeybadger.Insights.EctoTest do
         refute_receive {:api_request, _}
       end)
     end
+
+    test "includes stacktrace" do
+      with_config([insights_config: %{ecto: %{include_stacktrace: true}}], fn ->
+        :telemetry.execute(
+          [:a, :b, :query],
+          %{},
+          %{
+            query: "SELECT u0.id, u0.name FROM users u0 WHERE u0.id = ?",
+            source: "users",
+            stacktrace: [
+              {Test.MockEctoConfig, :query, 1, [file: "test.ex", line: 1]}
+            ],
+            repo: %{__adapter__: Adapter.Postgres}
+          }
+        )
+
+        assert_receive {:api_request, event}
+
+        assert event["stacktrace"] == [
+                 ["test.ex:1", "Honeybadger.Insights.EctoTest.Test.MockEctoConfig.query/1"]
+               ]
+      end)
+    end
+
+    test "excludes stacktrace for other sources" do
+      with_config([insights_config: %{ecto: %{include_stacktrace: ["orders"]}}], fn ->
+        :telemetry.execute(
+          [:a, :b, :query],
+          %{},
+          %{
+            query: "SELECT u0.id, u0.name FROM users u0 WHERE u0.id = ?",
+            source: "users",
+            stacktrace: [
+              {Test.MockEctoConfig, :query, 1, [file: "test.ex", line: 1]}
+            ],
+            repo: %{__adapter__: Adapter.Postgres}
+          }
+        )
+
+        assert_receive {:api_request, event}
+        refute event["stacktrace"]
+      end)
+    end
+
+    test "includes all params" do
+      with_config([insights_config: %{ecto: %{include_params: true}}], fn ->
+        :telemetry.execute(
+          [:a, :b, :query],
+          %{},
+          %{
+            query: "SELECT u0.id, u0.name FROM users u0 WHERE u0.id = ?",
+            source: "users",
+            params: [1],
+            repo: %{__adapter__: Adapter.Postgres}
+          }
+        )
+
+        assert_receive {:api_request, event}
+        assert event["params"] == [1]
+      end)
+    end
+
+    test "includes params for specific sources" do
+      with_config([insights_config: %{ecto: %{include_params: ["users"]}}], fn ->
+        :telemetry.execute(
+          [:a, :b, :query],
+          %{},
+          %{
+            query: "SELECT u0.id, u0.name FROM users u0 WHERE u0.id = ?",
+            source: "users",
+            params: [1],
+            repo: %{__adapter__: Adapter.Postgres}
+          }
+        )
+
+        assert_receive {:api_request, event}
+        assert event["params"] == [1]
+      end)
+    end
+
+    test "excludes params for other sources" do
+      with_config([insights_config: %{ecto: %{include_params: ["orders"]}}], fn ->
+        :telemetry.execute(
+          [:a, :b, :query],
+          %{},
+          %{
+            query: "SELECT u0.id, u0.name FROM users u0 WHERE u0.id = ?",
+            source: "users",
+            params: [1],
+            repo: %{__adapter__: Adapter.Postgres}
+          }
+        )
+
+        assert_receive {:api_request, event}
+        refute event["params"]
+      end)
+    end
   end
 
   describe "obfuscate/2" do