sentry-ruby 0.2.0 → 4.1.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.
@@ -10,11 +10,8 @@ module Sentry
10
10
  @buffer = Array.new(size)
11
11
  end
12
12
 
13
- def record(crumb = nil)
14
- if block_given?
15
- crumb = Breadcrumb.new if crumb.nil?
16
- yield(crumb)
17
- end
13
+ def record(crumb)
14
+ yield(crumb) if block_given?
18
15
  @buffer.slice!(0)
19
16
  @buffer << crumb
20
17
  end
@@ -20,8 +20,10 @@ module Sentry
20
20
  end
21
21
  end
22
22
 
23
- def capture_event(event, scope)
24
- scope.apply_to_event(event)
23
+ def capture_event(event, scope, hint = {})
24
+ return unless configuration.sending_allowed?
25
+
26
+ scope.apply_to_event(event, hint)
25
27
 
26
28
  if configuration.async?
27
29
  begin
@@ -30,10 +32,16 @@ module Sentry
30
32
  configuration.async.call(event.to_json_compatible)
31
33
  rescue => e
32
34
  configuration.logger.error(LOGGER_PROGNAME) { "async event sending failed: #{e.message}" }
33
- send_event(event)
35
+ send_event(event, hint)
34
36
  end
35
37
  else
36
- send_event(event)
38
+ if hint.fetch(:background, true)
39
+ Sentry.background_worker.perform do
40
+ send_event(event, hint)
41
+ end
42
+ else
43
+ send_event(event, hint)
44
+ end
37
45
  end
38
46
 
39
47
  event
@@ -63,10 +71,8 @@ module Sentry
63
71
  end
64
72
  end
65
73
 
66
- def send_event(event)
67
- return false unless configuration.sending_allowed?(event)
68
-
69
- event = configuration.before_send.call(event) if configuration.before_send
74
+ def send_event(event, hint = nil)
75
+ event = configuration.before_send.call(event, hint) if configuration.before_send
70
76
  if event.nil?
71
77
  configuration.logger.info(LOGGER_PROGNAME) { "Discarded event because before_send returned nil" }
72
78
  return
@@ -1,3 +1,5 @@
1
+ require "concurrent/utility/processor_counter"
2
+
1
3
  require "sentry/utils/exception_cause_chain"
2
4
  require "sentry/dsn"
3
5
  require "sentry/transport/configuration"
@@ -15,6 +17,15 @@ module Sentry
15
17
  attr_reader :async
16
18
  alias async? async
17
19
 
20
+ # to send events in a non-blocking way, sentry-ruby has its own background worker
21
+ # by default, the worker holds a thread pool that has [the number of processors] threads
22
+ # but you can configure it with this configuration option
23
+ # E.g.: config.background_worker_threads = 5
24
+ #
25
+ # if you want to send events synchronously, set the value to 0
26
+ # E.g.: config.background_worker_threads = 0
27
+ attr_accessor :background_worker_threads
28
+
18
29
  # a proc/lambda that takes an array of stack traces
19
30
  # it'll be used to silence (reduce) backtrace of the exception
20
31
  #
@@ -46,13 +57,13 @@ module Sentry
46
57
  attr_accessor :context_lines
47
58
 
48
59
  # RACK_ENV by default.
49
- attr_reader :current_environment
60
+ attr_reader :environment
50
61
 
51
62
  # the dsn value, whether it's set via `config.dsn=` or `ENV["SENTRY_DSN"]`
52
63
  attr_reader :dsn
53
64
 
54
- # Whitelist of environments that will send notifications to Sentry. Array of Strings.
55
- attr_accessor :environments
65
+ # Whitelist of enabled_environments that will send notifications to Sentry. Array of Strings.
66
+ attr_accessor :enabled_environments
56
67
 
57
68
  # Logger 'progname's to exclude from breadcrumbs
58
69
  attr_accessor :exclude_loggers
@@ -101,12 +112,6 @@ module Sentry
101
112
 
102
113
  attr_accessor :server_name
103
114
 
104
- # Provide a configurable callback to determine event capture.
105
- # Note that the object passed into the block will be a String (messages) or
106
- # an exception.
107
- # e.g. lambda { |exc_or_msg| exc_or_msg.some_attr == false }
108
- attr_reader :should_capture
109
-
110
115
  # Return a Transport::Configuration object for transport-related configurations.
111
116
  attr_reader :transport
112
117
 
@@ -152,10 +157,11 @@ module Sentry
152
157
 
