Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
20 changes: 20 additions & 0 deletions lib/sentry/client_error.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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()}
Expand All @@ -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)}"
Expand All @@ -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
Expand Down
7 changes: 7 additions & 0 deletions lib/sentry/transport.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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}}}

Expand Down
16 changes: 16 additions & 0 deletions test/sentry/client_error_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions test/sentry/transport_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Payload Too Large>)
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
Expand Down
Loading