Repository: elixir-error-tracker/error-tracker
Pull request #79 (2024-09-02 15:06:15)

Ignore Errors
This pull request adds the new `ErrorTracker.Ignorer` behaviour that can be used by clients to ignore errors based on their attributes and context.

I've added a test to verify that it works as expected and also updated the docs and getting started guide.


Patch:
diff --git a/guides/Getting Started.md b/guides/Getting Started.md
index 7793e14..00378a8 100644
--- a/guides/Getting Started.md
+++ b/guides/Getting Started.md
@@ -136,9 +136,9 @@ This is completely optional, and you can find more information about it in the `
 
 ## Notifications
 
-We currently do not support notifications out of the box.
+Currently ErrorTracker does not support notifications out of the box.
 
-However, we provideo some detailed Telemetry events that you may use to implement your own notifications following your custom rules and notification channels.
+However, it provides some detailed Telemetry events that you may use to implement your own notifications following your custom rules and notification channels.
 
 If you want to take a look at the events you can attach to, take a look at `ErrorTracker.Telemetry` module documentation.
 
@@ -149,3 +149,10 @@ environments where you may want to prune old errors that have been resolved.
 
 The `ErrorTracker.Plugins.Pruner` module provides automatic pruning functionality with a configurable
 interval and error age.
+
+## Ignoring errors
+
+ErrorTracker tracks every error by default. In certain cases some errors may be expected or just not interesting to track.
+ErrorTracker provides functionality that allows you to ignore errors based on their attributes and context.
+
+Take a look at the `ErrorTracker.Ignorer` behaviour for more information about how to implement your own ignorer.
diff --git a/lib/error_tracker.ex b/lib/error_tracker.ex
index 7477138..c737cde 100644
--- a/lib/error_tracker.ex
+++ b/lib/error_tracker.ex
@@ -111,10 +111,9 @@ defmodule ErrorTracker do
     {kind, reason} = normalize_exception(exception, stacktrace)
     {:ok, stacktrace} = ErrorTracker.Stacktrace.new(stacktrace)
     {:ok, error} = Error.new(kind, reason, stacktrace)
-
     context = Map.merge(get_context(), given_context)
 
-    if enabled?() do
+    if enabled?() && !ignored?(error, context) do
       {_error, occurrence} = upsert_error!(error, stacktrace, context, reason)
       occurrence
     else
@@ -195,6 +194,12 @@ defmodule ErrorTracker do
     !!Application.get_env(:error_tracker, :enabled, true)
   end
 
+  defp ignored?(error, context) do
+    ignorer = Application.get_env(:error_tracker, :ignorer)
+
+    ignorer && ignorer.ignore?(error, context)
+  end
+
   defp normalize_exception(%struct{} = ex, _stacktrace) when is_exception(ex) do
     {to_string(struct), Exception.message(ex)}
   end
diff --git a/lib/error_tracker/ignorer.ex b/lib/error_tracker/ignorer.ex
new file mode 100644
index 0000000..28e19b9
--- /dev/null
+++ b/lib/error_tracker/ignorer.ex
@@ -0,0 +1,39 @@
+defmodule ErrorTracker.Ignorer do
+  @moduledoc """
+  Behaviour for ignoring errors.
+
+  The ErrorTracker tracks every error that happens in your application. In certain cases you may
+  want to ignore some errors and don't track them. To do so you can implement this behaviour.
+
+      defmodule MyApp.ErrorIgnorer do
+        @behaviour ErrorTracker.Ignorer
+
+        @impl true
+        def ignore?(error = %ErrorTracker.Error{}, context) do
+          # return true if the error should be ignored
+        end
+      end
+
+  Once implemented, include it in the ErrorTracker configuration:
+
+      config :error_tracker, ignorer: MyApp.ErrorIgnorer
+
+  With this configuration in place, the ErrorTracker will call `MyApp.ErrorIgnorer.ignore?/2` before
+  tracking errors. If the function returns `true` the error will be ignored and won't be tracked.
+
+  > #### A note on performance {: .warning}
+  >
+  > Keep in mind that the `ignore?/2` will be called in the context of the ErrorTracker itself.
+  > Slow code will have a significant impact in the ErrorTracker performance. Buggy code can bring
+  > the ErrorTracker process down.
+  """
+
+  @doc """
+  Decide wether the given error should be ignored or not.
+
+  This function receives both the current Error and context and should return a boolean indicating
+  if it should be ignored or not. If the function returns true the error will be ignored, otherwise
+  it will be tracked.
+  """
+  @callback ignore?(error :: ErrorTracker.Error.t(), context :: map()) :: boolean
+end


Test patch:
diff --git a/test/error_tracker/ignorer_test.exs b/test/error_tracker/ignorer_test.exs
new file mode 100644
index 0000000..9b1bbd8
--- /dev/null
+++ b/test/error_tracker/ignorer_test.exs
@@ -0,0 +1,35 @@
+defmodule ErrorTracker.IgnorerTest do
+  use ErrorTracker.Test.Case
+
+  setup context do
+    if ignorer = context[:ignorer] do
+      previous_setting = Application.get_env(:error_tracker, :ignorer)
+      Application.put_env(:error_tracker, :ignorer, ignorer)
+      # Ensure that the application env is restored after each test
+      on_exit(fn -> Application.put_env(:error_tracker, :ignorer, previous_setting) end)
+    end
+
+    []
+  end
+
+  @tag ignorer: ErrorTracker.EveryErrorIgnorer
+  test "with an ignorer ignores errors" do
+    assert :noop = report_error(fn -> raise "[IGNORE] Sample error" end)
+    assert %ErrorTracker.Occurrence{} = report_error(fn -> raise "Sample error" end)
+  end
+
+  @tag ignorer: false
+  test "without an ignorer does not ignore errors" do
+    assert %ErrorTracker.Occurrence{} = report_error(fn -> raise "[IGNORE] Sample error" end)
+    assert %ErrorTracker.Occurrence{} = report_error(fn -> raise "Sample error" end)
+  end
+end
+
+defmodule ErrorTracker.EveryErrorIgnorer do
+  @behaviour ErrorTracker.Ignorer
+
+  @impl true
+  def ignore?(error, _context) do
+    String.contains?(error.reason, "[IGNORE]")
+  end
+end