Skip to content

Commit 84a0b86

Browse files
committed
allow custom table name through options
1 parent f33d00d commit 84a0b86

File tree

5 files changed

+133
-87
lines changed

5 files changed

+133
-87
lines changed

lib/sentry/application.ex

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ defmodule Sentry.Application do
3333
[]
3434
end
3535

36+
# Don't start RateLimiter in test environment - tests start their own instances
37+
maybe_rate_limiter =
38+
if Mix.env() == :test do
39+
[]
40+
else
41+
[Sentry.Transport.RateLimiter]
42+
end
43+
3644
children =
3745
[
3846
{Registry, keys: :unique, name: Sentry.Transport.SenderRegistry},
@@ -47,7 +55,8 @@ defmodule Sentry.Application do
4755
] ++
4856
maybe_http_client_spec ++
4957
maybe_span_storage ++
50-
[Sentry.Transport.RateLimiter, Sentry.Transport.SenderPool]
58+
maybe_rate_limiter ++
59+
[Sentry.Transport.SenderPool]
5160

5261
cache_loaded_applications()
5362

lib/sentry/transport/rate_limiter.ex

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,45 @@ defmodule Sentry.Transport.RateLimiter do
1010

1111
use GenServer
1212

13-
@table __MODULE__
14-
@sweep_interval_ms 60_000
13+
@default_sweep_interval_ms 60_000
14+
15+
defstruct [:table_name]
1516

1617
## Public API
1718

1819
@doc """
1920
Starts the RateLimiter GenServer.
21+
22+
## Options
23+
24+
* `:name` - The name to register the GenServer under. Defaults to `__MODULE__`.
25+
* `:table_name` - The name for the ETS table. Defaults to `__MODULE__`.
26+
2027
"""
2128
@spec start_link(keyword()) :: GenServer.on_start()
2229
def start_link(opts \\ []) do
23-
GenServer.start_link(__MODULE__, nil, name: Keyword.get(opts, :name, __MODULE__))
30+
name = Keyword.get(opts, :name, __MODULE__)
31+
GenServer.start_link(__MODULE__, opts, name: name)
2432
end
2533

2634
## GenServer Callbacks
2735

2836
@impl true
29-
def init(nil) do
30-
_table = :ets.new(@table, [:named_table, :public, :set, read_concurrency: true])
37+
def init(opts) do
38+
table_name = Keyword.get(opts, :table_name, __MODULE__)
39+
_table = :ets.new(table_name, [:named_table, :public, :set, read_concurrency: true])
3140
schedule_sweep()
32-
{:ok, :no_state}
41+
{:ok, %__MODULE__{table_name: table_name}}
3342
end
3443

3544
@impl true
36-
def handle_info(:sweep, state) do
45+
def handle_info(:sweep, %__MODULE__{table_name: table_name} = state) do
3746
now = System.system_time(:second)
3847

3948
# Match spec: select entries where expiry (position 2) < now
4049
match_spec = [{{:"$1", :"$2"}, [{:<, :"$2", now}], [true]}]
4150

42-
:ets.select_delete(@table, match_spec)
51+
:ets.select_delete(table_name, match_spec)
4352

