sentry-ruby-core 4.1.6 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,23 +1,27 @@
1
1
  require "concurrent/executor/thread_pool_executor"
2
2
  require "concurrent/executor/immediate_executor"
3
+ require "concurrent/configuration"
3
4
 
4
5
  module Sentry
5
6
  class BackgroundWorker
6
- attr_reader :max_queue, :number_of_threads
7
+ include LoggingHelper
8
+
9
+ attr_reader :max_queue, :number_of_threads, :logger
7
10
 
8
11
  def initialize(configuration)
9
12
  @max_queue = 30
10
13
  @number_of_threads = configuration.background_worker_threads
14
+ @logger = configuration.logger
11
15
 
12
16
  @executor =
13
17
  if configuration.async
14
- configuration.logger.debug(LOGGER_PROGNAME) { "config.async is set, BackgroundWorker is disabled" }
18
+ log_debug("config.async is set, BackgroundWorker is disabled")
15
19
  Concurrent::ImmediateExecutor.new
16
20
  elsif @number_of_threads == 0
17
- configuration.logger.debug(LOGGER_PROGNAME) { "config.background_worker_threads is set to 0, all events will be sent synchronously" }
21
+ log_debug("config.background_worker_threads is set to 0, all events will be sent synchronously")
18
22
  Concurrent::ImmediateExecutor.new
19
23
  else
20
- configuration.logger.debug(LOGGER_PROGNAME) { "initialized a background worker with #{@number_of_threads} threads" }
24
+ log_debug("initialized a background worker with #{@number_of_threads} threads")
21
25
 
22
26
  Concurrent::ThreadPoolExecutor.new(
23
27
  min_threads: 0,
@@ -2,15 +2,16 @@ module Sentry
2
2
  class Breadcrumb
3
3
  DATA_SERIALIZATION_ERROR_MESSAGE = "[data were removed due to serialization issues]"
4
4
 
5
- attr_accessor :category, :data, :message, :level, :timestamp, :type
5
+ attr_accessor :category, :data, :level, :timestamp, :type
6
+ attr_reader :message
6
7
 
7
8
  def initialize(category: nil, data: nil, message: nil, timestamp: nil, level: nil, type: nil)
8
9
  @category = category
9
10
  @data = data || {}
10
11
  @level = level
11
- @message = message
12
12
  @timestamp = timestamp || Sentry.utc_now.to_i
13
13
  @type = type
14
+ self.message = message
14
15
  end
15
16
 
16
17
  def to_hash
@@ -24,6 +25,10 @@ module Sentry
24
25
  }
25
26
  end
26
27
 
28
+ def message=(msg)
29
+ @message = (msg || "").byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
30
+ end
31
+
27
32
  private
28
33
 
29
34
  def serialized_data
@@ -14,6 +14,7 @@ module Sentry
14
14
  def add(*args, &block)
15
15
  super
16
16
  add_breadcrumb(*args, &block)
17
+ nil
17
18
  end
18
19
 
19
20
  def add_breadcrumb(severity, message = nil, progname = nil)
@@ -49,7 +50,7 @@ module Sentry
49
50
  end
50
51
  end
51
52
 
52
- return if ignored_logger?(progname) || message.empty?
53
+ return if ignored_logger?(progname) || message == ""
53
54
 
54
55
  # some loggers will add leading/trailing space as they (incorrectly, mind you)
55
56
  # think of logging as a shortcut to std{out,err}
@@ -2,12 +2,13 @@ require "sentry/breadcrumb"
2
2
 
3
3
  module Sentry
4
4
  class BreadcrumbBuffer
5
+ DEFAULT_SIZE = 100
5
6
  include Enumerable
6
7
 
7
8
  attr_accessor :buffer
8
9
 
9
- def initialize(size = 100)
10
- @buffer = Array.new(size)
10
+ def initialize(size = nil)
11
+ @buffer = Array.new(size || DEFAULT_SIZE)
11
12
  end
12
13
 
13
14
  def record(crumb)
data/lib/sentry/client.rb CHANGED
@@ -2,10 +2,13 @@ require "sentry/transport"
2
2
 
3
3
  module Sentry
4
4
  class Client
5
- attr_reader :transport, :configuration
5
+ include LoggingHelper
6
+
7
+ attr_reader :transport, :configuration, :logger
6
8
 
7
9
  def initialize(configuration)
8
10
  @configuration = configuration
11
+ @logger = configuration.logger
9
12
 
10
13
  if transport_class = configuration.transport.transport_class
11
14
  @transport = transport_class.new(configuration)
@@ -26,32 +29,17 @@ module Sentry
26
29
  scope.apply_to_event(event, hint)
27
30
 
28
31
  if async_block = configuration.async
