Repository: honeybadger-io/honeybadger-elixir (elixir)

Issue:
Refine batch event logging
We are reporting every UUID and status for each event stored in Insights at the debug level. This is unnecessary as its not even apparant which event is which in the log data. We should iterate through the results and report any non 200 IDs, but other than that just log the count of events transmitted.

On top of reducing the noise, we should log failed event (not rate-limited) payloads to the debug log, as this can be used to investigate why the event failed to ingest.

Pull request description:
Revised event logging
resolves #607

This prints out less in the debug logs for successful event API responses. It also prints the event data to the warn log for any non 200 event responses.

![CleanShot 2025-04-22 at 16 13 29@2x](https://github.com/user-attachments/assets/701fa78c-6adf-44e6-9e73-badddf250e30)


Patch:
diff --git a/lib/honeybadger/client.ex b/lib/honeybadger/client.ex
index 4dc092e..5f478c9 100644
--- a/lib/honeybadger/client.ex
+++ b/lib/honeybadger/client.ex
@@ -261,19 +261,19 @@ defmodule Honeybadger.Client do
       response =
         case HTTPAdapter.request(:post, event_url, payload, headers, hackney_opts) do
           {:ok, %HTTPResponse{body: body, status: status}} when status in 200..399 ->
-            Logger.debug(fn -> "[Honeybadger] API success: #{inspect(body)}" end)
+            process_and_log_events_response(events, body)
             :ok
 
           {:ok, %HTTPResponse{status: status}} when status == 429 ->
-            Logger.warning("[Honeybadger] API rate limited:")
+            Logger.warning("[Honeybadger] Events API rate limited")
             {:error, :throttled}
 
           {:ok, %HTTPResponse{status: status}} when status in 400..599 ->
-            Logger.warning("[Honeybadger] API failure")
+            Logger.warning("[Honeybadger] Events API failure with status: #{status}")
             {:error, :api_error}
 
           {:error, reason} ->
-            Logger.warning(fn -> "[Honeybadger] connection error: #{inspect(reason)}" end)
+            Logger.warning(fn -> "[Honeybadger] Events connection error: #{inspect(reason)}" end)
             {:error, :connection_error}
         end
 
@@ -281,6 +281,26 @@ defmodule Honeybadger.Client do
     end
   end
 
+  defp process_and_log_events_response(events, %{"errors" => true, "events" => res_events}) do
+    Logger.debug("[Honeybadger] Events API (#{length(events)} events sent) with errors:")
+
+    events
+    |> Enum.zip(res_events)
+    |> Enum.each(fn {event, %{"status" => status}} ->
+      if status != 200 && status != 201 do
+        Logger.warning(fn ->
+          "[Honeybadger] Event error: #{status}\nEvent: #{inspect(event)}"
+        end)
+      end
+    end)
+  end
+
+  defp process_and_log_events_response(events, %{"errors" => false}) do
+    Logger.debug("[Honeybadger] Events API success (#{length(events)} events sent)")
+  end
+
+  defp process_and_log_events_response(_, _), do: nil
+
   # API Integration
 
   defp build_headers(opts) do
diff --git a/lib/honeybadger/events_worker.ex b/lib/honeybadger/events_worker.ex
index 433b15a..fbdd81e 100644
--- a/lib/honeybadger/events_worker.ex
+++ b/lib/honeybadger/events_worker.ex
@@ -165,7 +165,6 @@ defmodule Honeybadger.EventsWorker do
         %{batch: batch, attempts: attempts} = b, {acc, false} ->
           case state.send_events_fn.(batch) do
             :ok ->
-              Logger.debug("[Honeybadger] Sent batch of #{length(batch)} events.")
               {acc, false}
 
             {:error, reason} ->
diff --git a/lib/honeybadger/http_adapter/hackney.ex b/lib/honeybadger/http_adapter/hackney.ex
index b99b18a..2c7ce20 100644
--- a/lib/honeybadger/http_adapter/hackney.ex
+++ b/lib/honeybadger/http_adapter/hackney.ex
@@ -33,14 +33,23 @@ defmodule Honeybadger.HTTPAdapter.Hackney do
   end
 
   defp decode(headers, body, opts) when is_binary(body) do
-    case List.keyfind(headers, "content-type", 0) do
-      {"content-type", "application/json" <> _rest} ->
+    content_type =
+      headers
+      |> Enum.find_value(fn {k, v} ->
+        if String.downcase(k) == "content-type", do: String.downcase(v)
+      end)
+
+    case content_type do
+      nil ->
+        {:ok, body}
+
+      "application/json" <> _rest ->
         Jason.decode(body, opts)
 
-      {"content-type", "text/javascript" <> _rest} ->
+      "text/javascript" <> _rest ->
         Jason.decode(body, opts)
 
-      {"content-type", "application/x-www-form-urlencoded" <> _rest} ->
+      "application/x-www-form-urlencoded" <> _rest ->
         {:ok, URI.decode_query(body)}
 
       _any ->


Test patch:
diff --git a/test/honeybadger/http_adapter/hackney_test.exs b/test/honeybadger/http_adapter/hackney_test.exs
index 9f7617a..d334496 100644
--- a/test/honeybadger/http_adapter/hackney_test.exs
+++ b/test/honeybadger/http_adapter/hackney_test.exs
@@ -80,6 +80,17 @@ defmodule Honeybadger.HTTPAdapter.HackneyTest do
 
     assert response.body == @body
 
+    assert {:ok, response} =
+             Hackney.decode_response_body(
+               %HTTPResponse{
+                 body: @json_encoded_body,
+                 headers: [{"Content-Type", "application/json"}]
+               },
+               []
+             )
+
+    assert response.body == @body
+
     assert {:ok, response} =
              Hackney.decode_response_body(
                %HTTPResponse{