sentry-ruby 5.10.0 → 5.26.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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -1
  3. data/Gemfile +12 -13
  4. data/README.md +26 -11
  5. data/Rakefile +9 -11
  6. data/bin/console +2 -0
  7. data/lib/sentry/attachment.rb +40 -0
  8. data/lib/sentry/background_worker.rb +11 -5
  9. data/lib/sentry/backpressure_monitor.rb +45 -0
  10. data/lib/sentry/backtrace.rb +12 -9
  11. data/lib/sentry/baggage.rb +7 -7
  12. data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
  13. data/lib/sentry/breadcrumb.rb +13 -6
  14. data/lib/sentry/check_in_event.rb +61 -0
  15. data/lib/sentry/client.rb +214 -25
  16. data/lib/sentry/configuration.rb +221 -38
  17. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  18. data/lib/sentry/cron/configuration.rb +23 -0
  19. data/lib/sentry/cron/monitor_check_ins.rb +77 -0
  20. data/lib/sentry/cron/monitor_config.rb +53 -0
  21. data/lib/sentry/cron/monitor_schedule.rb +42 -0
  22. data/lib/sentry/dsn.rb +4 -4
  23. data/lib/sentry/envelope/item.rb +88 -0
  24. data/lib/sentry/envelope.rb +2 -68
  25. data/lib/sentry/error_event.rb +2 -2
  26. data/lib/sentry/event.rb +28 -47
  27. data/lib/sentry/excon/middleware.rb +77 -0
  28. data/lib/sentry/excon.rb +10 -0
  29. data/lib/sentry/faraday.rb +77 -0
  30. data/lib/sentry/graphql.rb +9 -0
  31. data/lib/sentry/hub.rb +138 -6
  32. data/lib/sentry/integrable.rb +10 -0
  33. data/lib/sentry/interface.rb +1 -0
  34. data/lib/sentry/interfaces/exception.rb +5 -3
  35. data/lib/sentry/interfaces/mechanism.rb +20 -0
  36. data/lib/sentry/interfaces/request.rb +8 -8
  37. data/lib/sentry/interfaces/single_exception.rb +13 -9
  38. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  39. data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
  40. data/lib/sentry/linecache.rb +3 -3
  41. data/lib/sentry/log_event.rb +206 -0
  42. data/lib/sentry/log_event_buffer.rb +75 -0
  43. data/lib/sentry/logger.rb +1 -1
  44. data/lib/sentry/metrics/aggregator.rb +248 -0
  45. data/lib/sentry/metrics/configuration.rb +47 -0
  46. data/lib/sentry/metrics/counter_metric.rb +25 -0
  47. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  48. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  49. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  50. data/lib/sentry/metrics/metric.rb +19 -0
  51. data/lib/sentry/metrics/set_metric.rb +28 -0
  52. data/lib/sentry/metrics/timing.rb +51 -0
  53. data/lib/sentry/metrics.rb +56 -0
  54. data/lib/sentry/net/http.rb +27 -44
  55. data/lib/sentry/profiler/helpers.rb +46 -0
  56. data/lib/sentry/profiler.rb +41 -60
  57. data/lib/sentry/propagation_context.rb +135 -0
  58. data/lib/sentry/puma.rb +12 -5
  59. data/lib/sentry/rack/capture_exceptions.rb +17 -8
  60. data/lib/sentry/rack.rb +2 -2
  61. data/lib/sentry/rake.rb +4 -15
  62. data/lib/sentry/redis.rb +10 -4
  63. data/lib/sentry/release_detector.rb +5 -5
  64. data/lib/sentry/rspec.rb +91 -0
  65. data/lib/sentry/scope.rb +75 -39
  66. data/lib/sentry/session.rb +2 -2
  67. data/lib/sentry/session_flusher.rb +15 -43
  68. data/lib/sentry/span.rb +92 -8
  69. data/lib/sentry/std_lib_logger.rb +50 -0
  70. data/lib/sentry/structured_logger.rb +138 -0
  71. data/lib/sentry/test_helper.rb +42 -13
  72. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  73. data/lib/sentry/transaction.rb +44 -43
  74. data/lib/sentry/transaction_event.rb +10 -6
  75. data/lib/sentry/transport/configuration.rb +73 -1
  76. data/lib/sentry/transport/http_transport.rb +71 -41
  77. data/lib/sentry/transport/spotlight_transport.rb +50 -0
  78. data/lib/sentry/transport.rb +53 -49
  79. data/lib/sentry/utils/argument_checking_helper.rb +12 -0
  80. data/lib/sentry/utils/env_helper.rb +21 -0
  81. data/lib/sentry/utils/http_tracing.rb +74 -0
  82. data/lib/sentry/utils/logging_helper.rb +10 -7
  83. data/lib/sentry/utils/real_ip.rb +2 -2
  84. data/lib/sentry/utils/request_id.rb +1 -1
  85. data/lib/sentry/utils/uuid.rb +13 -0
  86. data/lib/sentry/vernier/output.rb +89 -0
  87. data/lib/sentry/vernier/profiler.rb +132 -0
  88. data/lib/sentry/version.rb +1 -1
  89. data/lib/sentry-ruby.rb +206 -35
  90. data/sentry-ruby-core.gemspec +3 -1
  91. data/sentry-ruby.gemspec +15 -6
  92. metadata +61 -11