29
- begin
30
- # We have to convert to a JSON-like hash, because background job
31
- # processors (esp ActiveJob) may not like weird types in the event hash
32
- event_hash = event.to_json_compatible
33
-
34
- if async_block.arity == 2
35
- hint = JSON.parse(JSON.generate(hint))
36
- async_block.call(event_hash, hint)
37
- else
38
- async_block.call(event_hash)
39
- end
40
- rescue => e
41
- configuration.logger.error(LOGGER_PROGNAME) { "async event sending failed: #{e.message}" }
42
- send_event(event, hint)
43
- end
32
+ dispatch_async_event(async_block, event, hint)
33
+ elsif configuration.background_worker_threads != 0 && hint.fetch(:background, true)
34
+ dispatch_background_event(event, hint)
44
35
  else
45
- if hint.fetch(:background, true)
46
- Sentry.background_worker.perform do
47
- send_event(event, hint)
48
- end
49
- else
50
- send_event(event, hint)
51
- end
36
+ send_event(event, hint)
52
37
  end
53
38
 
54
39
  event
40
+ rescue => e
41
+ log_error("Event capturing failed", e, debug: configuration.debug)
42
+ nil
55
43
  end
56
44
 
57
45
  def event_from_exception(exception, hint = {})
@@ -60,12 +48,15 @@ module Sentry
60
48
 
61
49
  Event.new(configuration: configuration, integration_meta: integration_meta).tap do |event|
62
50
  event.add_exception_interface(exception)
51
+ event.add_threads_interface(crashed: true)
63
52
  end
64
53
  end
65
54
 
66
55
  def event_from_message(message, hint = {})
67
56
  integration_meta = Sentry.integrations[hint[:integration]]
68
- Event.new(configuration: configuration, integration_meta: integration_meta, message: message)
57
+ event = Event.new(configuration: configuration, integration_meta: integration_meta, message: message)
58
+ event.add_threads_interface(backtrace: caller)
59
+ event
69
60
  end
70
61
 
71
62
  def event_from_transaction(transaction)
@@ -82,16 +73,59 @@ module Sentry
82
73
 
83
74
  def send_event(event, hint = nil)
84
75
  event_type = event.is_a?(Event) ? event.type : event["type"]
85
- event = configuration.before_send.call(event, hint) if configuration.before_send && event_type == "event"
86
76
 
87
- if event.nil?
88
- configuration.logger.info(LOGGER_PROGNAME) { "Discarded event because before_send returned nil" }
89
- return
77
+ if event_type != TransactionEvent::TYPE && configuration.before_send
78
+ event = configuration.before_send.call(event, hint)
79
+
80
+ if event.nil?
81
+ log_info("Discarded event because before_send returned nil")
82
+ return
83
+ end
90
84
  end
91
85
 
92
86
  transport.send_event(event)
93
87
 
94
88
  event
89
+ rescue => e
90
+ loggable_event_type = (event_type || "event").capitalize
91
+ log_error("#{loggable_event_type} sending failed", e, debug: configuration.debug)
92
+
93
+ event_info = Event.get_log_message(event.to_hash)
94
+ log_info("Unreported #{loggable_event_type}: #{event_info}")
95
+ raise
96
+ end
97
+
98
+ def generate_sentry_trace(span)
99
+ return unless configuration.propagate_traces
100
+
101
+ trace = span.to_sentry_trace
102
+ log_debug("[Tracing] Adding #{SENTRY_TRACE_HEADER_NAME} header to outgoing request: #{trace}")
103
+ trace
104
+ end
105
+
106
+ private
107
+
108
+ def dispatch_background_event(event, hint)
109
+ Sentry.background_worker.perform do
110
+ send_event(event, hint)
111
+ end
112
+ end
113
+
114
+ def dispatch_async_event(async_block, event, hint)
115
+ # We have to convert to a JSON-like hash, because background job
116
+ # processors (esp ActiveJob) may not like weird types in the event hash
117
+ event_hash = event.to_json_compatible
118
+
119
+ if async_block.arity == 2
120
+ hint = JSON.parse(JSON.generate(hint))
121
+ async_block.call(event_hash, hint)
122
+ else
123
+ async_block.call(event_hash)
124
+ end
125
+ rescue => e
126
+ loggable_event_type = event_hash["type"] || "event"
127
+ log_error("Async #{loggable_event_type} sending failed", e, debug: configuration.debug)
128
+ send_event(event, hint)
95
129
  end
96
130
  end
97
131
  end
@@ -4,9 +4,11 @@ require "sentry/utils/exception_cause_chain"
4
4
  require "sentry/dsn"
5
5
  require "sentry/transport/configuration"
6
6
  require "sentry/linecache"
7
+ require "sentry/interfaces/stacktrace_builder"
7
8
 
