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
14 changes: 12 additions & 2 deletions lib/sentry/transport.ex
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ defmodule Sentry.Transport do
end

defp update_rate_limits(headers, status) do
rate_limits_header = :proplists.get_value("X-Sentry-Rate-Limits", headers, nil)
rate_limits_header = get_header(headers, "x-sentry-rate-limits")

cond do
is_binary(rate_limits_header) ->
Expand All @@ -154,7 +154,7 @@ defmodule Sentry.Transport do
end

defp get_global_delay(headers) do
with timeout when is_binary(timeout) <- :proplists.get_value("Retry-After", headers, nil),
with timeout when is_binary(timeout) <- get_header(headers, "retry-after"),
{delay, ""} <- Integer.parse(timeout) do
delay
else
Expand All @@ -164,6 +164,16 @@ defmodule Sentry.Transport do
end
end

defp get_header(headers, lowercase_name) do
Enum.find_value(headers, fn
{header_name, value} when is_binary(header_name) ->
if String.downcase(header_name) == lowercase_name, do: value

_other ->
nil
end)
end

defp get_endpoint_and_headers do
%Sentry.DSN{} = dsn = Config.dsn()

Expand Down
4 changes: 1 addition & 3 deletions test/sentry/telemetry_processor_integration_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,7 @@ defmodule Sentry.TelemetryProcessorIntegrationTest do
ref = make_ref()
request_count = :counters.new(1, [])

# Use HackneyClient because FinchClient (Mint) lowercases response headers,
# which prevents the transport from matching "X-Sentry-Rate-Limits".
put_test_config(client: Sentry.HackneyClient)
put_test_config(client: Sentry.FinchClient)

Bypass.expect(ctx.bypass, "POST", "/api/1/envelope/", fn conn ->
count = :counters.get(request_count, 1)
Expand Down
67 changes: 67 additions & 0 deletions test/sentry_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,32 @@ defmodule SentryTest do
assert :ignored = Sentry.send_event(event)
end

test "reads Retry-After response headers case-insensitively", %{bypass: bypass} do
request_count = :counters.new(1, [])
put_test_config(client: Sentry.FinchClient)

Bypass.expect(bypass, "POST", "/api/1/envelope/", fn conn ->
request_number = :counters.get(request_count, 1)
:counters.add(request_count, 1, 1)

if request_number == 0 do
conn
|> Plug.Conn.put_resp_header("Retry-After", "0")
|> Plug.Conn.resp(429, ~s<{}>)
else
Plug.Conn.resp(conn, 200, ~s<{"id": "#{Sentry.UUID.uuid4_hex()}"}>)
end
end)

assert {:error, %Sentry.ClientError{reason: :rate_limited}} =
Sentry.capture_message("rate-limited", result: :sync, request_retries: [])

assert {:ok, _event_id} =
Sentry.capture_message("accepted", result: :sync, request_retries: [])

assert :counters.get(request_count, 1) == 2
end

describe "send_check_in/1" do
test "posts a check-in with all the explicit arguments", %{bypass: bypass} do
put_test_config(environment_name: "test", release: "1.3.2")
Expand Down Expand Up @@ -248,6 +274,47 @@ defmodule SentryTest do
assert_sentry_report(:transaction, transaction: "test-transaction")
end

test "does not apply categorized error rate limits to transactions", %{
bypass: bypass,
transaction: transaction
} do
test_pid = self()
ref = make_ref()
request_count = :counters.new(1, [])
put_test_config(client: Sentry.FinchClient)

Bypass.expect(bypass, "POST", "/api/1/envelope/", fn conn ->
request_number = :counters.get(request_count, 1)
:counters.add(request_count, 1, 1)

if request_number == 0 do
conn
|> Plug.Conn.put_resp_header("X-Sentry-Rate-Limits", "60:error:organization")
|> Plug.Conn.resp(429, ~s<{"error": "Rate limited"}>)
else
{:ok, body, conn} = Plug.Conn.read_body(conn)

if body =~ ~s("type":"transaction") do
send(test_pid, {:bypass_envelope, ref, body})
end

Plug.Conn.resp(conn, 200, ~s<{"id": "#{Sentry.UUID.uuid4_hex()}"}>)
end
end)

assert {:error, %Sentry.ClientError{reason: :rate_limited}} =
Sentry.capture_message(
"rate-limited-error",
result: :sync,
request_retries: []
)

assert {:ok, _event_id} =
Sentry.send_transaction(transaction, result: :sync, request_retries: [])

assert_sentry_transaction(ref, transaction: "test-transaction")
end

test "validates options", %{transaction: transaction} do
assert_raise NimbleOptions.ValidationError, fn ->
Sentry.send_transaction(transaction, client: "oops")
Expand Down
Loading