@@ -3,12 +3,17 @@
3
3
  require "concurrent/utility/processor_counter"
4
4
 
5
5
  require "sentry/utils/exception_cause_chain"
6
- require 'sentry/utils/custom_inspection'
6
+ require "sentry/utils/custom_inspection"
7
+ require "sentry/utils/env_helper"
7
8
  require "sentry/dsn"
8
9
  require "sentry/release_detector"
9
10
  require "sentry/transport/configuration"
11
+ require "sentry/cron/configuration"
12
+ require "sentry/metrics/configuration"
10
13
  require "sentry/linecache"
11
14
  require "sentry/interfaces/stacktrace_builder"
15
+ require "sentry/logger"
16
+ require "sentry/log_event_buffer"
12
17
 
13
18
  module Sentry
14
19
  class Configuration
@@ -20,6 +25,8 @@ module Sentry
20
25
  # have an `engines` dir at the root of your project, you may want
21
26
  # to set this to something like /(app|config|engines|lib)/
22
27
  #
28
+ # The default is value is /(bin|exe|app|config|lib|test|spec)/
29
+ #
23
30
  # @return [Regexp, nil]
24
31
  attr_accessor :app_dirs_pattern
25
32
 
@@ -40,6 +47,13 @@ module Sentry
40
47
  # @return [Integer]
41
48
  attr_accessor :background_worker_threads
42
49
 
50
+ # The maximum queue size for the background worker.
51
+ # Jobs will be rejected above this limit.
52
+ #
53
+ # Default is {BackgroundWorker::DEFAULT_MAX_QUEUE}.
54
+ # @return [Integer]
55
+ attr_accessor :background_worker_max_queue
56
+
43
57
  # a proc/lambda that takes an array of stack traces
44
58
  # it'll be used to silence (reduce) backtrace of the exception
45
59
  #
@@ -87,6 +101,15 @@ module Sentry
87
101
  # @return [Proc]
88
102
  attr_reader :before_send_transaction
89
103
 
104
+ # Optional Proc, called before sending an event to the server
105
+ # @example
106
+ # config.before_send_log = lambda do |log|
107
+ # log.attributes["sentry"] = true
108
+ # log
109
+ # end
110
+ # @return [Proc]
111
+ attr_accessor :before_send_log
112
+
90
113
  # An array of breadcrumbs loggers to be used. Available options are:
91
114
  # - :sentry_logger
92
115
  # - :http_logger
@@ -142,6 +165,14 @@ module Sentry
142
165
  # @return [Boolean]
143
166
  attr_accessor :include_local_variables
144
167
 
168
+ # Whether to capture events and traces into Spotlight. Default is false.
169
+ # If you set this to true, Sentry will send events and traces to the local
170
+ # Sidecar proxy at http://localhost:8969/stream.
171
+ # If you want to use a different Sidecar proxy address, set this to String
172
+ # with the proxy URL.
173
+ # @return [Boolean, String]
174
+ attr_accessor :spotlight
175
+
145
176
  # @deprecated Use {#include_local_variables} instead.