8
9
  module Sentry
9
10
  class Configuration
11
+ include LoggingHelper
10
12
  # Directories to be recognized as part of your app. e.g. if you
11
13
  # have an `engines` dir at the root of your project, you may want
12
14
  # to set this to something like /(app|config|engines|lib)/
@@ -38,10 +40,19 @@ module Sentry
38
40
  #
39
41
  attr_accessor :backtrace_cleanup_callback
40
42
 
43
+ # Optional Proc, called before adding the breadcrumb to the current scope
44
+ # E.g.: lambda { |breadcrumb, hint| breadcrumb }
45
+ # E.g.: lambda { |breadcrumb, hint| nil }
46
+ # E.g.: lambda { |breadcrumb, hint|
47
+ # breadcrumb.message = 'a'
48
+ # breadcrumb
49
+ # }
50
+ attr_reader :before_breadcrumb
51
+
41
52
  # Optional Proc, called before sending an event to the server/
42
- # E.g.: lambda { |event| event }
43
- # E.g.: lambda { |event| nil }
44
- # E.g.: lambda { |event|
53
+ # E.g.: lambda { |event, hint| event }
54
+ # E.g.: lambda { |event, hint| nil }
55
+ # E.g.: lambda { |event, hint|
45
56
  # event[:message] = 'a'
46
57
  # event
47
58
  # }
@@ -52,12 +63,19 @@ module Sentry
52
63
  # - :active_support_logger
53
64
  attr_reader :breadcrumbs_logger
54
65
 
66
+ # Max number of breadcrumbs a breadcrumb buffer can hold
67
+ attr_accessor :max_breadcrumbs
68
+
55
69
  # Number of lines of code context to capture, or nil for none
56
70
  attr_accessor :context_lines
57
71
 
58
72
  # RACK_ENV by default.
59
73
  attr_reader :environment
60
74
 
75
+ # Whether the SDK should run in the debugging mode. Default is false.
76
+ # If set to true, SDK errors will be logged with backtrace
77
+ attr_accessor :debug
78
+
61
79
  # the dsn value, whether it's set via `config.dsn=` or `ENV["SENTRY_DSN"]`
62
80
  attr_reader :dsn
63
81
 
@@ -88,6 +106,9 @@ module Sentry
88
106
  # Set automatically for Rails.
89
107
  attr_reader :project_root
90
108
 
109
+ # Insert sentry-trace to outgoing requests' headers
110
+ attr_accessor :propagate_traces
111
+
91
112
  # Array of rack env parameters to be included in the event sent to sentry.
92
113
  attr_accessor :rack_env_whitelist
93
114
 
@@ -109,6 +130,12 @@ module Sentry
109
130
  # will not be sent to Sentry.
110
131
  attr_accessor :send_default_pii
111
132
 
133
+ # Allow to skip Sentry emails within rake tasks
134
+ attr_accessor :skip_rake_integration
135
+
136
+ # IP ranges for trusted proxies that will be skipped when calculating IP address.
137
+ attr_accessor :trusted_proxies
138
+
112
139
  attr_accessor :server_name
113
140
 
114
141
  # Return a Transport::Configuration object for transport-related configurations.
@@ -151,25 +178,32 @@ module Sentry
151
178
  LOG_PREFIX = "** [Sentry] ".freeze
152
179
  MODULE_SEPARATOR = "::".freeze
153
180
 
154
- AVAILABLE_BREADCRUMBS_LOGGERS = [:sentry_logger, :active_support_logger].freeze
181
+ # Post initialization callbacks are called at the end of initialization process
182
+ # allowing extending the configuration of sentry-ruby by multiple extensions
183
+ @@post_initialization_callbacks = []
155
184
 
156
185
  def initialize
186
+ self.debug = false
157
187
  self.background_worker_threads = Concurrent.processor_count
188
+ self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
158
189
  self.breadcrumbs_logger = []
159
190
  self.context_lines = 3
160
191
  self.environment = environment_from_env
161
192
  self.enabled_environments = []
162
193
  self.exclude_loggers = []
163
194
  self.excluded_exceptions = IGNORE_DEFAULT.dup
164
- self.inspect_exception_causes_for_exclusion = false
195
+ self.inspect_exception_causes_for_exclusion = true
165
196
  self.linecache = ::Sentry::LineCache.new
166
197
  self.logger = ::Sentry::Logger.new(STDOUT)
167
198
  self.project_root = Dir.pwd
199
+ self.propagate_traces = true
168
200
 
169
201
  self.release = detect_release
170
202
  self.sample_rate = 1.0
171
203
  self.send_modules = true
172
204
  self.send_default_pii = false
