sentry-ruby-core 4.1.6 → 4.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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