@@ -66,26 +66,34 @@ defmodule Sentry.LoggerHandler do
66
66
""" ,
67
67
type_doc: "`t:keyword/0` or `nil`" ,
68
68
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
+ """
69
82
]
70
83
]
71
84
72
85
@ options_schema NimbleOptions . new! ( options_schema )
73
86
74
87
@ 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.
82
90
83
91
*This module is available since v9.0.0 of this library*.
84
92
85
93
> #### When to Use the Handler vs the Backend? {: .info}
86
94
>
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
89
97
> difference is that `Sentry.LoggerBackend` runs in its own process, while
90
98
> `Sentry.LoggerHandler` runs in the process that logs. The latter is generally
91
99
> preferable.
@@ -94,11 +102,15 @@ defmodule Sentry.LoggerHandler do
94
102
> feature in Erlang/OTP, and `Sentry.LoggerBackend` was created before `:logger`
95
103
> handlers were introduced.
96
104
>
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,
98
106
> `Logger` backends may become deprecated and hence `Sentry.LoggerBackend` may be
99
107
> eventually removed.
100
108
101
- ## Crash Reports
109
+ ## Features
110
+
111
+ This logger handler provides the features listed here.
112
+
113
+ ### Crash Reports
102
114
103
115
The reason you'll want to add this handler to your application is so that you can
104
116
report **crashes** in your system to Sentry. Sometimes, you're able to catch exceptions
@@ -111,6 +123,24 @@ defmodule Sentry.LoggerHandler do
111
123
crash reports in Sentry. That's where this handler comes in. This handler hooks
112
124
into `:logger` and reports nicely-formatted crash reports to Sentry.
113
125
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
+
114
144
## Usage
115
145
116
146
To add this handler to your system, see [the documentation for handlers in
@@ -183,9 +213,17 @@ defmodule Sentry.LoggerHandler do
183
213
184
214
alias Sentry.LoggerUtils
185
215
alias Sentry.LoggerHandler.RateLimiter
216
+ alias Sentry.Transport.SenderPool
186
217
187
218
# 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
+ ]
189
227
190
228
## Logger handler callbacks
191
229
@@ -301,30 +339,29 @@ defmodule Sentry.LoggerHandler do
301
339
end
302
340
303
341
# "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
305
343
case Map . new ( report ) do
306
344
% { report: % { reason: { exception , stacktrace } } }
307
345
when is_exception ( exception ) and is_list ( stacktrace ) ->
308
346
sentry_opts = Keyword . merge ( sentry_opts , stacktrace: stacktrace , handled: false )
309
- Sentry . capture_exception ( exception , sentry_opts )
347
+ capture ( : exception, exception , sentry_opts , config )
310
348
311
349
% { report: % { reason: { reason , stacktrace } } } when is_list ( stacktrace ) ->
312
350
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 )
314
352
315
353
% { report: report_info } ->
316
- Sentry . capture_message ( inspect ( report_info ) , sentry_opts )
354
+ capture ( :message , inspect ( report_info ) , sentry_opts , config )
317
355
318
356
% { reason: { reason , stacktrace } } when is_list ( stacktrace ) ->
319
357
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 )
321
359
322
360
% { reason: reason } ->
323
361
sentry_opts =
324
362
Keyword . update! ( sentry_opts , :extra , & Map . put ( & 1 , :crash_reason , inspect ( reason ) ) )
325
363
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 )
328
365
329
366
_other ->
330
367
:ok
@@ -355,14 +392,19 @@ defmodule Sentry.LoggerHandler do
355
392
{ exception , stacktrace } ,
356
393
_chardata_message ,
357
394
sentry_opts ,
358
- % __MODULE__ { }
395
+ % __MODULE__ { } = config
359
396
)
360
397
when is_exception ( exception ) and is_list ( stacktrace ) do
361
398
sentry_opts = Keyword . merge ( sentry_opts , stacktrace: stacktrace , handled: false )
362
- Sentry . capture_exception ( exception , sentry_opts )
399
+ capture ( : exception, exception , sentry_opts , config )
363
400
end
364
401
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
+ )
366
408
when is_list ( stacktrace ) do
367
409
sentry_opts =
368
410
sentry_opts
@@ -380,18 +422,23 @@ defmodule Sentry.LoggerHandler do
380
422
inspect ( call )
381
423
] )
382
424
383
- Sentry . capture_message ( Exception . format_exit ( reason ) , sentry_opts )
425
+ capture ( :message , Exception . format_exit ( reason ) , sentry_opts , config )
384
426
385
427
_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 )
387
429
end
388
430
end
389
431
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
393
440
string_message = :unicode . characters_to_binary ( chardata_message )
394
- Sentry . capture_message ( string_message , sentry_opts )
441
+ capture ( :message , string_message , sentry_opts , config )
395
442
end
396
443
397
444
defp log_from_crash_reason ( _other_reason , _string_message , _sentry_opts , _config ) do
@@ -439,7 +486,8 @@ defmodule Sentry.LoggerHandler do
439
486
"\n State: " ,
440
487
inspected_state | _
441
488
] ,
442
- sentry_opts
489
+ sentry_opts ,
490
+ config
443
491
) do
444
492
string_reason = chardata_reason |> :unicode . characters_to_binary ( ) |> String . trim ( )
445
493
@@ -452,7 +500,7 @@ defmodule Sentry.LoggerHandler do
452
500
genserver_state: inspected_state
453
501
} )
454
502
455
- Sentry . capture_message ( "GenServer %s terminating: #{ string_reason } " , sentry_opts )
503
+ capture ( :message , "GenServer %s terminating: #{ string_reason } " , sentry_opts , config )
456
504
end
457
505
458
506
defp try_to_parse_message_or_just_report_it (
@@ -468,7 +516,8 @@ defmodule Sentry.LoggerHandler do
468
516
"\n State: " ,
469
517
inspected_state | _
470
518
] ,
471
- sentry_opts
519
+ sentry_opts ,
520
+ config
472
521
) do
473
522
string_reason = chardata_reason |> :unicode . characters_to_binary ( ) |> String . trim ( )
474
523
@@ -480,15 +529,30 @@ defmodule Sentry.LoggerHandler do
480
529
genserver_state: inspected_state
481
530
} )
482
531
483
- Sentry . capture_message ( "GenServer %s terminating: #{ string_reason } " , sentry_opts )
532
+ capture ( :message , "GenServer %s terminating: #{ string_reason } " , sentry_opts , config )
484
533
end
485
534
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
487
536
string_message = :unicode . characters_to_binary ( chardata_message )
488
- Sentry . capture_message ( string_message , sentry_opts )
537
+ capture ( :message , string_message , sentry_opts , config )
489
538
end
490
539
491
540
defp add_extra_to_sentry_opts ( sentry_opts , new_extra ) do
492
541
Keyword . update ( sentry_opts , :extra , % { } , & Map . merge ( new_extra , & 1 ) )
493
542
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
494
558
end
0 commit comments