Skip to content

Commit 937d08a

Browse files
Add :discard_threshold option to logger handler (#849)
Closes #848. Co-authored-by: Andrea Leopardi <[email protected]>
1 parent fbbf707 commit 937d08a

File tree

2 files changed

+105
-7
lines changed

2 files changed

+105
-7
lines changed

lib/sentry/logger_handler.ex

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,32 @@ defmodule Sentry.LoggerHandler do
7676
default: nil
7777
],
7878
sync_threshold: [
79-
type: :non_neg_integer,
79+
type: {:or, [nil, :non_neg_integer]},
8080
default: 100,
8181
doc: """
82-
*since v10.6.0* - The number of queued events after which this handler switches
82+
(*since v10.6.0*) The number of queued events after which this handler switches
8383
to *sync mode*. Generally, this handler sends messages to Sentry **asynchronously**,
8484
equivalent to using `result: :none` in `Sentry.send_event/2`. However, if the number
8585
of queued events exceeds this threshold, the handler will switch to *sync mode*,
8686
where it starts using `result: :sync` to block until the event is sent. If you always
8787
want to use sync mode, set this option to `0`. This option effectively implements
8888
**overload protection**.
89+
90+
If you would rather *drop events* to shed load instead, use the `:discard_threshold` option.
91+
`:sync_threshold` and `:discard_threshold` cannot be used together. The default behavior
92+
of the handler is to switch to sync mode, so to disable this option and discard events
93+
instead set `:sync_threshold` to `nil` and set `:discard_threshold` instead.
94+
"""
95+
],
96+
discard_threshold: [
97+
type: {:or, [nil, :non_neg_integer]},
98+
default: nil,
99+
doc: """
100+
(*since v10.9.0*) The number of queued events after which this handler will start
101+
to **discard** events. This option effectively implements **load shedding**.
102+
103+
`:discard_threshold` and `:sync_threshold` cannot be used together. If you set this option,
104+
set `:sync_threshold` to `nil`.
89105
"""
90106
]
91107
]
@@ -231,7 +247,8 @@ defmodule Sentry.LoggerHandler do
231247
:tags_from_metadata,
232248
:capture_log_messages,
233249
:rate_limiting,
234-
:sync_threshold
250+
:sync_threshold,
251+
:discard_threshold
235252
]
236253

237254
## Logger handler callbacks
@@ -319,6 +336,11 @@ defmodule Sentry.LoggerHandler do
319336
config.rate_limiting && RateLimiter.increment(handler_id) == :rate_limited ->
320337
:ok
321338

339+
# Discard event.
340+
config.discard_threshold &&
341+
SenderPool.get_queued_events_counter() >= config.discard_threshold ->
342+
:ok
343+
322344
true ->
323345
# Logger handlers run in the process that logs, so we already read all the
324346
# necessary Sentry context from the process dictionary (when creating the event).
@@ -407,7 +429,14 @@ defmodule Sentry.LoggerHandler do
407429
|> Map.to_list()
408430
|> NimbleOptions.validate!(@options_schema)
409431

410-
struct!(existing_config, validated_config)
432+
config = struct!(existing_config, validated_config)
433+
434+
if config.sync_threshold && config.discard_threshold do
435+
raise ArgumentError,
436+
":sync_threshold and :discard_threshold cannot be used together, one of them must be nil"
437+
else
438+
config
439+
end
411440
end
412441

413442
defp log_from_crash_reason(
@@ -642,7 +671,8 @@ defmodule Sentry.LoggerHandler do
642671

643672
defp capture(unquote(function), exception_or_message, sentry_opts, %__MODULE__{} = config) do
644673
sentry_opts =
645-
if SenderPool.get_queued_events_counter() >= config.sync_threshold do
674+
if config.sync_threshold &&
675+
SenderPool.get_queued_events_counter() >= config.sync_threshold do
646676
Keyword.put(sentry_opts, :result, :sync)
647677
else
648678
sentry_opts

test/sentry/logger_handler_test.exs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -601,14 +601,82 @@ defmodule Sentry.LoggerHandlerTest do
601601
end
602602
end
603603

604-
defp register_before_send(_context) do
604+
describe "discard threshold" do
605+
@tag handler_config: %{
606+
discard_threshold: 2,
607+
sync_threshold: nil,
608+
capture_log_messages: true
609+
},
610+
send_request: true
611+
test "discards logged messages", %{sender_ref: ref} do
612+
register_delay()
613+
614+
Logger.error("First")
615+
assert_receive {^ref, %{message: %{formatted: "First"}}}
616+
617+
Logger.error("Second")
618+
assert_receive {^ref, %{message: %{formatted: "Second"}}}
619+
620+
Logger.error("Third")
621+
refute_receive {^ref, _event}, 100
622+
623+
Process.sleep(300)
624+
Logger.error("Fourth")
625+
assert_receive {^ref, %{message: %{formatted: "Fourth"}}}
626+
end
627+
end
628+
629+
@tag handler_config: %{
630+
sync_threshold: 2
631+
}
632+
test "cannot set discard_threshold and sync_threshold" do
633+
assert {:ok, %{config: config}} = :logger.get_handler_config(@handler_name)
634+
635+
assert {:error,
636+
{:callback_crashed,
637+
{:error,
638+
%ArgumentError{
639+
message:
640+
":sync_threshold and :discard_threshold cannot be used together, one of them must be nil"
641+
},
642+
_}}} =
643+
:logger.update_handler_config(
644+
@handler_name,
645+
:config,
646+
Map.put(config, :discard_threshold, 1)
647+
)
648+
end
649+
650+
defp register_delay do
651+
bypass = Bypass.open()
652+
653+
put_test_config(
654+
dsn: "http://public:secret@localhost:#{bypass.port}/1",
655+
dedup_events: false,
656+
hackney_opts: [recv_timeout: 500, pool: :sentry_pool]
657+
)
658+
659+
Bypass.expect(bypass, fn conn ->
660+
assert conn.request_path == "/api/1/envelope/"
661+
assert conn.method == "POST"
662+
Process.sleep(150)
663+
Plug.Conn.resp(conn, 200, ~s<{"id": "340"}>)
664+
end)
665+
end
666+
667+
defp register_before_send(context) do
605668
pid = self()
606669
ref = make_ref()
607670

608671
put_test_config(
609672
before_send: fn event ->
610673
send(pid, {ref, event})
611-
false
674+
675+
if Map.get(context, :send_request, false) do
676+
event
677+
else
678+
false
679+
end
612680
end,
613681
dsn: "http://public:secret@localhost:9392/1"
614682
)

0 commit comments

Comments
 (0)