From 0f320869333a41d51d385e3d4963e0428a3a51ee Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Tue, 17 Feb 2026 10:59:43 -0600 Subject: [PATCH] Ignore pod shutdown errors in ErrorTracker Add ErrorTrackerIgnorer to drop port_died, write_failed, epipe, and broken pipe errors that occur during K8s pod shutdown. Same patterns already filtered by HoneybadgerFilter and LoggerFilters. --- config/config.exs | 3 +- lib/towerops/error_tracker_ignorer.ex | 24 +++++++++++ test/towerops/error_tracker_ignorer_test.exs | 45 ++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 lib/towerops/error_tracker_ignorer.ex create mode 100644 test/towerops/error_tracker_ignorer_test.exs diff --git a/config/config.exs b/config/config.exs index a84764e2..01a3e138 100644 --- a/config/config.exs +++ b/config/config.exs @@ -12,7 +12,8 @@ config :elixir, :time_zone_database, Tzdata.TimeZoneDatabase config :error_tracker, repo: Towerops.Repo, - otp_app: :towerops + otp_app: :towerops, + ignorer: Towerops.ErrorTrackerIgnorer # Configure esbuild (the version is required) config :esbuild, diff --git a/lib/towerops/error_tracker_ignorer.ex b/lib/towerops/error_tracker_ignorer.ex new file mode 100644 index 00000000..ffe260f5 --- /dev/null +++ b/lib/towerops/error_tracker_ignorer.ex @@ -0,0 +1,24 @@ +defmodule Towerops.ErrorTrackerIgnorer do + @moduledoc """ + Drops benign errors from ErrorTracker that occur during K8s pod shutdown. + + Matches the same patterns as `Towerops.HoneybadgerFilter` and + `Towerops.LoggerFilters.drop_shutdown_errors/2`. + """ + + @behaviour ErrorTracker.Ignorer + + @impl true + def ignore?(%ErrorTracker.Error{reason: reason}, _context) do + shutdown_error?(reason) + end + + defp shutdown_error?(reason) when is_binary(reason) do + String.contains?(reason, "port_died") or + String.contains?(reason, "write_failed") or + String.contains?(reason, "epipe") or + String.contains?(reason, "broken pipe") + end + + defp shutdown_error?(_), do: false +end diff --git a/test/towerops/error_tracker_ignorer_test.exs b/test/towerops/error_tracker_ignorer_test.exs new file mode 100644 index 00000000..9b8186bd --- /dev/null +++ b/test/towerops/error_tracker_ignorer_test.exs @@ -0,0 +1,45 @@ +defmodule Towerops.ErrorTrackerIgnorerTest do + use ExUnit.Case, async: true + + alias Towerops.ErrorTrackerIgnorer + + defp build_error(reason) do + %ErrorTracker.Error{ + kind: "ErlangError", + reason: reason, + source_line: "-", + source_function: "-", + status: :unresolved, + fingerprint: "abc", + last_occurrence_at: DateTime.utc_now(), + muted: false + } + end + + describe "ignore?/2" do + test "ignores port_died errors" do + error = build_error("Erlang error: {:port_died, :normal}") + assert ErrorTrackerIgnorer.ignore?(error, %{}) + end + + test "ignores write_failed errors" do + error = build_error("{:write_failed, :standard_io, :enoent}") + assert ErrorTrackerIgnorer.ignore?(error, %{}) + end + + test "ignores epipe errors" do + error = build_error("{:error, {:write_failed, %{type: :standard_io}, {:badarg, :epipe}}}") + assert ErrorTrackerIgnorer.ignore?(error, %{}) + end + + test "ignores broken pipe errors" do + error = build_error("broken pipe (epipe)") + assert ErrorTrackerIgnorer.ignore?(error, %{}) + end + + test "does not ignore regular errors" do + error = build_error("something went wrong") + refute ErrorTrackerIgnorer.ignore?(error, %{}) + end + end +end