153
158
  def initialize
154
159
  self.async = false
160
+ self.background_worker_threads = Concurrent.processor_count
155
161
  self.breadcrumbs_logger = []
156
162
  self.context_lines = 3
157
- self.current_environment = current_environment_from_env
158
- self.environments = []
163
+ self.environment = environment_from_env
164
+ self.enabled_environments = []
159
165
  self.exclude_loggers = []
160
166
  self.excluded_exceptions = IGNORE_DEFAULT.dup
161
167
  self.inspect_exception_causes_for_exclusion = false
@@ -169,7 +175,6 @@ module Sentry
169
175
  self.send_default_pii = false
170
176
  self.dsn = ENV['SENTRY_DSN']
171
177
  self.server_name = server_name_from_env
172
- self.should_capture = false
173
178
 
174
179
  self.before_send = false
175
180
  self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
@@ -213,14 +218,6 @@ module Sentry
213
218
  @breadcrumbs_logger = logger
214
219
  end
215
220
 
216
- def should_capture=(value)
217
- unless value == false || value.respond_to?(:call)
218
- raise ArgumentError, "should_capture must be callable (or false to disable)"
219
- end
220
-
221
- @should_capture = value
222
- end
223
-
224
221
  def before_send=(value)
225
222
  unless value == false || value.respond_to?(:call)
226
223
  raise ArgumentError, "before_send must be callable (or false to disable)"
@@ -229,20 +226,17 @@ module Sentry
229
226
  @before_send = value
230
227
  end
231
228
 
232
- def current_environment=(environment)
233
- @current_environment = environment.to_s
229
+ def environment=(environment)
230
+ @environment = environment.to_s
234
231
  end
235
232
 
236
- def capture_allowed?(message_or_exc = nil)
233
+ def sending_allowed?
237
234
  @errors = []
238
235
 
239
236
  valid? &&
240
- capture_in_current_environment? &&
241
- capture_allowed_by_callback?(message_or_exc) &&
237
+ capture_in_environment? &&
242
238
  sample_allowed?
243
239
  end
244
- # If we cannot capture, we cannot send.
245
- alias sending_allowed? capture_allowed?
246
240
 
247
241
  def error_messages
248
242
  @errors = [@errors[0]] + @errors[1..-1].map(&:downcase) # fix case of all but first
@@ -267,7 +261,7 @@ module Sentry
267
261
  end
268
262
 
269
263
  def enabled_in_current_env?
270
- environments.empty? || environments.include?(current_environment)
264
+ enabled_environments.empty? || enabled_environments.include?(environment)
271
265
  end
272
266
 
273
267
  def tracing_enabled?
@@ -353,17 +347,10 @@ module Sentry
353
347
  ENV['SENTRY_RELEASE']
354
348
  end
355
349
 
356
- def capture_in_current_environment?
350
+ def capture_in_environment?
357
351
  return true if enabled_in_current_env?
358
352
 
359
- @errors << "Not configured to send/capture in environment '#{current_environment}'"
360
- false
361
- end
362
-
363
- def capture_allowed_by_callback?(message_or_exc)
364
- return true if !should_capture || message_or_exc.nil? || should_capture.call(message_or_exc)
365
-
366
- @errors << "should_capture returned false"
353
+ @errors << "Not configured to send/capture in environment '#{environment}'"
367
354
  false
368
355
  end
369
356
 
@@ -394,7 +381,7 @@ module Sentry
394
381
  Socket.gethostbyname(hostname).first rescue server_name
395
382
  end
396
383
 
397
- def current_environment_from_env
384
+ def environment_from_env
398
385
  ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'default'
399
386
  end
400
387
 
@@ -5,6 +5,7 @@ require 'securerandom'
5
5
  require 'sentry/interface'
6
6
  require 'sentry/backtrace'
7
7
  require 'sentry/utils/real_ip'
8
+ require 'sentry/utils/request_id'
8
9
 
9
10
  module Sentry
10
11
  class Event
@@ -17,7 +18,7 @@ module Sentry
17
18
  )
18
19
 
19
20
  attr_accessor(*ATTRIBUTES)
20
- attr_reader :configuration
21
+ attr_reader :configuration, :request, :exception, :stacktrace
21
22
 
22
23
  def initialize(configuration:, message: nil)
23
24
  # this needs to go first because some setters rely on configuration
@@ -37,7 +38,7 @@ module Sentry
37
38
  @fingerprint = []
38
39
 
39
40
  @server_name = configuration.server_name
