sentry-ruby 0.1.3 → 4.1.0

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 false 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
@@ -51,10 +59,20 @@ module Sentry
51
59
  Event.new(configuration: configuration, message: message)
52
60
  end
53
61
 
54
- def send_event(event)
55
- return false unless configuration.sending_allowed?(event)
62
+ def event_from_transaction(transaction)
63
+ TransactionEvent.new(configuration: configuration).tap do |event|
64
+ event.transaction = transaction.name
65
+ event.contexts.merge!(trace: transaction.get_trace_context)
66
+ event.timestamp = transaction.timestamp
67
+ event.start_timestamp = transaction.start_timestamp
68
+
69
+ finished_spans = transaction.span_recorder.spans.select { |span| span.timestamp && span != transaction }
70
+ event.spans = finished_spans.map(&:to_hash)
71
+ end
72
+ end
56
73
 
57
- 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
58
76
  if event.nil?
59
77
  configuration.logger.info(LOGGER_PROGNAME) { "Discarded event because before_send returned nil" }
60
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,37 @@ 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
+
29
+ # a proc/lambda that takes an array of stack traces
30
+ # it'll be used to silence (reduce) backtrace of the exception
31
+ #
32
+ # for example:
33
+ #
34
+ # ```ruby
35
+ # Sentry.configuration.backtrace_cleanup_callback = lambda do |backtrace|
36
+ # Rails.backtrace_cleaner.clean(backtrace)
37
+ # end
38
+ # ```
39
+ #
40
+ attr_accessor :backtrace_cleanup_callback
41
+
42
+ # Optional Proc, called before sending an event to the server/
43
+ # E.g.: lambda { |event| event }
44
+ # E.g.: lambda { |event| nil }
45
+ # E.g.: lambda { |event|
46
+ # event[:message] = 'a'
47
+ # event
48
+ # }
49
+ attr_reader :before_send
50
+
18
51
  # An array of breadcrumbs loggers to be used. Available options are:
19
52
  # - :sentry_logger
20
53
  # - :active_support_logger
@@ -24,10 +57,13 @@ module Sentry
24
57
  attr_accessor :context_lines
25
58
 
26
59
  # RACK_ENV by default.
27
- attr_reader :current_environment
60
+ attr_reader :environment
28
61
 
29
- # Whitelist of environments that will send notifications to Sentry. Array of Strings.
30
- attr_accessor :environments
62
+ # the dsn value, whether it's set via `config.dsn=` or `ENV["SENTRY_DSN"]`
63
+ attr_reader :dsn
64
+
65
+ # Whitelist of enabled_environments that will send notifications to Sentry. Array of Strings.
66
+ attr_accessor :enabled_environments
31
67
 
32
68
  # Logger 'progname's to exclude from breadcrumbs
33
69
  attr_accessor :exclude_loggers
@@ -64,51 +100,36 @@ module Sentry
64
100
  # any events, and a value of 1.0 will send 100% of events.
65
101
  attr_accessor :sample_rate
66
102
 
67
- # a proc/lambda that takes an array of stack traces
68
- # it'll be used to silence (reduce) backtrace of the exception
69
- #
70
- # for example:
71
- #
72
- # ```ruby
73
- # Sentry.configuration.backtrace_cleanup_callback = lambda do |backtrace|
74
- # Rails.backtrace_cleaner.clean(backtrace)
75
- # end
76
- # ```
77
- #
78
- attr_accessor :backtrace_cleanup_callback
79
-
80
103
  # Include module versions in reports - boolean.
81
104
  attr_accessor :send_modules
82
105
 
106
+ # When send_default_pii's value is false (default), sensitive information like
107
+ # - user ip
108
+ # - user cookie
109
+ # - request body
110
+ # will not be sent to Sentry.
83
111
  attr_accessor :send_default_pii
84
112
 
85
113
  attr_accessor :server_name
86
114
 
87
- # Provide a configurable callback to determine event capture.
88
- # Note that the object passed into the block will be a String (messages) or
89
- # an exception.
90
- # e.g. lambda { |exc_or_msg| exc_or_msg.some_attr == false }
91
- attr_reader :should_capture
92
-
93
- # Silences ready message when true.
94
- attr_accessor :silence_ready
95
-
115
+ # Return a Transport::Configuration object for transport-related configurations.
96
116
  attr_reader :transport
97
117
 
98
- # Optional Proc, called before sending an event to the server/
99
- # E.g.: lambda { |event| event }
100
- # E.g.: lambda { |event| nil }
101
- # E.g.: lambda { |event|
102
- # event[:message] = 'a'
103
- # event
104
- # }
105
- attr_reader :before_send
118
+ # Take a float between 0.0 and 1.0 as the sample rate for tracing events (transactions).
119
+ attr_accessor :traces_sample_rate
106
120
 