146
177
  alias_method :capture_exception_frame_locals, :include_local_variables
147
178
 
@@ -163,13 +194,30 @@ module Sentry
163
194
  # Logger used by Sentry. In Rails, this is the Rails logger, otherwise
164
195
  # Sentry provides its own Sentry::Logger.
165
196
  # @return [Logger]
166
- attr_accessor :logger
197
+ attr_accessor :sdk_logger
198
+
199
+ # @deprecated Use {#sdk_logger=} instead.
200
+ def logger=(logger)
201
+ warn "[sentry] `config.logger=` is deprecated. Please use `config.sdk_logger=` instead."
202
+ self.sdk_logger = logger
203
+ end
204
+
205
+ # @deprecated Use {#sdk_logger} instead.
206
+ def logger
207
+ warn "[sentry] `config.logger` is deprecated. Please use `config.sdk_logger` instead."
208
+ self.sdk_logger
209
+ end
167
210
 
168
211
  # Project directory root for in_app detection. Could be Rails root, etc.
169
212
  # Set automatically for Rails.
170
213
  # @return [String]
171
214
  attr_accessor :project_root
172
215
 
216
+ # Whether to strip the load path while constructing the backtrace frame filename.
217
+ # Defaults to true.
218
+ # @return [Boolean]
219
+ attr_accessor :strip_backtrace_load_path
220
+
173
221
  # Insert sentry-trace to outgoing requests' headers
174
222
  # @return [Boolean]
175
223
  attr_accessor :propagate_traces
@@ -211,10 +259,18 @@ module Sentry
211
259
  # @return [String]
212
260
  attr_accessor :server_name
213
261
 
214
- # Return a Transport::Configuration object for transport-related configurations.
215
- # @return [Transport]
262
+ # Transport related configuration.
263
+ # @return [Transport::Configuration]
216
264
  attr_reader :transport
217
265
 
266
+ # Cron related configuration.
267
+ # @return [Cron::Configuration]
268
+ attr_reader :cron
269
+
270
+ # Metrics related configuration.
271
+ # @return [Metrics::Configuration]
272
+ attr_reader :metrics
273
+
218
274
  # Take a float between 0.0 and 1.0 as the sample rate for tracing events (transactions).
219
275
  # @return [Float, nil]
220
276
  attr_reader :traces_sample_rate
@@ -229,8 +285,13 @@ module Sentry
229
285
  # @return [Proc]
230
286
  attr_accessor :traces_sampler
231
287
 
288
+ # Enable Structured Logging
289
+ # @return [Boolean]
290
+ attr_accessor :enable_logs
291
+
232
292
  # Easier way to use performance tracing
233
293
  # If set to true, will set traces_sample_rate to 1.0
294
+ # @deprecated It will be removed in the next major release.
234
295
  # @return [Boolean, nil]
235
296
  attr_reader :enable_tracing
236
297
 
@@ -243,16 +304,40 @@ module Sentry
243
304
  # @return [Boolean]
244
305
  attr_accessor :auto_session_tracking
245
306
 
307
+ # Whether to downsample transactions automatically because of backpressure.
308
+ # Starts a new monitor thread to check health of the SDK every 10 seconds.
309
+ # Default is false
310
+ # @return [Boolean]
311
+ attr_accessor :enable_backpressure_handling
312
+
313
+ # Allowlist of outgoing request targets to which sentry-trace and baggage headers are attached.
314
+ # Default is all (/.*/)
315
+ # @return [Array<String, Regexp>]
316
+ attr_accessor :trace_propagation_targets
317
+
246
318
  # The instrumenter to use, :sentry or :otel
247
319
  # @return [Symbol]
248
320
  attr_reader :instrumenter
249
321
 
322
+ # The profiler class
323
+ # @return [Class]
324
+ attr_reader :profiler_class
325
+
250
326
  # Take a float between 0.0 and 1.0 as the sample rate for capturing profiles.