4453
schedule_sweep()
4554
{:noreply, state}
@@ -53,6 +62,10 @@ defmodule Sentry.Transport.RateLimiter do
5362
Returns `true` if the category is rate-limited (either specifically or via
5463
a global rate limit), `false` otherwise.
5564
65+
## Options
66+
67+
* `:table_name` - The ETS table name. Defaults to `__MODULE__`.
68+
5669
## Examples
5770
5871
iex> RateLimiter.rate_limited?("error")
@@ -63,10 +76,11 @@ defmodule Sentry.Transport.RateLimiter do
6376
true
6477
6578
"""
66-
@spec rate_limited?(String.t()) :: boolean()
67-
def rate_limited?(category) do
79+
@spec rate_limited?(String.t(), keyword()) :: boolean()
80+
def rate_limited?(category, opts \\ []) do
81+
table_name = Keyword.get(opts, :table_name, __MODULE__)
6882
now = System.system_time(:second)
69-
check_rate_limited(category, now) or check_rate_limited(:global, now)
83+
check_rate_limited(table_name, category, now) or check_rate_limited(table_name, :global, now)
7084
end
7185

7286
@doc """
@@ -75,17 +89,23 @@ defmodule Sentry.Transport.RateLimiter do
7589
This is a fallback for when X-Sentry-Rate-Limits is not present.
7690
Stores a global rate limit (:global key) that affects all categories.
7791
92+
## Options
93+
94+
* `:table_name` - The ETS table name. Defaults to `__MODULE__`.
95+
7896
## Examples
7997
8098
iex> RateLimiter.update_global_rate_limit(60)
8199
:ok
82100
83101
"""
84-
@spec update_global_rate_limit(pos_integer()) :: :ok
85-
def update_global_rate_limit(retry_after_seconds) when is_integer(retry_after_seconds) do
102+
@spec update_global_rate_limit(pos_integer(), keyword()) :: :ok
103+
def update_global_rate_limit(retry_after_seconds, opts \\ [])
104+
when is_integer(retry_after_seconds) do
105+
table_name = Keyword.get(opts, :table_name, __MODULE__)
86106
now = System.system_time(:second)
87107
expiry = now + retry_after_seconds
88-
:ets.insert(@table, {:global, expiry})
108+
:ets.insert(table_name, {:global, expiry})
89109
:ok
90110
end
91111

@@ -95,30 +115,35 @@ defmodule Sentry.Transport.RateLimiter do
95115
Parses the header value and stores expiry timestamps for each category.
96116
Returns `:ok` regardless of parsing success.
97117
118+
## Options
119+
120+
* `:table_name` - The ETS table name. Defaults to `__MODULE__`.
121+
98122
## Examples
99123
100124
iex> RateLimiter.update_rate_limits("60:error;transaction")
101125
:ok
102126
103127
"""
104-
@spec update_rate_limits(String.t()) :: :ok
105-
def update_rate_limits(rate_limits_header) do
128+
@spec update_rate_limits(String.t(), keyword()) :: :ok
129+
def update_rate_limits(rate_limits_header, opts \\ []) do
130+
table_name = Keyword.get(opts, :table_name, __MODULE__)
106131
now = System.system_time(:second)
107132
rate_limits = parse_rate_limits_header(rate_limits_header)
108133

109134
Enum.each(rate_limits, fn {category, retry_after_seconds} ->
110135
expiry = now + retry_after_seconds
111-
:ets.insert(@table, {category, expiry})
136+
:ets.insert(table_name, {category, expiry})
112137
end)
113138

114139
:ok
115140
end
116141

117142
## Private Helpers
118143

119-
@spec check_rate_limited(String.t() | :global, integer()) :: boolean()
120-
defp check_rate_limited(category, time) do
121-
case :ets.lookup(@table, category) do
144+
@spec check_rate_limited(atom(), String.t() | :global, integer()) :: boolean()
145+
defp check_rate_limited(table_name, category, time) do
146+
case :ets.lookup(table_name, category) do
122147
[{^category, expiry}] when expiry > time -> true
123148
_ -> false
124149
end
@@ -167,6 +192,6 @@ defmodule Sentry.Transport.RateLimiter do
167192

168193
@spec schedule_sweep() :: reference()
169194
defp schedule_sweep do
170-
Process.send_after(self(), :sweep, @sweep_interval_ms)
195+
Process.send_after(self(), :sweep, @default_sweep_interval_ms)
171196
end
172197
end

test/sentry/test_test.exs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ defmodule Sentry.TestTest do
99
doctest Test
1010

1111
setup do
12+
# Start RateLimiter for tests that send events through Transport
13+
start_supervised!(Sentry.Transport.RateLimiter)
14+
1215
bypass = Bypass.open()
1316
put_test_config(dsn: "http://public:secret@localhost:#{bypass.port}/1", dedup_events: false)
1417
%{bypass: bypass}

0 commit comments

Comments
 (0)