107
- # Errors object - an Array that contains error messages. See #
108
- attr_reader :errors
121
+ # Take a Proc that controls the sample rate for every tracing event, e.g.
122
+ # ```
123
+ # lambda do |tracing_context|
124
+ # # tracing_context[:transaction_context] contains the information about the transaction
125
+ # # tracing_context[:parent_sampled] contains the transaction's parent's sample decision
126
+ # true # return value can be a boolean or a float between 0.0 and 1.0
127
+ # end
128
+ # ```
129
+ attr_accessor :traces_sampler
109
130
 
110
- # the dsn value, whether it's set via `config.dsn=` or `ENV["SENTRY_DSN"]`
111
- attr_reader :dsn
131
+ # these are not config options
132
+ attr_reader :errors, :gem_specs
112
133
 
113
134
  # Most of these errors generate 4XX responses. In general, Sentry clients
114
135
  # only automatically report 5xx responses.
@@ -136,10 +157,11 @@ module Sentry
136
157
 
137
158
  def initialize
138
159
  self.async = false
160
+ self.background_worker_threads = Concurrent.processor_count
139
161
  self.breadcrumbs_logger = []
140
162
  self.context_lines = 3
141
- self.current_environment = current_environment_from_env
142
- self.environments = []
163
+ self.environment = environment_from_env
164
+ self.enabled_environments = []
143
165
  self.exclude_loggers = []
144
166
  self.excluded_exceptions = IGNORE_DEFAULT.dup
145
167
  self.inspect_exception_causes_for_exclusion = false
@@ -153,11 +175,12 @@ module Sentry
153
175
  self.send_default_pii = false
154
176
  self.dsn = ENV['SENTRY_DSN']
155
177
  self.server_name = server_name_from_env
156
- self.should_capture = false
157
178
 
158
- @transport = Transport::Configuration.new
159
179
  self.before_send = false
160
180
  self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
181
+
182
+ @transport = Transport::Configuration.new
183
+ @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
161
184
  post_initialization_callback
162
185
  end
163
186
 
@@ -195,14 +218,6 @@ module Sentry
195
218
  @breadcrumbs_logger = logger
196
219
  end
197
220
 
198
- def should_capture=(value)
199
- unless value == false || value.respond_to?(:call)
200
- raise ArgumentError, "should_capture must be callable (or false to disable)"
201
- end
202
-
203
- @should_capture = value
204
- end
205
-
206
221
  def before_send=(value)
207
222
  unless value == false || value.respond_to?(:call)
208
223
  raise ArgumentError, "before_send must be callable (or false to disable)"
@@ -211,31 +226,21 @@ module Sentry
211
226
  @before_send = value
212
227
  end
213
228
 
214
- # Allows config options to be read like a hash
215
- #
216
- # @param [Symbol] option Key for a given attribute
217
- def [](option)
218
- public_send(option)
229
+ def environment=(environment)
230
+ @environment = environment.to_s
219
231
  end
220
232
 
221
- def current_environment=(environment)
222
- @current_environment = environment.to_s
223
- end
224
-
225
- def capture_allowed?(message_or_exc = nil)
233
+ def sending_allowed?
226
234
  @errors = []
227
235
 
228
236
  valid? &&
229
- capture_in_current_environment? &&
230
- capture_allowed_by_callback?(message_or_exc) &&
237
+ capture_in_environment? &&
231
238
  sample_allowed?
232
239
  end
233
- # If we cannot capture, we cannot send.
234
- alias sending_allowed? capture_allowed?
235
240
 
236
241
  def error_messages
237
- @errors = [errors[0]] + errors[1..-1].map(&:downcase) # fix case of all but first
238
- errors.join(", ")
242
+ @errors = [@errors[0]] + @errors[1..-1].map(&:downcase) # fix case of all but first
243
+ @errors.join(", ")
239
244
  end
240
245
 
241
246
  def project_root=(root_dir)
@@ -256,7 +261,11 @@ module Sentry
256
261
  end
257
262
 
258
263
  def enabled_in_current_env?
259
- environments.empty? || environments.include?(current_environment)
264
+ enabled_environments.empty? || enabled_environments.include?(environment)
265
+ end
266
+
267
+ def tracing_enabled?
268
+ !!((@traces_sample_rate && @traces_sample_rate > 0.0) || @traces_sampler)
260
269
  end
261
270
 
262
271
  private