251
327
  # Note that this rate is relative to traces_sample_rate / traces_sampler,
252
328
  # i.e. the profile is sampled by this rate after the transaction is sampled.
253
329
  # @return [Float, nil]
254
330
  attr_reader :profiles_sample_rate
255
331
 
332
+ # Array of patches to apply.
333
+ # Default is {DEFAULT_PATCHES}
334
+ # @return [Array<Symbol>]
335
+ attr_accessor :enabled_patches
336
+
337
+ # Maximum number of log events to buffer before sending
338
+ # @return [Integer]
339
+ attr_accessor :max_log_events
340
+
256
341
  # these are not config options
257
342
  # @!visibility private
258
343
  attr_reader :errors, :gem_specs
@@ -261,35 +346,41 @@ module Sentry
261
346
  # But they are mostly considered as noise and should be ignored by default
262
347
  # Please see https://github.com/getsentry/sentry-ruby/pull/2026 for more information
263
348
  PUMA_IGNORE_DEFAULT = [
264
- 'Puma::MiniSSL::SSLError',
265
- 'Puma::HttpParserError',
266
- 'Puma::HttpParserError501'
349
+ "Puma::MiniSSL::SSLError",
350
+ "Puma::HttpParserError",
351
+ "Puma::HttpParserError501"
267
352
  ].freeze
268
353
 
269
354
  # Most of these errors generate 4XX responses. In general, Sentry clients
270
355
  # only automatically report 5xx responses.
271
356
  IGNORE_DEFAULT = [
272
- 'Mongoid::Errors::DocumentNotFound',
273
- 'Rack::QueryParser::InvalidParameterError',
274
- 'Rack::QueryParser::ParameterTypeError',
275
- 'Sinatra::NotFound'
357
+ "Mongoid::Errors::DocumentNotFound",
358
+ "Rack::QueryParser::InvalidParameterError",
359
+ "Rack::QueryParser::ParameterTypeError",
360
+ "Sinatra::NotFound"
276
361
  ].freeze
277
362
 
278
- RACK_ENV_WHITELIST_DEFAULT = %w(
363
+ RACK_ENV_WHITELIST_DEFAULT = %w[
279
364
  REMOTE_ADDR
280
365
  SERVER_NAME
281
366
  SERVER_PORT
282
- ).freeze
367
+ ].freeze
283
368
 
284
369
  HEROKU_DYNO_METADATA_MESSAGE = "You are running on Heroku but haven't enabled Dyno Metadata. For Sentry's "\
285
- "release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`".freeze
370
+ "release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`"
286
371
 
287
- LOG_PREFIX = "** [Sentry] ".freeze
288
- MODULE_SEPARATOR = "::".freeze
372
+ LOG_PREFIX = "** [Sentry] "
373
+ MODULE_SEPARATOR = "::"
289
374
  SKIP_INSPECTION_ATTRIBUTES = [:@linecache, :@stacktrace_builder]
290
375
 
291
376
  INSTRUMENTERS = [:sentry, :otel]
292
377
 
378
+ PROPAGATION_TARGETS_MATCH_ALL = /.*/
379
+
380
+ DEFAULT_PATCHES = %i[redis puma http].freeze
381
+
382
+ APP_DIRS_PATTERN = /(bin|exe|app|config|lib|test|spec)/
383
+
293
384
  class << self
294
385
  # Post initialization callbacks are called at the end of initialization process
295
386
  # allowing extending the configuration of sentry-ruby by multiple extensions
@@ -297,17 +388,58 @@ module Sentry
297
388
  @post_initialization_callbacks ||= []
298
389
  end
299
390
 
300
- # allow extensions to add their hooks to the Configuration class
391
+ # allow extensions to add their hooks to the Configuration class
301
392
  def add_post_initialization_callback(&block)
302
393
  post_initialization_callbacks << block
303
394
  end
