From 93b526c761d729808827285302f9c55d2514e1dc Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Wed, 22 Apr 2026 16:50:50 -0500 Subject: [PATCH] fix(web): suppress HEAD request logs The log_level(%{method: \"HEAD\"}) clause never matched because Plug.Head rewrites the method to \"GET\" before Plug.Telemetry's register_before_send callback fires, so by log time conn.method is \"GET\". Stash the original method in private before Plug.Head runs and key the filter off it. --- lib/microwaveprop_web/endpoint.ex | 12 +++++++- .../metrics_log_suppression_test.exs | 28 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/microwaveprop_web/endpoint.ex b/lib/microwaveprop_web/endpoint.ex index 1586f508..24fba93e 100644 --- a/lib/microwaveprop_web/endpoint.ex +++ b/lib/microwaveprop_web/endpoint.ex @@ -46,6 +46,12 @@ defmodule MicrowavepropWeb.Endpoint do plug Plug.RequestId plug MicrowavepropWeb.Plugs.RemoteIp + # Plug.Head (below) rewrites method=HEAD to "GET" before + # Plug.Telemetry's register_before_send callback fires, so a + # `log_level(%{method: "HEAD"})` clause never matches. Capture + # the original method in private here so the log filter can still + # see HEAD requests and suppress them. + plug :stash_request_method plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint], log: {__MODULE__, :log_level, []} plug Plug.Parsers, @@ -71,6 +77,10 @@ defmodule MicrowavepropWeb.Endpoint do def log_level(%{request_path: "/live"}), do: false def log_level(%{request_path: "/metrics"}), do: false def log_level(%{request_path: "/metrics/" <> _}), do: false - def log_level(%{method: "HEAD"}), do: false + def log_level(%{private: %{original_method: "HEAD"}}), do: false def log_level(_conn), do: :info + + defp stash_request_method(conn, _opts) do + Plug.Conn.put_private(conn, :original_method, conn.method) + end end diff --git a/test/microwaveprop_web/metrics_log_suppression_test.exs b/test/microwaveprop_web/metrics_log_suppression_test.exs index 1c18cb29..7bd686e9 100644 --- a/test/microwaveprop_web/metrics_log_suppression_test.exs +++ b/test/microwaveprop_web/metrics_log_suppression_test.exs @@ -71,4 +71,32 @@ defmodule MicrowavepropWeb.MetricsLogSuppressionTest do assert MicrowavepropWeb.Endpoint.log_level(rewritten) == false end + + test "log_level/1 returns false when the original method stashed before Plug.Head was HEAD" do + # Plug.Head runs BEFORE Plug.Telemetry's register_before_send fires + # and rewrites HEAD→GET, so matching `method: \"HEAD\"` at log time + # never hits. Endpoint now stashes the original method in private + # before Plug.Head runs; assert the filter keys off it. + conn = %{ + request_path: "/some/page", + method: "GET", + private: %{original_method: "HEAD"} + } + + assert MicrowavepropWeb.Endpoint.log_level(conn) == false + end + + test "HEAD request to a real route does not emit a Phoenix endpoint Sent log line", %{ + conn: conn + } do + log = + capture_log([level: :info], fn -> + conn = dispatch(conn, MicrowavepropWeb.Endpoint, "HEAD", "/") + assert conn.status in [200, 302] + end) + + refute log =~ "Sent 200" + refute log =~ "Sent 302" + refute log =~ "HEAD /" + end end