40
- @environment = configuration.current_environment
41
+ @environment = configuration.environment
41
42
  @release = configuration.release
42
43
  @modules = configuration.gem_specs if configuration.send_modules
43
44
 
@@ -74,7 +75,7 @@ module Sentry
74
75
  end
75
76
 
76
77
  def rack_env=(env)
77
- unless @request || env.empty?
78
+ unless request || env.empty?
78
79
  @request = Sentry::RequestInterface.new.tap do |int|
79
80
  int.from_rack(env)
80
81
  end
@@ -82,6 +83,9 @@ module Sentry
82
83
  if configuration.send_default_pii && ip = calculate_real_ip_from_rack(env.dup)
83
84
  user[:ip_address] = ip
84
85
  end
86
+ if request_id = Utils::RequestId.read_from(env)
87
+ tags[:request_id] = request_id
88
+ end
85
89
  end
86
90
  end
87
91
 
@@ -92,9 +96,9 @@ module Sentry
92
96
  def to_hash
93
97
  data = serialize_attributes
94
98
  data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
95
- data[:stacktrace] = @stacktrace.to_hash if @stacktrace
96
- data[:request] = @request.to_hash if @request
97
- data[:exception] = @exception.to_hash if @exception
99
+ data[:stacktrace] = stacktrace.to_hash if stacktrace
100
+ data[:request] = request.to_hash if request
101
+ data[:exception] = exception.to_hash if exception
98
102
 
99
103
  data
100
104
  end
@@ -80,12 +80,16 @@ module Sentry
80
80
 
81
81
  return unless event
82
82
 
83
+ options[:hint] ||= {}
84
+ options[:hint] = options[:hint].merge(exception: exception)
83
85
  capture_event(event, **options, &block)
84
86
  end
85
87
 
86
88
  def capture_message(message, **options, &block)
87
89
  return unless current_client
88
90
 
91
+ options[:hint] ||= {}
92
+ options[:hint] = options[:hint].merge(message: message)
89
93
  event = current_client.event_from_message(message)
90
94
  capture_event(event, **options, &block)
91
95
  end
@@ -93,6 +97,7 @@ module Sentry
93
97
  def capture_event(event, **options, &block)
94
98
  return unless current_client
95
99
 
100
+ hint = options.delete(:hint) || {}
96
101
  scope = current_scope.dup
97
102
 
98
103
  if block
@@ -103,9 +108,9 @@ module Sentry
103
108
  scope.update_from_options(**options)
104
109
  end
105
110
 
106
- event = current_client.capture_event(event, scope)
111
+ event = current_client.capture_event(event, scope, hint)
107
112
 
108
- @last_event_id = event.event_id
113
+ @last_event_id = event&.event_id
109
114
  event
110
115
  end
111
116
 
@@ -1,5 +1,3 @@
1
- require 'rack'
2
-
3
1
  module Sentry
4
2
  class RequestInterface < Interface
5
3
  REQUEST_ID_HEADERS = %w(action_dispatch.request_id HTTP_X_REQUEST_ID).freeze
@@ -18,36 +16,8 @@ module Sentry
18
16
  self.cookies = nil
19
17
  end
20
18
 
21
- def from_rack(env_hash)
22
- req = ::Rack::Request.new(env_hash)
23
-
24
- if Sentry.configuration.send_default_pii
25
- self.data = read_data_from(req)
26
- self.cookies = req.cookies
27
- else
28
- # need to completely wipe out ip addresses
29
- IP_HEADERS.each { |h| env_hash.delete(h) }
30
- end
31
-
32
- self.url = req.scheme && req.url.split('?').first
33
- self.method = req.request_method
34
- self.query_string = req.query_string
35
-
36
- self.headers = format_headers_for_sentry(env_hash)
37
- self.env = format_env_for_sentry(env_hash)
38
- end
39
-
40
19
  private
41
20
 
42
- # Request ID based on ActionDispatch::RequestId
43
- def read_request_id_from(env_hash)
44
- REQUEST_ID_HEADERS.each do |key|
45
- request_id = env_hash[key]
46
- return request_id if request_id
47
- end
48
- nil
49
- end
50
-
51
21
  # See Sentry server default limits at
52
22
  # https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py
53
23
  def read_data_from(request)
@@ -67,7 +37,7 @@ module Sentry
67
37
  begin
68
38
  key = key.to_s # rack env can contain symbols
69
39
  value = value.to_s