395
+
396
+ def validations
397
+ @validations ||= {}
398
+ end
399
+
400
+ def validate(attribute, optional: false, type: nil)
401
+ validations[attribute] = {
402
+ optional: optional,
403
+ type: type,
404
+ proc: build_validation_proc(optional, type)
405
+ }
406
+ end
407
+
408
+ private
409
+
410
+ def build_validation_proc(optional, type)
411
+ case type
412
+ when :numeric
413
+ ->(value) do
414
+ if optional && value.nil?
415
+ true
416
+ else
417
+ unless value.is_a?(Numeric)
418
+ message = "must be a Numeric"
419
+ message += " or nil" if optional
420
+
421
+ { error: message, value: value }
422
+ else
423
+ true
424
+ end
425
+ end
426
+ end
427
+ else
428
+ ->(value) { true }
429
+ end
430
+ end
304
431
  end
305
432
 
433
+ validate :traces_sample_rate, optional: true, type: :numeric
434
+ validate :profiles_sample_rate, optional: true, type: :numeric
435
+
306
436
  def initialize
307
- self.app_dirs_pattern = nil
308
- self.debug = false
309
- self.background_worker_threads = Concurrent.processor_count
437
+ self.app_dirs_pattern = APP_DIRS_PATTERN
438
+ self.debug = Sentry::Utils::EnvHelper.env_to_bool(ENV["SENTRY_DEBUG"])
439
+ self.background_worker_threads = (processor_count / 2.0).ceil
440
+ self.background_worker_max_queue = BackgroundWorker::DEFAULT_MAX_QUEUE
310
441
  self.backtrace_cleanup_callback = nil
442
+ self.strip_backtrace_load_path = true
311
443
  self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
312
444
  self.breadcrumbs_logger = []
313
445
  self.context_lines = 3
@@ -318,7 +450,7 @@ module Sentry
318
450
  self.excluded_exceptions = IGNORE_DEFAULT + PUMA_IGNORE_DEFAULT
319
451
  self.inspect_exception_causes_for_exclusion = true
320
452
  self.linecache = ::Sentry::LineCache.new
321
- self.logger = ::Sentry::Logger.new(STDOUT)
453
+ self.sdk_logger = ::Sentry::Logger.new(STDOUT)
322
454
  self.project_root = Dir.pwd
323
455
  self.propagate_traces = true
324
456
 
@@ -328,21 +460,54 @@ module Sentry
328
460
  self.skip_rake_integration = false
329
461
  self.send_client_reports = true
330
462
  self.auto_session_tracking = true
463
+ self.enable_backpressure_handling = false
331
464
  self.trusted_proxies = []
332
- self.dsn = ENV['SENTRY_DSN']
465
+ self.dsn = ENV["SENTRY_DSN"]
466
+
467
+ spotlight_env = ENV["SENTRY_SPOTLIGHT"]
468
+ spotlight_bool = Sentry::Utils::EnvHelper.env_to_bool(spotlight_env, strict: true)
469
+ self.spotlight = spotlight_bool.nil? ? (spotlight_env || false) : spotlight_bool
333
470
  self.server_name = server_name_from_env
334
471
  self.instrumenter = :sentry
472
+ self.trace_propagation_targets = [PROPAGATION_TARGETS_MATCH_ALL]
473
+ self.enabled_patches = DEFAULT_PATCHES.dup
335
474
 
336
475
  self.before_send = nil
337
476
  self.before_send_transaction = nil
477
+ self.before_send_log = nil
338
478
  self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
339
479
  self.traces_sampler = nil
340
480
  self.enable_tracing = nil
481
+ self.enable_logs = false
482
+
483
+ self.profiler_class = Sentry::Profiler
341
484
 
342
485
  @transport = Transport::Configuration.new
486
+ @cron = Cron::Configuration.new
487
+ @metrics = Metrics::Configuration.new
343
488
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
344
489
 
345
490
  run_post_initialization_callbacks
