From de7b38fe0441368caea2bb7433044b9ffb7f99c4 Mon Sep 17 00:00:00 2001 From: Peter Solnica Date: Mon, 2 Feb 2026 11:16:42 +0000 Subject: [PATCH 1/2] feat(transport): handle HTTP 413 responses for oversized envelopes - Add :envelope_too_large reason to ClientError with specific error message - Handle HTTP 413 in Transport without retrying (similar to 429) - Record client report with :send_error reason per SDK spec - Log warning about envelope being rejected due to size limits This implements the SDK specification for handling HTTP 413 Content Too Large responses from Relay. Previously, oversized envelopes received HTTP 400 Bad Request, but Relay now returns HTTP 413 to allow SDKs to distinguish size-related rejections from other errors. Closes #978 Signed-off-by: Peter Solnica --- lib/sentry/client_error.ex | 20 ++++++++++++++++++ lib/sentry/transport.ex | 7 +++++++ test/sentry/client_error_test.exs | 16 ++++++++++++++ test/sentry/transport_test.exs | 35 +++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+) diff --git a/lib/sentry/client_error.ex b/lib/sentry/client_error.ex index b3792c37..69549af4 100644 --- a/lib/sentry/client_error.ex +++ b/lib/sentry/client_error.ex @@ -35,6 +35,7 @@ defmodule Sentry.ClientError do :too_many_retries | :rate_limited | :server_error + | :envelope_too_large | {:invalid_json, Exception.t()} | {:request_failure, reason :: :inet.posix() | term()} | {Exception.kind(), reason :: term(), Exception.stacktrace()} @@ -52,6 +53,16 @@ defmodule Sentry.ClientError do %__MODULE__{reason: :server_error, http_response: {status, headers, body}} end + @doc false + @spec envelope_too_large( + status :: 100..599, + headers :: [{String.t(), String.t()}], + body :: binary() + ) :: t + def envelope_too_large(status, headers, body) do + %__MODULE__{reason: :envelope_too_large, http_response: {status, headers, body}} + end + @impl true def message(%__MODULE__{reason: reason, http_response: http_response}) do "Sentry failed to report event: #{format(reason, http_response)}" @@ -66,6 +77,15 @@ defmodule Sentry.ClientError do """ end + defp format(:envelope_too_large, {status, headers, body}) do + """ + the envelope was rejected due to exceeding size limits. + HTTP Status: #{status} + Response Headers: #{inspect(headers)} + Response Body: #{inspect(body)} + """ + end + defp format(reason, nil) do format(reason) end diff --git a/lib/sentry/transport.ex b/lib/sentry/transport.ex index 6cb967c4..f7f10440 100644 --- a/lib/sentry/transport.ex +++ b/lib/sentry/transport.ex @@ -56,6 +56,10 @@ defmodule Sentry.Transport do ClientReport.Sender.record_discarded_events(:ratelimit_backoff, envelope_items) {:error, ClientError.new(:rate_limited)} + {:error, {:envelope_too_large, {status, headers, body}}} -> + ClientReport.Sender.record_discarded_events(:send_error, envelope_items) + {:error, ClientError.envelope_too_large(status, headers, body)} + {:error, _reason} when retries_left != [] -> [sleep_interval | retries_left] = retries_left Process.sleep(sleep_interval) @@ -99,6 +103,9 @@ defmodule Sentry.Transport do {:ok, 429, _headers, _body} -> {:error, :rate_limited} + {:ok, 413, headers, body} -> + {:error, {:envelope_too_large, {413, headers, body}}} + {:ok, status, headers, body} -> {:error, {:http, {status, headers, body}}} diff --git a/test/sentry/client_error_test.exs b/test/sentry/client_error_test.exs index 9d15934d..1eee882f 100644 --- a/test/sentry/client_error_test.exs +++ b/test/sentry/client_error_test.exs @@ -48,6 +48,22 @@ defmodule Sentry.ClientErrorTest do Response Body: "{}" """ end + + test "with :envelope_too_large and HTTP response as the reason" do + exception = + ClientError.envelope_too_large( + 413, + [{"X-Sentry-Error", "envelope too large"}], + "Payload Too Large" + ) + + assert Exception.message(exception) == """ + Sentry failed to report event: the envelope was rejected due to exceeding size limits. + HTTP Status: 413 + Response Headers: [{"X-Sentry-Error", "envelope too large"}] + Response Body: "Payload Too Large" + """ + end end defp message_for_reason(reason) do diff --git a/test/sentry/transport_test.exs b/test/sentry/transport_test.exs index 786948ba..a3a68235 100644 --- a/test/sentry/transport_test.exs +++ b/test/sentry/transport_test.exs @@ -269,6 +269,41 @@ defmodule Sentry.TransportTest do assert_received {:request, ^ref} end + test "fails immediately when Sentry replies with 413 (envelope too large)", %{bypass: bypass} do + envelope = Envelope.from_event(Event.create_event(message: "Hello")) + test_pid = self() + ref = make_ref() + + Bypass.expect(bypass, "POST", "/api/1/envelope/", fn conn -> + send(test_pid, {:request, ref}) + + conn + |> Plug.Conn.put_resp_header("x-sentry-error", "Payload Too Large") + |> Plug.Conn.resp(413, ~s) + end) + + {:error, %ClientError{} = error} = + Transport.encode_and_post_envelope(envelope, FinchClient, _retries = [100, 200]) + + assert error.reason == :envelope_too_large + assert {413, headers, "Payload Too Large"} = error.http_response + assert :proplists.get_value("x-sentry-error", headers, nil) == "Payload Too Large" + + assert Exception.message(error) =~ + "the envelope was rejected due to exceeding size limits" + + assert_received {:request, ^ref} + refute_received {:request, ^ref} + + log = + capture_log(fn -> + Transport.encode_and_post_envelope(envelope, FinchClient, _retries = []) + end) + + assert log =~ "[warning]" + assert log =~ "exceeding size limits" + end + test "updates rate limits from X-Sentry-Rate-Limits header in 200 OK response", %{ bypass: bypass } do From 78b8c5a90c9fb5594998a9ae6e5371ebecfd4482 Mon Sep 17 00:00:00 2001 From: Peter Solnica Date: Mon, 2 Feb 2026 11:26:22 +0000 Subject: [PATCH 2/2] docs: Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4bcb0ca..e01122f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Support for Structured Logs ([#969](https://github.com/getsentry/sentry-elixir/pull/969)) - Support for Distributed Tracing ([957](https://github.com/getsentry/sentry-elixir/pull/957)) - Support for LiveView spans captured under single trace root ([#977](https://github.com/getsentry/sentry-elixir/pull/977)) +- Handle HTTP 413 responses for oversized envelopes ([#982](https://github.com/getsentry/sentry-elixir/pull/982)) ### Bug Fixes