@@ -279,13 +288,17 @@ module Sentry
279
288
  end
280
289
 
281
290
  def excluded_exception?(incoming_exception)
282
- excluded_exceptions.any? do |excluded_exception|
283
- matches_exception?(get_exception_class(excluded_exception), incoming_exception)
291
+ excluded_exception_classes.any? do |excluded_exception|
292
+ matches_exception?(excluded_exception, incoming_exception)
284
293
  end
285
294
  end
286
295
 
296
+ def excluded_exception_classes
297
+ @excluded_exception_classes ||= excluded_exceptions.map { |e| get_exception_class(e) }
298
+ end
299
+
287
300
  def get_exception_class(x)
288
- x.is_a?(Module) ? x : qualified_const_get(x)
301
+ x.is_a?(Module) ? x : safe_const_get(x)
289
302
  end
290
303
 
291
304
  def matches_exception?(excluded_exception_class, incoming_exception)
@@ -296,14 +309,9 @@ module Sentry
296
309
  end
297
310
  end
298
311
 
299
- # In Ruby <2.0 const_get can't lookup "SomeModule::SomeClass" in one go
300
- def qualified_const_get(x)
301
- x = x.to_s
302
- if !x.match(/::/)
303
- Object.const_get(x)
304
- else
305
- x.split(MODULE_SEPARATOR).reject(&:empty?).inject(Object) { |a, e| a.const_get(e) }
306
- end
312
+ def safe_const_get(x)
313
+ x = x.to_s unless x.is_a?(String)
314
+ Object.const_get(x)
307
315
  rescue NameError # There's no way to safely ask if a constant exist for an unknown string
308
316
  nil
309
317
  end
@@ -339,17 +347,10 @@ module Sentry
339
347
  ENV['SENTRY_RELEASE']
340
348
  end
341
349
 
342
- def capture_in_current_environment?
350
+ def capture_in_environment?
343
351
  return true if enabled_in_current_env?
344
352
 
345
- @errors << "Not configured to send/capture in environment '#{current_environment}'"
346
- false
347
- end
348
-
349
- def capture_allowed_by_callback?(message_or_exc)
350
- return true if !should_capture || message_or_exc.nil? || should_capture.call(message_or_exc)
351
-
352
- @errors << "should_capture returned false"
353
+ @errors << "Not configured to send/capture in environment '#{environment}'"
353
354
  false
354
355
  end
355
356
 
@@ -380,7 +381,7 @@ module Sentry
380
381
  Socket.gethostbyname(hostname).first rescue server_name
381
382
  end
382
383
 
383
- def current_environment_from_env
384
+ def environment_from_env
384
385
  ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'default'
385
386
  end
386
387
 
@@ -2,7 +2,10 @@ require "uri"
2
2
 
3
3
  module Sentry
4
4
  class DSN
5
- attr_reader :scheme, :project_id, :public_key, :secret_key, :host, :port, :path
5
+ PORT_MAP = { 'http' => 80, 'https' => 443 }.freeze
6
+ REQUIRED_ATTRIBUTES = %w(host path public_key project_id).freeze
7
+
8
+ attr_reader :scheme, :secret_key, :port, *REQUIRED_ATTRIBUTES
6
9
 
7
10
  def initialize(dsn_string)
8
11
  @raw_value = dsn_string
@@ -24,7 +27,7 @@ module Sentry
24
27
  end
25
28
 
26
29
  def valid?
27
- %w(host path public_key project_id).all? { |k| public_send(k) }
30
+ REQUIRED_ATTRIBUTES.all? { |k| public_send(k) }
28
31
  end
29
32
 
30
33
  def to_s
@@ -33,7 +36,7 @@ module Sentry
33
36
 
34
37
  def server
35
38
  server = "#{scheme}://#{host}"
36
- server += ":#{port}" unless port == { 'http' => 80, 'https' => 443 }[scheme]
39
+ server += ":#{port}" unless port == PORT_MAP[scheme]
37
40
  server += path
38
41
  server
39
42
  end
@@ -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
@@ -13,21 +14,19 @@ module Sentry
13
14
  release environment server_name modules
14
15
  message user tags contexts extra
15
16
  fingerprint breadcrumbs backtrace transaction
16
- platform sdk
17
+ platform sdk type
17
18
  )
18
19
 
19
20
  attr_accessor(*ATTRIBUTES)
20
- attr_reader :id, :configuration
21
-
22
- alias event_id id
21
+ attr_reader :configuration, :request, :exception, :stacktrace
23
22
 
24
23
  def initialize(configuration:, message: nil)
25
24
  # this needs to go first because some setters rely on configuration