491
+
492
+ self.max_log_events = LogEventBuffer::DEFAULT_MAX_EVENTS
493
+ end
494
+
495
+ def validate
496
+ if profiler_class == Sentry::Profiler && profiles_sample_rate && !Sentry.dependency_installed?(:StackProf)
497
+ log_warn("Please add the 'stackprof' gem to your Gemfile to use the StackProf profiler with Sentry.")
498
+ end
499
+
500
+ if profiler_class == Sentry::Vernier::Profiler && profiles_sample_rate && !Sentry.dependency_installed?(:Vernier)
501
+ log_warn("Please add the 'vernier' gem to your Gemfile to use the Vernier profiler with Sentry.")
502
+ end
503
+
504
+ self.class.validations.each do |attribute, validation|
505
+ value = public_send(attribute)
506
+
507
+ next if (result = validation[:proc].call(value)) === true
508
+
509
+ raise ArgumentError, result[:error]
510
+ end
346
511
  end
347
512
 
348
513
  def dsn=(value)
@@ -413,26 +578,40 @@ module Sentry
413
578
  end
414
579
 
415
580
  def enable_tracing=(enable_tracing)
581
+ unless enable_tracing.nil?
582
+ log_warn <<~MSG
583
+ `enable_tracing` is now deprecated in favor of `traces_sample_rate = 1.0`.
584
+ MSG
585
+ end
586
+
416
587
  @enable_tracing = enable_tracing
417
588
  @traces_sample_rate ||= 1.0 if enable_tracing
418
589
  end
419
590
 
420
- def is_numeric_or_nil?(value)
421
- value.is_a?(Numeric) || value.nil?
422
- end
423
-
424
591
  def traces_sample_rate=(traces_sample_rate)
425
- raise ArgumentError, "traces_sample_rate must be a Numeric or nil" unless is_numeric_or_nil?(traces_sample_rate)
426
592
  @traces_sample_rate = traces_sample_rate
427
593
  end
428
594
 
429
595
  def profiles_sample_rate=(profiles_sample_rate)
430
- raise ArgumentError, "profiles_sample_rate must be a Numeric or nil" unless is_numeric_or_nil?(profiles_sample_rate)
431
- log_info("Please make sure to include the 'stackprof' gem in your Gemfile to use Profiling with Sentry.") unless defined?(StackProf)
432
596
  @profiles_sample_rate = profiles_sample_rate
433
597
  end
434
598
 
599
+ def profiler_class=(profiler_class)
600
+ if profiler_class == Sentry::Vernier::Profiler
601
+ begin
602
+ require "vernier"
603
+ rescue LoadError
604
+ end
605
+ end
606
+
607
+ @profiler_class = profiler_class
608
+ end
609
+
435
610
  def sending_allowed?
611
+ spotlight || sending_to_dsn_allowed?
612
+ end
613
+
614
+ def sending_to_dsn_allowed?
436
615
  @errors = []
437
616
 
438
617
  valid? && capture_in_environment?
@@ -444,6 +623,10 @@ module Sentry
444
623
  Random.rand < sample_rate
445
624
  end
446
625
 
626
+ def session_tracking?
627
+ auto_session_tracking && enabled_in_current_env?
628
+ end
629
+
447
630
  def exception_class_allowed?(exc)
448
631
  if exc.is_a?(Sentry::Error)
449
632
  # Try to prevent error reporting loops
@@ -495,7 +678,8 @@ module Sentry
495
678
  app_dirs_pattern: @app_dirs_pattern,
496
679
  linecache: @linecache,
497
680
  context_lines: @context_lines,
498
- backtrace_cleanup_callback: @backtrace_cleanup_callback
681
+ backtrace_cleanup_callback: @backtrace_cleanup_callback,
682
+ strip_backtrace_load_path: @strip_backtrace_load_path
499
683
  )
500
684
  end
501
685
 
@@ -520,12 +704,6 @@ module Sentry
520
704
 
521
705
  private
522
706
 
523
- def check_callable!(name, value)
524
- unless value == nil || value.respond_to?(:call)
525
- raise ArgumentError, "#{name} must be callable (or nil to disable)"
526
- end
527
- end
528
-
529
707
  def init_dsn(dsn_string)
530
708
  return if dsn_string.nil? || dsn_string.empty?
531
709
 
@@ -578,12 +756,12 @@ module Sentry
578
756
  end
579
757
 
580
758
  def environment_from_env