205
+ self.skip_rake_integration = false
206
+ self.trusted_proxies = []
173
207
  self.dsn = ENV['SENTRY_DSN']
174
208
  self.server_name = server_name_from_env
175
209
 
@@ -178,7 +212,8 @@ module Sentry
178
212
 
179
213
  @transport = Transport::Configuration.new
180
214
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
181
- post_initialization_callback
215
+
216
+ run_post_initialization_callbacks
182
217
  end
183
218
 
184
219
  def dsn=(value)
@@ -203,10 +238,6 @@ module Sentry
203
238
  if logger.is_a?(Array)
204
239
  logger
205
240
  else
206
- unless AVAILABLE_BREADCRUMBS_LOGGERS.include?(logger)
207
- raise Sentry::Error, "Unsupported breadcrumbs logger. Supported loggers: #{AVAILABLE_BREADCRUMBS_LOGGERS}"
208
- end
209
-
210
241
  Array(logger)
211
242
  end
212
243
 
@@ -223,6 +254,14 @@ module Sentry
223
254
  @before_send = value
224
255
  end
225
256
 
257
+ def before_breadcrumb=(value)
258
+ unless value.nil? || value.respond_to?(:call)
259
+ raise ArgumentError, "before_breadcrumb must be callable (or nil to disable)"
260
+ end
261
+
262
+ @before_breadcrumb = value
263
+ end
264
+
226
265
  def environment=(environment)
227
266
  @environment = environment.to_s
228
267
  end
@@ -247,10 +286,10 @@ module Sentry
247
286
  def exception_class_allowed?(exc)
248
287
  if exc.is_a?(Sentry::Error)
249
288
  # Try to prevent error reporting loops
250
- logger.debug(LOGGER_PROGNAME) { "Refusing to capture Sentry error: #{exc.inspect}" }
289
+ log_debug("Refusing to capture Sentry error: #{exc.inspect}")
251
290
  false
252
291
  elsif excluded_exception?(exc)
253
- logger.debug(LOGGER_PROGNAME) { "User excluded error: #{exc.inspect}" }
292
+ log_debug("User excluded error: #{exc.inspect}")
254
293
  false
255
294
  else
256
295
  true
@@ -262,7 +301,17 @@ module Sentry
262
301
  end
263
302
 
264
303
  def tracing_enabled?
265
- !!((@traces_sample_rate && @traces_sample_rate > 0.0) || @traces_sampler)
304
+ !!((@traces_sample_rate && @traces_sample_rate >= 0.0 && @traces_sample_rate <= 1.0) || @traces_sampler) && sending_allowed?
305
+ end
306
+
307
+ def stacktrace_builder
308
+ @stacktrace_builder ||= StacktraceBuilder.new(
309
+ project_root: @project_root.to_s,
310
+ app_dirs_pattern: @app_dirs_pattern,
311
+ linecache: @linecache,
312
+ context_lines: @context_lines,
313
+ backtrace_cleanup_callback: @backtrace_cleanup_callback
314
+ )
266
315
  end
267
316
 
268
317
  private
@@ -273,7 +322,7 @@ module Sentry
273
322
  detect_release_from_capistrano ||
274
323
  detect_release_from_heroku
275
324
  rescue => e
276
- logger.error(LOGGER_PROGNAME) { "Error detecting release: #{e.message}" }
325
+ log_error("Error detecting release", e, debug: debug)
277
326
  end
278
327
 
279
328
  def excluded_exception?(incoming_exception)
@@ -308,7 +357,7 @@ module Sentry
308
357
  def detect_release_from_heroku
309
358
  return unless running_on_heroku?
310
359
  return if ENV['CI']
311
- logger.warn(LOGGER_PROGNAME) { HEROKU_DYNO_METADATA_MESSAGE } && return unless ENV['HEROKU_SLUG_COMMIT']
360
+ log_warn(HEROKU_DYNO_METADATA_MESSAGE) && return unless ENV['HEROKU_SLUG_COMMIT']
312
361
 
313
362
  ENV['HEROKU_SLUG_COMMIT']
314
363
  end
@@ -382,7 +431,21 @@ module Sentry
382
431
  end
383
432
  end
384
433
 
385
- # allow extensions to extend the Configuration class
386
- def post_initialization_callback; end
434
+ def run_post_initialization_callbacks
435
+ self.class.post_initialization_callbacks.each do |hook|
436
+ instance_eval(&hook)
437
+ end
438
+ end
439
+
440
+ # allow extensions to add their hooks to the Configuration class
441
+ def self.add_post_initialization_callback(&block)
442
+ self.post_initialization_callbacks << block
443
+ end
444
+
445
+ protected
446
+
447
+ def self.post_initialization_callbacks
448
+ @@post_initialization_callbacks
449
+ end
387
450
  end
388
451
  end