26
25
  @configuration = configuration
27
26
 
28
27
  # Set some simple default values
29
- @id = SecureRandom.uuid.delete("-")
30
- @timestamp = Time.now.utc
28
+ @event_id = SecureRandom.uuid.delete("-")
29
+ @timestamp = Sentry.utc_now.iso8601
31
30
  @platform = :ruby
32
31
  @sdk = Sentry.sdk_meta
33
32
 
@@ -39,9 +38,9 @@ module Sentry
39
38
  @fingerprint = []
40
39
 
41
40
  @server_name = configuration.server_name
42
- @environment = configuration.current_environment
41
+ @environment = configuration.environment
43
42
  @release = configuration.release
44
- @modules = list_gem_specs if configuration.send_modules
43
+ @modules = configuration.gem_specs if configuration.send_modules
45
44
 
46
45
  @message = message || ""
47
46
 
@@ -51,8 +50,8 @@ module Sentry
51
50
  class << self
52
51
  def get_log_message(event_hash)
53
52
  message = event_hash[:message] || event_hash['message']
54
- message = get_message_from_exception(event_hash) if message.empty?
55
- message = '<no message value>' if message.empty?
53
+ message = get_message_from_exception(event_hash) if message.nil? || message.empty?
54
+ message = '<no message value>' if message.nil? || message.empty?
56
55
  message
57
56
  end
58
57
 
@@ -68,7 +67,7 @@ module Sentry
68
67
  end
69
68
 
70
69
  def timestamp=(time)
71
- @timestamp = time.is_a?(Time) ? time.strftime('%Y-%m-%dT%H:%M:%S') : time
70
+ @timestamp = time.is_a?(Time) ? time.to_f : time
72
71
  end
73
72
 
74
73
  def level=(new_level) # needed to meet the Sentry spec
@@ -76,7 +75,7 @@ module Sentry
76
75
  end
77
76
 
78
77
  def rack_env=(env)
79
- unless @request || env.empty?
78
+ unless request || env.empty?
80
79
  @request = Sentry::RequestInterface.new.tap do |int|
81
80
  int.from_rack(env)
82
81
  end
@@ -84,18 +83,22 @@ module Sentry
84
83
  if configuration.send_default_pii && ip = calculate_real_ip_from_rack(env.dup)
85
84
  user[:ip_address] = ip
86
85
  end
86
+ if request_id = Utils::RequestId.read_from(env)
87
+ tags[:request_id] = request_id
88
+ end
87
89
  end
88
90
  end
89
91
 
90
- def to_hash
91
- data = ATTRIBUTES.each_with_object({}) do |att, memo|
92
- memo[att] = public_send(att) if public_send(att)
93
- end
92
+ def type
93
+ "event"
94
+ end
94
95
 
96
+ def to_hash
97
+ data = serialize_attributes
95
98
  data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
96
- data[:stacktrace] = @stacktrace.to_hash if @stacktrace
97
- data[:request] = @request.to_hash if @request
98
- 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
99
102
 
100
103
  data
101
104
  end
@@ -141,9 +144,9 @@ module Sentry
141
144
  frame.in_app = line.in_app
142
145
  frame.module = line.module_name if line.module_name
143
146
 
144
- if configuration[:context_lines] && frame.abs_path
147
+ if configuration.context_lines && frame.abs_path
145
148
  frame.pre_context, frame.context_line, frame.post_context = \
146
- configuration.linecache.get_file_context(frame.abs_path, frame.lineno, configuration[:context_lines])
149
+ configuration.linecache.get_file_context(frame.abs_path, frame.lineno, configuration.context_lines)
147
150
  end
148
151
 
149
152
  memo << frame if frame.filename
@@ -152,6 +155,14 @@ module Sentry
152
155
 
153
156
  private
154
157
 
158
+ def serialize_attributes
159
+ self.class::ATTRIBUTES.each_with_object({}) do |att, memo|
160
+ if value = public_send(att)
161
+ memo[att] = value
162
+ end
163
+ end
164
+ end
165
+
155
166
  # When behind a proxy (or if the user is using a proxy), we can't use
156
167
  # REMOTE_ADDR to determine the Event IP, and must use other headers instead.
157
168
  def calculate_real_ip_from_rack(env)
@@ -162,10 +173,5 @@ module Sentry
162
173
  :forwarded_for => env["HTTP_X_FORWARDED_FOR"]
163
174
  ).calculate_ip
164
175
  end
165
-
166
- def list_gem_specs
167
- # Older versions of Rubygems don't support iterating over all specs
168
- Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
169
- end
170
176
  end
171
177
  end