581
- ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
759
+ ENV["SENTRY_CURRENT_ENV"] || ENV["SENTRY_ENVIRONMENT"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
582
760
  end
583
761
 
584
762
  def server_name_from_env
585
763
  if running_on_heroku?
586
- ENV['DYNO']
764
+ ENV["DYNO"]
587
765
  else
588
766
  # Try to resolve the hostname to an FQDN, but fall back to whatever
589
767
  # the load name is.
@@ -600,5 +778,10 @@ module Sentry
600
778
  instance_eval(&hook)
601
779
  end
602
780
  end
781
+
782
+ def processor_count
783
+ available_processor_count = Concurrent.available_processor_count if Concurrent.respond_to?(:available_processor_count)
784
+ available_processor_count || Concurrent.processor_count
785
+ end
603
786
  end
604
787
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  return if Object.method_defined?(:deep_dup)
4
4
 
5
- require 'sentry/core_ext/object/duplicable'
5
+ require "sentry/core_ext/object/duplicable"
6
6
 
7
7
  #########################################
8
8
  # This file was copied from Rails 5.2 #
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Cron
5
+ class Configuration
6
+ # Defaults set here will apply to all {Cron::MonitorConfig} objects unless overwritten.
7
+
8
+ # How long (in minutes) after the expected checkin time will we wait
9
+ # until we consider the checkin to have been missed.
10
+ # @return [Integer, nil]
11
+ attr_accessor :default_checkin_margin
12
+
13
+ # How long (in minutes) is the checkin allowed to run for in in_progress
14
+ # before it is considered failed.
15
+ # @return [Integer, nil]
16
+ attr_accessor :default_max_runtime
17
+
18
+ # tz database style timezone string
19
+ # @return [String, nil]
20
+ attr_accessor :default_timezone
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Cron
5
+ module MonitorCheckIns
6
+ MAX_SLUG_LENGTH = 50
7
+
8
+ module Patch
9
+ def perform(*args, **opts)
10
+ slug = self.class.sentry_monitor_slug
11
+ monitor_config = self.class.sentry_monitor_config
12
+
13
+ check_in_id = Sentry.capture_check_in(slug,
14
+ :in_progress,
15
+ monitor_config: monitor_config)
16
+
17
+ start = Metrics::Timing.duration_start
18
+
19
+ begin
20
+ # need to do this on ruby <= 2.6 sadly
21
+ ret = method(:perform).super_method.arity == 0 ? super() : super
22
+ duration = Metrics::Timing.duration_end(start)
23
+
24
+ Sentry.capture_check_in(slug,
25
+ :ok,
26
+ check_in_id: check_in_id,
27
+ duration: duration,
28
+ monitor_config: monitor_config)
29
+
30
+ ret
31
+ rescue Exception
32
+ duration = Metrics::Timing.duration_end(start)
33
+
34
+ Sentry.capture_check_in(slug,
35
+ :error,
36
+ check_in_id: check_in_id,
37
+ duration: duration,
38
+ monitor_config: monitor_config)
39
+
40
+ raise
41
+ end
42
+ end
43
+ end
44
+
45
+ module ClassMethods
46
+ def sentry_monitor_check_ins(slug: nil, monitor_config: nil)
47
+ if monitor_config && Sentry.configuration
48
+ cron_config = Sentry.configuration.cron
49
+ monitor_config.checkin_margin ||= cron_config.default_checkin_margin
50
+ monitor_config.max_runtime ||= cron_config.default_max_runtime
51
+ monitor_config.timezone ||= cron_config.default_timezone
52
+ end
53
+
54
+ @sentry_monitor_slug = slug
55
+ @sentry_monitor_config = monitor_config
56
+
57
+ prepend Patch
58
+ end
59
+
60
+ def sentry_monitor_slug(name: self.name)
61
+ @sentry_monitor_slug ||= begin
62
+ slug = name.gsub("::", "-").downcase
63
+ slug[-MAX_SLUG_LENGTH..-1] || slug
64
+ end
65
+ end
66
+
67
+ def sentry_monitor_config
68
+ @sentry_monitor_config
69
+ end
70
+ end
71
+
72
+ def self.included(base)
73
+ base.extend(ClassMethods)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/cron/monitor_schedule"
4
+
5
+ module Sentry
6
+ module Cron
7
+ class MonitorConfig
8
+ # The monitor schedule configuration
9
+ # @return [MonitorSchedule::Crontab, MonitorSchedule::Interval]
10
+ attr_accessor :schedule
11
+
12
+ # How long (in minutes) after the expected checkin time will we wait
13
+ # until we consider the checkin to have been missed.
14
+ # @return [Integer, nil]
15
+ attr_accessor :checkin_margin
16
+
17
+ # How long (in minutes) is the checkin allowed to run for in in_progress
18
+ # before it is considered failed.
19
+ # @return [Integer, nil]
20
+ attr_accessor :max_runtime
21
+
22
+ # tz database style timezone string
23
+ # @return [String, nil]
24
+ attr_accessor :timezone
25
+
26
+ def initialize(schedule, checkin_margin: nil, max_runtime: nil, timezone: nil)
27
+ @schedule = schedule
28
+ @checkin_margin = checkin_margin
29
+ @max_runtime = max_runtime
30
+ @timezone = timezone
31
+ end
32
+
33
+ def self.from_crontab(crontab, **options)
34
+ new(MonitorSchedule::Crontab.new(crontab), **options)
35
+ end
36
+
37
+ def self.from_interval(num, unit, **options)
38
+ return nil unless MonitorSchedule::Interval::VALID_UNITS.include?(unit)
39
+
40
+ new(MonitorSchedule::Interval.new(num, unit), **options)
41
+ end
42
+
43
+ def to_hash
44
+ {
45
+ schedule: schedule.to_hash,
46
+ checkin_margin: checkin_margin,
47
+ max_runtime: max_runtime,
48
+ timezone: timezone
49
+ }.compact
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Cron
5
+ module MonitorSchedule
6
+ class Crontab
7
+ # A crontab formatted string such as "0 * * * *".
8
+ # @return [String]
9
+ attr_accessor :value
10
+
11
+ def initialize(value)
12
+ @value = value
13
+ end
14
+
15
+ def to_hash
16
+ { type: :crontab, value: value }
17
+ end
18
+ end
19
+
20
+ class Interval
21
+ # The number representing duration of the interval.
22
+ # @return [Integer]
23
+ attr_accessor :value
24
+
25
+ # The unit representing duration of the interval.
26
+ # @return [Symbol]
27
+ attr_accessor :unit
28
+
29
+ VALID_UNITS = %i[year month week day hour minute]
30
+
31
+ def initialize(value, unit)
32
+ @value = value
33
+ @unit = unit
34
+ end
35
+
36
+ def to_hash
37
+ { type: :interval, value: value, unit: unit }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
data/lib/sentry/dsn.rb CHANGED
@@ -4,8 +4,8 @@ require "uri"
4
4
 
5
5
  module Sentry
6
6
  class DSN
7
- PORT_MAP = { 'http' => 80, 'https' => 443 }.freeze
8
- REQUIRED_ATTRIBUTES = %w(host path public_key project_id).freeze
7
+ PORT_MAP = { "http" => 80, "https" => 443 }.freeze
8
+ REQUIRED_ATTRIBUTES = %w[host path public_key project_id].freeze
9
9
 
10
10
  attr_reader :scheme, :secret_key, :port, *REQUIRED_ATTRIBUTES
11
11
 
@@ -13,7 +13,7 @@ module Sentry
13
13
  @raw_value = dsn_string
14
14
 
15
15
  uri = URI.parse(dsn_string)
16
- uri_path = uri.path.split('/')
16
+ uri_path = uri.path.split("/")
17
17
 
18
18
  if uri.user
19
19
  # DSN-style string
@@ -25,7 +25,7 @@ module Sentry
25
25
  @scheme = uri.scheme
26
26
  @host = uri.host
27
27
  @port = uri.port if uri.port
28
- @path = uri_path.join('/')
28
+ @path = uri_path.join("/")
29
29
  end
30
30
 
31
31
  def valid?