sentry-ruby 0.2.0 → 4.1.1

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