@@ -66,26 +66,34 @@ defmodule Sentry.LoggerHandler do
6666 """ ,
6767 type_doc: "`t:keyword/0` or `nil`" ,
6868 default: nil
69+ ] ,
70+ sync_threshold: [
71+ type: :non_neg_integer ,
72+ default: 100 ,
73+ doc: """
74+ *since v10.6.0* - The number of queued events after which this handler switches
75+ to *sync mode*. Generally, this handler sends messages to Sentry **asynchronously**,
76+ equivalent to using `result: :none` in `Sentry.send_event/2`. However, if the number
77+ of queued events exceeds this threshold, the handler will switch to *sync mode*,
78+ where it starts using `result: :sync` to block until the event is sent. If you always
79+ want to use sync mode, set this option to `0`. This option effectively implements
80+ **overload protection**.
81+ """
6982 ]
7083 ]
7184
7285 @ options_schema NimbleOptions . new! ( options_schema )
7386
7487 @ moduledoc """
75- `:logger` handler to report logged events to Sentry.
76-
77- This module is similar to `Sentry.LoggerBackend`, but it implements a
78- [`:logger` handler](https://erlang.org/doc/man/logger_chapter.html#handlers) rather
79- than an Elixir's `Logger` backend. It provides additional functionality compared to
80- the `Logger` backend, such as rate-limiting of reported messages, better fingerprinting,
81- and better handling of crashes.
88+ A highly-configurable [`:logger` handler](https://erlang.org/doc/man/logger_chapter.html#handlers)
89+ that reports logged messages and crashes to Sentry.
8290
8391 *This module is available since v9.0.0 of this library*.
8492
8593 > #### When to Use the Handler vs the Backend? {: .info}
8694 >
87- > There is **no functional difference in behavior** between `Sentry.LoggerHandler` and
88- > `Sentry.LoggerBackend` when it comes to reporting to Sentry . The main functional
95+ > Sentry's Elixir SDK also ships with `Sentry.LoggerBackend`, an Elixir `Logger`
96+ > backend. The backend has similar functionality to this handler . The main functional
8997 > difference is that `Sentry.LoggerBackend` runs in its own process, while
9098 > `Sentry.LoggerHandler` runs in the process that logs. The latter is generally
9199 > preferable.
@@ -94,11 +102,15 @@ defmodule Sentry.LoggerHandler do
94102 > feature in Erlang/OTP, and `Sentry.LoggerBackend` was created before `:logger`
95103 > handlers were introduced.
96104 >
97- > In general, try to use `Sentry.LoggerHandler` if possible. In future Elixir releases,
105+ > In general, use `Sentry.LoggerHandler` whenever possible. In future Elixir releases,
98106 > `Logger` backends may become deprecated and hence `Sentry.LoggerBackend` may be
99107 > eventually removed.
100108
101- ## Crash Reports
109+ ## Features
110+
111+ This logger handler provides the features listed here.
112+
113+ ### Crash Reports
102114
103115 The reason you'll want to add this handler to your application is so that you can
104116 report **crashes** in your system to Sentry. Sometimes, you're able to catch exceptions
@@ -111,6 +123,24 @@ defmodule Sentry.LoggerHandler do
111123 crash reports in Sentry. That's where this handler comes in. This handler hooks
112124 into `:logger` and reports nicely-formatted crash reports to Sentry.
113125
126+ ### Overload Protection
127+
128+ This handler has built-in *overload protection* via the `:sync_threshold`
129+ configuration option. Under normal circumstances, this handler sends events to
130+ Sentry asynchronously, without blocking the logging process. However, if the
131+ number of queued up events exceeds the `:sync_threshold`, then this handler
132+ starts *blocking* the logging process until the event is sent.
133+
134+ *Overload protection is available since v10.6.0*.
135+
136+ ### Rate Limiting
137+
138+ You can configure this handler to rate-limit the number of messages it sends to
139+ Sentry. This can help avoid "spamming" Sentry. See the `:rate_limiting` configuration
140+ option.
141+
142+ *Rate limiting is available since v10.5.0*.
143+
114144 ## Usage
115145
116146 To add this handler to your system, see [the documentation for handlers in
@@ -183,9 +213,17 @@ defmodule Sentry.LoggerHandler do
183213
184214 alias Sentry.LoggerUtils
185215 alias Sentry.LoggerHandler.RateLimiter
216+ alias Sentry.Transport.SenderPool
186217
187218 # The config for this logger handler.
188- defstruct [ :level , :excluded_domains , :metadata , :capture_log_messages , :rate_limiting ]
219+ defstruct [
220+ :level ,
221+ :excluded_domains ,
222+ :metadata ,
223+ :capture_log_messages ,
224+ :rate_limiting ,
225+ :sync_threshold
226+ ]
189227
190228 ## Logger handler callbacks
191229
@@ -301,30 +339,29 @@ defmodule Sentry.LoggerHandler do
301339 end
302340
303341 # "report" here is of type logger:report/0, which is a map or keyword list.
304- defp log_unfiltered ( % { msg: { :report , report } } , sentry_opts , % __MODULE__ { } = _config ) do
342+ defp log_unfiltered ( % { msg: { :report , report } } , sentry_opts , % __MODULE__ { } = config ) do
305343 case Map . new ( report ) do
306344 % { report: % { reason: { exception , stacktrace } } }
307345 when is_exception ( exception ) and is_list ( stacktrace ) ->
308346 sentry_opts = Keyword . merge ( sentry_opts , stacktrace: stacktrace , handled: false )
309- Sentry . capture_exception ( exception , sentry_opts )
347+ capture ( : exception, exception , sentry_opts , config )
310348
311349 % { report: % { reason: { reason , stacktrace } } } when is_list ( stacktrace ) ->
312350 sentry_opts = Keyword . put ( sentry_opts , :stacktrace , stacktrace )
313- Sentry . capture_message ( "** (stop) " <> Exception . format_exit ( reason ) , sentry_opts )
351+ capture ( :message , "** (stop) " <> Exception . format_exit ( reason ) , sentry_opts , config )
314352
315353 % { report: report_info } ->
316- Sentry . capture_message ( inspect ( report_info ) , sentry_opts )
354+ capture ( :message , inspect ( report_info ) , sentry_opts , config )
317355
318356 % { reason: { reason , stacktrace } } when is_list ( stacktrace ) ->
319357 sentry_opts = Keyword . put ( sentry_opts , :stacktrace , stacktrace )
320- Sentry . capture_message ( "** (stop) " <> Exception . format_exit ( reason ) , sentry_opts )
358+ capture ( :message , "** (stop) " <> Exception . format_exit ( reason ) , sentry_opts , config )
321359
322360 % { reason: reason } ->
323361 sentry_opts =
324362 Keyword . update! ( sentry_opts , :extra , & Map . put ( & 1 , :crash_reason , inspect ( reason ) ) )
325363
326- msg = "** (stop) #{ Exception . format_exit ( reason ) } "
327- Sentry . capture_message ( msg , sentry_opts )
364+ capture ( :message , "** (stop) #{ Exception . format_exit ( reason ) } " , sentry_opts , config )
328365
329366 _other ->
330367 :ok
@@ -355,14 +392,19 @@ defmodule Sentry.LoggerHandler do
355392 { exception , stacktrace } ,
356393 _chardata_message ,
357394 sentry_opts ,
358- % __MODULE__ { }
395+ % __MODULE__ { } = config
359396 )
360397 when is_exception ( exception ) and is_list ( stacktrace ) do
361398 sentry_opts = Keyword . merge ( sentry_opts , stacktrace: stacktrace , handled: false )
362- Sentry . capture_exception ( exception , sentry_opts )
399+ capture ( : exception, exception , sentry_opts , config )
363400 end
364401
365- defp log_from_crash_reason ( { reason , stacktrace } , chardata_message , sentry_opts , % __MODULE__ { } )
402+ defp log_from_crash_reason (
403+ { reason , stacktrace } ,
404+ chardata_message ,
405+ sentry_opts ,
406+ % __MODULE__ { } = config
407+ )
366408 when is_list ( stacktrace ) do
367409 sentry_opts =
368410 sentry_opts
@@ -380,18 +422,23 @@ defmodule Sentry.LoggerHandler do
380422 inspect ( call )
381423 ] )
382424
383- Sentry . capture_message ( Exception . format_exit ( reason ) , sentry_opts )
425+ capture ( :message , Exception . format_exit ( reason ) , sentry_opts , config )
384426
385427 _other ->
386- try_to_parse_message_or_just_report_it ( chardata_message , sentry_opts )
428+ try_to_parse_message_or_just_report_it ( chardata_message , sentry_opts , config )
387429 end
388430 end
389431
390- defp log_from_crash_reason ( _other_reason , chardata_message , sentry_opts , % __MODULE__ {
391- capture_log_messages: true
392- } ) do
432+ defp log_from_crash_reason (
433+ _other_reason ,
434+ chardata_message ,
435+ sentry_opts ,
436+ % __MODULE__ {
437+ capture_log_messages: true
438+ } = config
439+ ) do
393440 string_message = :unicode . characters_to_binary ( chardata_message )
394- Sentry . capture_message ( string_message , sentry_opts )
441+ capture ( :message , string_message , sentry_opts , config )
395442 end
396443
397444 defp log_from_crash_reason ( _other_reason , _string_message , _sentry_opts , _config ) do
@@ -439,7 +486,8 @@ defmodule Sentry.LoggerHandler do
439486 "\n State: " ,
440487 inspected_state | _
441488 ] ,
442- sentry_opts
489+ sentry_opts ,
490+ config
443491 ) do
444492 string_reason = chardata_reason |> :unicode . characters_to_binary ( ) |> String . trim ( )
445493
@@ -452,7 +500,7 @@ defmodule Sentry.LoggerHandler do
452500 genserver_state: inspected_state
453501 } )
454502
455- Sentry . capture_message ( "GenServer %s terminating: #{ string_reason } " , sentry_opts )
503+ capture ( :message , "GenServer %s terminating: #{ string_reason } " , sentry_opts , config )
456504 end
457505
458506 defp try_to_parse_message_or_just_report_it (
@@ -468,7 +516,8 @@ defmodule Sentry.LoggerHandler do
468516 "\n State: " ,
469517 inspected_state | _
470518 ] ,
471- sentry_opts
519+ sentry_opts ,
520+ config
472521 ) do
473522 string_reason = chardata_reason |> :unicode . characters_to_binary ( ) |> String . trim ( )
474523
@@ -480,15 +529,30 @@ defmodule Sentry.LoggerHandler do
480529 genserver_state: inspected_state
481530 } )
482531
483- Sentry . capture_message ( "GenServer %s terminating: #{ string_reason } " , sentry_opts )
532+ capture ( :message , "GenServer %s terminating: #{ string_reason } " , sentry_opts , config )
484533 end
485534
486- defp try_to_parse_message_or_just_report_it ( chardata_message , sentry_opts ) do
535+ defp try_to_parse_message_or_just_report_it ( chardata_message , sentry_opts , config ) do
487536 string_message = :unicode . characters_to_binary ( chardata_message )
488- Sentry . capture_message ( string_message , sentry_opts )
537+ capture ( :message , string_message , sentry_opts , config )
489538 end
490539
491540 defp add_extra_to_sentry_opts ( sentry_opts , new_extra ) do
492541 Keyword . update ( sentry_opts , :extra , % { } , & Map . merge ( new_extra , & 1 ) )
493542 end
543+
544+ for function <- [ :exception , :message ] do
545+ sentry_fun = :"capture_#{ function } "
546+
547+ defp capture ( unquote ( function ) , exception_or_message , sentry_opts , % __MODULE__ { } = config ) do
548+ sentry_opts =
549+ if SenderPool . get_queued_events_counter ( ) >= config . sync_threshold do
550+ Keyword . put ( sentry_opts , :result , :sync )
551+ else
552+ sentry_opts
553+ end
554+
555+ Sentry . unquote ( sentry_fun ) ( exception_or_message , sentry_opts )
556+ end
557+ end
494558end
0 commit comments