70
- next memo['X-Request-Id'] ||= read_request_id_from(env_hash) if REQUEST_ID_HEADERS.include?(key)
40
+ next memo['X-Request-Id'] ||= Utils::RequestId.read_from(env_hash) if Utils::RequestId::REQUEST_ID_HEADERS.include?(key)
71
41
  next unless key.upcase == key # Non-upper case stuff isn't either
72
42
 
73
43
  # Rack adds in an incorrect HTTP_VERSION key, which causes downstream
@@ -1,5 +1,5 @@
1
- require 'time'
2
1
  require 'rack'
3
2
 
4
- require 'sentry/rack/capture_exception'
5
- require 'sentry/rack/tracing'
3
+ require 'sentry/rack/capture_exceptions'
4
+ require 'sentry/rack/interface'
5
+ require 'sentry/rack/deprecations'
@@ -1,45 +1,54 @@
1
1
  module Sentry
2
2
  module Rack
3
- class CaptureException
3
+ class CaptureExceptions
4
4
  def initialize(app)
5
5
  @app = app
6
6
  end
7
7
 
8
8
  def call(env)
9
- # this call clones the main (global) hub
10
- # and assigns it to the current thread's Sentry#get_current_hub
11
- # it's essential for multi-thread servers (e.g. puma)
12
- Sentry.clone_hub_to_current_thread unless Sentry.get_current_hub
13
- # this call creates an isolated scope for every request
14
- # it's essential for multi-process servers (e.g. unicorn)
9
+ return @app.call(env) unless Sentry.initialized?
10
+
11
+ # make sure the current thread has a clean hub
12
+ Sentry.clone_hub_to_current_thread
13
+
15
14
  Sentry.with_scope do |scope|
16
- # there could be some breadcrumbs already stored in the top-level scope
17
- # and for request information, we don't need those breadcrumbs
18
15
  scope.clear_breadcrumbs
19
- env['sentry.client'] = Sentry.get_current_client
20
-
21
16
  scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
22
17
  scope.set_rack_env(env)
23
18
 
19
+ span = Sentry.start_transaction(name: scope.transaction_name, op: "rack.request")
20
+ scope.set_span(span)
21
+
24
22
  begin
25
23
  response = @app.call(env)
26
24
  rescue Sentry::Error
25
+ finish_span(span, 500)
27
26
  raise # Don't capture Sentry errors
28
27
  rescue Exception => e
29
28
  Sentry.capture_exception(e)
29
+ finish_span(span, 500)
30
30
  raise
31
31
  end
32
32
 
33
33
  exception = collect_exception(env)
34
34
  Sentry.capture_exception(exception) if exception
35
35
 
36
+ finish_span(span, response[0])
37
+
36
38
  response
37
39
  end
38
40
  end
39
41
 
42
+ private
43
+
40
44
  def collect_exception(env)
41
45
  env['rack.exception'] || env['sinatra.error']
42
46
  end
47
+
48
+ def finish_span(span, status_code)
49
+ span.set_http_status(status_code)
50
+ span.finish
51
+ end
43
52
  end
44
53
  end
45
54
  end
@@ -0,0 +1,19 @@
1
+ module Sentry
2
+ module Rack
3
+ class DeprecatedMiddleware
4
+ def initialize(_)
5
+ raise Sentry::Error.new <<~MSG
6
+
7
+ You're seeing this message because #{self.class} has been replaced by Sentry::Rack::CaptureExceptions.
8
+ Removing this middleware from your app and upgrading sentry-rails to 4.1.0+ should solve the issue.
9
+ MSG
10
+ end
11
+ end
12
+
13
+ class Tracing < DeprecatedMiddleware
14
+ end
15
+
16
+ class CaptureException < DeprecatedMiddleware
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ module Sentry
2
+ class RequestInterface
3
+ def from_rack(env_hash)
4
+ req = ::Rack::Request.new(env_hash)
5
+
6
+ if Sentry.configuration.send_default_pii
7
+ self.data = read_data_from(req)
8
+ self.cookies = req.cookies
9
+ else
10
+ # need to completely wipe out ip addresses
11
+ IP_HEADERS.each { |h| env_hash.delete(h) }
12
+ end
13
+
14
+ self.url = req.scheme && req.url.split('?').first
15
+ self.method = req.request_method
16
+ self.query_string = req.query_string
17
+
18
+ self.headers = format_headers_for_sentry(env_hash)
19
+ self.env = format_env_for_sentry(env_hash)
20
+ end
21
+ end
22
+ end