sentry-ruby 5.16.1 → 5.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +6 -0
  3. data/README.md +20 -10
  4. data/Rakefile +3 -1
  5. data/bin/console +2 -0
  6. data/lib/sentry/attachment.rb +40 -0
  7. data/lib/sentry/background_worker.rb +1 -1
  8. data/lib/sentry/backpressure_monitor.rb +2 -32
  9. data/lib/sentry/backtrace.rb +10 -8
  10. data/lib/sentry/baggage.rb +7 -7
  11. data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
  12. data/lib/sentry/check_in_event.rb +5 -5
  13. data/lib/sentry/client.rb +61 -11
  14. data/lib/sentry/configuration.rb +77 -31
  15. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  16. data/lib/sentry/cron/monitor_check_ins.rb +3 -1
  17. data/lib/sentry/cron/monitor_config.rb +1 -1
  18. data/lib/sentry/cron/monitor_schedule.rb +1 -1
  19. data/lib/sentry/dsn.rb +4 -4
  20. data/lib/sentry/envelope/item.rb +88 -0
  21. data/lib/sentry/envelope.rb +2 -68
  22. data/lib/sentry/error_event.rb +2 -2
  23. data/lib/sentry/event.rb +20 -18
  24. data/lib/sentry/faraday.rb +77 -0
  25. data/lib/sentry/graphql.rb +9 -0
  26. data/lib/sentry/hub.rb +23 -3
  27. data/lib/sentry/integrable.rb +4 -0
  28. data/lib/sentry/interface.rb +1 -0
  29. data/lib/sentry/interfaces/exception.rb +5 -3
  30. data/lib/sentry/interfaces/mechanism.rb +20 -0
  31. data/lib/sentry/interfaces/request.rb +7 -7
  32. data/lib/sentry/interfaces/single_exception.rb +9 -7
  33. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  34. data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
  35. data/lib/sentry/logger.rb +1 -1
  36. data/lib/sentry/metrics/aggregator.rb +248 -0
  37. data/lib/sentry/metrics/configuration.rb +47 -0
  38. data/lib/sentry/metrics/counter_metric.rb +25 -0
  39. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  40. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  41. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  42. data/lib/sentry/metrics/metric.rb +19 -0
  43. data/lib/sentry/metrics/set_metric.rb +28 -0
  44. data/lib/sentry/metrics/timing.rb +43 -0
  45. data/lib/sentry/metrics.rb +56 -0
  46. data/lib/sentry/net/http.rb +18 -39
  47. data/lib/sentry/profiler/helpers.rb +46 -0
  48. data/lib/sentry/profiler.rb +25 -56
  49. data/lib/sentry/propagation_context.rb +10 -9
  50. data/lib/sentry/puma.rb +1 -1
  51. data/lib/sentry/rack/capture_exceptions.rb +16 -4
  52. data/lib/sentry/rack.rb +2 -2
  53. data/lib/sentry/rake.rb +4 -2
  54. data/lib/sentry/redis.rb +2 -1
  55. data/lib/sentry/release_detector.rb +4 -4
  56. data/lib/sentry/scope.rb +36 -26
  57. data/lib/sentry/session.rb +2 -2
  58. data/lib/sentry/session_flusher.rb +7 -39
  59. data/lib/sentry/span.rb +46 -5
  60. data/lib/sentry/test_helper.rb +5 -2
  61. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  62. data/lib/sentry/transaction.rb +19 -17
  63. data/lib/sentry/transaction_event.rb +6 -2
  64. data/lib/sentry/transport/configuration.rb +0 -1
  65. data/lib/sentry/transport/http_transport.rb +12 -12
  66. data/lib/sentry/transport.rb +18 -26
  67. data/lib/sentry/utils/argument_checking_helper.rb +6 -0
  68. data/lib/sentry/utils/env_helper.rb +21 -0
  69. data/lib/sentry/utils/http_tracing.rb +41 -0
  70. data/lib/sentry/utils/logging_helper.rb +0 -4
  71. data/lib/sentry/utils/real_ip.rb +2 -2
  72. data/lib/sentry/utils/request_id.rb +1 -1
  73. data/lib/sentry/vernier/output.rb +89 -0
  74. data/lib/sentry/vernier/profiler.rb +125 -0
  75. data/lib/sentry/version.rb +1 -1
  76. data/lib/sentry-ruby.rb +38 -6
  77. data/sentry-ruby-core.gemspec +3 -1
  78. data/sentry-ruby.gemspec +15 -6
  79. metadata +44 -7
@@ -3,11 +3,13 @@
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"
10
11
  require "sentry/cron/configuration"
12
+ require "sentry/metrics/configuration"
11
13
  require "sentry/linecache"
12
14
  require "sentry/interfaces/stacktrace_builder"
13
15
 
@@ -21,6 +23,8 @@ module Sentry
21
23
  # have an `engines` dir at the root of your project, you may want
22
24
  # to set this to something like /(app|config|engines|lib)/
23
25
  #
26
+ # The default is value is /(bin|exe|app|config|lib|test|spec)/
27
+ #
24
28
  # @return [Regexp, nil]
25
29
  attr_accessor :app_dirs_pattern
26
30
 
@@ -186,6 +190,11 @@ module Sentry
186
190
  # @return [String]
187
191
  attr_accessor :project_root
188
192
 
193
+ # Whether to strip the load path while constructing the backtrace frame filename.
194
+ # Defaults to true.
195
+ # @return [Boolean]
196
+ attr_accessor :strip_backtrace_load_path
197
+
189
198
  # Insert sentry-trace to outgoing requests' headers
190
199
  # @return [Boolean]
191
200
  attr_accessor :propagate_traces
@@ -235,6 +244,10 @@ module Sentry
235
244
  # @return [Cron::Configuration]
236
245
  attr_reader :cron
237
246
 
247
+ # Metrics related configuration.
248
+ # @return [Metrics::Configuration]
249
+ attr_reader :metrics
250
+
238
251
  # Take a float between 0.0 and 1.0 as the sample rate for tracing events (transactions).
239
252
  # @return [Float, nil]
240
253
  attr_reader :traces_sample_rate
@@ -278,6 +291,10 @@ module Sentry
278
291
  # @return [Symbol]
279
292
  attr_reader :instrumenter
280
293
 
294
+ # The profiler class
295
+ # @return [Class]
296
+ attr_reader :profiler_class
297
+
281
298
  # Take a float between 0.0 and 1.0 as the sample rate for capturing profiles.
282
299
  # Note that this rate is relative to traces_sample_rate / traces_sampler,
283
300
  # i.e. the profile is sampled by this rate after the transaction is sampled.
@@ -297,38 +314,40 @@ module Sentry
297
314
  # But they are mostly considered as noise and should be ignored by default
298
315
  # Please see https://github.com/getsentry/sentry-ruby/pull/2026 for more information
299
316
  PUMA_IGNORE_DEFAULT = [
300
- 'Puma::MiniSSL::SSLError',
301
- 'Puma::HttpParserError',
302
- 'Puma::HttpParserError501'
317
+ "Puma::MiniSSL::SSLError",
318
+ "Puma::HttpParserError",
319
+ "Puma::HttpParserError501"
303
320
  ].freeze
304
321
 
305
322
  # Most of these errors generate 4XX responses. In general, Sentry clients
306
323
  # only automatically report 5xx responses.
307
324
  IGNORE_DEFAULT = [
308
- 'Mongoid::Errors::DocumentNotFound',
309
- 'Rack::QueryParser::InvalidParameterError',
310
- 'Rack::QueryParser::ParameterTypeError',
311
- 'Sinatra::NotFound'
325
+ "Mongoid::Errors::DocumentNotFound",
326
+ "Rack::QueryParser::InvalidParameterError",
327
+ "Rack::QueryParser::ParameterTypeError",
328
+ "Sinatra::NotFound"
312
329
  ].freeze
313
330
 
314
- RACK_ENV_WHITELIST_DEFAULT = %w(
331
+ RACK_ENV_WHITELIST_DEFAULT = %w[
315
332
  REMOTE_ADDR
316
333
  SERVER_NAME
317
334
  SERVER_PORT
318
- ).freeze
335
+ ].freeze
319
336
 
320
337
  HEROKU_DYNO_METADATA_MESSAGE = "You are running on Heroku but haven't enabled Dyno Metadata. For Sentry's "\
321
- "release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`".freeze
338
+ "release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`"
322
339
 
323
- LOG_PREFIX = "** [Sentry] ".freeze
324
- MODULE_SEPARATOR = "::".freeze
340
+ LOG_PREFIX = "** [Sentry] "
341
+ MODULE_SEPARATOR = "::"
325
342
  SKIP_INSPECTION_ATTRIBUTES = [:@linecache, :@stacktrace_builder]
326
343
 
327
344
  INSTRUMENTERS = [:sentry, :otel]
328
345
 
329
- PROPAGATION_TARGETS_MATCH_ALL = /.*/.freeze
346
+ PROPAGATION_TARGETS_MATCH_ALL = /.*/
330
347
 
331
- DEFAULT_PATCHES = %i(redis puma http).freeze
348
+ DEFAULT_PATCHES = %i[redis puma http].freeze
349
+
350
+ APP_DIRS_PATTERN = /(bin|exe|app|config|lib|test|spec)/
332
351
 
333
352
  class << self
334
353
  # Post initialization callbacks are called at the end of initialization process
@@ -337,18 +356,19 @@ module Sentry
337
356
  @post_initialization_callbacks ||= []
338
357
  end
339
358
 
340
- # allow extensions to add their hooks to the Configuration class
359
+ # allow extensions to add their hooks to the Configuration class
341
360
  def add_post_initialization_callback(&block)
342
361
  post_initialization_callbacks << block
343
362
  end
344
363
  end
345
364
 
346
365
  def initialize
347
- self.app_dirs_pattern = nil
348
- self.debug = false
349
- self.background_worker_threads = Concurrent.processor_count
366
+ self.app_dirs_pattern = APP_DIRS_PATTERN
367
+ self.debug = Sentry::Utils::EnvHelper.env_to_bool(ENV["SENTRY_DEBUG"])
368
+ self.background_worker_threads = (processor_count / 2.0).ceil
350
369
  self.background_worker_max_queue = BackgroundWorker::DEFAULT_MAX_QUEUE
351
370
  self.backtrace_cleanup_callback = nil
371
+ self.strip_backtrace_load_path = true
352
372
  self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
353
373
  self.breadcrumbs_logger = []
354
374
  self.context_lines = 3
@@ -371,8 +391,11 @@ module Sentry
371
391
  self.auto_session_tracking = true
372
392
  self.enable_backpressure_handling = false
373
393
  self.trusted_proxies = []
374
- self.dsn = ENV['SENTRY_DSN']
375
- self.spotlight = false
394
+ self.dsn = ENV["SENTRY_DSN"]
395
+
396
+ spotlight_env = ENV["SENTRY_SPOTLIGHT"]
397
+ spotlight_bool = Sentry::Utils::EnvHelper.env_to_bool(spotlight_env, strict: true)
398
+ self.spotlight = spotlight_bool.nil? ? (spotlight_env || false) : spotlight_bool
376
399
  self.server_name = server_name_from_env
377
400
  self.instrumenter = :sentry
378
401
  self.trace_propagation_targets = [PROPAGATION_TARGETS_MATCH_ALL]
@@ -384,8 +407,11 @@ module Sentry
384
407
  self.traces_sampler = nil
385
408
  self.enable_tracing = nil
386
409
 
410
+ self.profiler_class = Sentry::Profiler
411
+
387
412
  @transport = Transport::Configuration.new
388
413
  @cron = Cron::Configuration.new
414
+ @metrics = Metrics::Configuration.new
389
415
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
390
416
 
391
417
  run_post_initialization_callbacks
@@ -478,10 +504,26 @@ module Sentry
478
504
  @profiles_sample_rate = profiles_sample_rate
479
505
  end
480
506
 
507
+ def profiler_class=(profiler_class)
508
+ if profiler_class == Sentry::Vernier::Profiler
509
+ begin
510
+ require "vernier"
511
+ rescue LoadError
512
+ raise ArgumentError, "Please add the 'vernier' gem to your Gemfile to use the Vernier profiler with Sentry."
513
+ end
514
+ end
515
+
516
+ @profiler_class = profiler_class
517
+ end
518
+
481
519
  def sending_allowed?
520
+ spotlight || sending_to_dsn_allowed?
521
+ end
522
+
523
+ def sending_to_dsn_allowed?
482
524
  @errors = []
483
525
 
484
- spotlight || (valid? && capture_in_environment?)
526
+ valid? && capture_in_environment?
485
527
  end
486
528
 
487
529
  def sample_allowed?
@@ -490,6 +532,10 @@ module Sentry
490
532
  Random.rand < sample_rate
491
533
  end
492
534
 
535
+ def session_tracking?
536
+ auto_session_tracking && enabled_in_current_env?
537
+ end
538
+
493
539
  def exception_class_allowed?(exc)
494
540
  if exc.is_a?(Sentry::Error)
495
541
  # Try to prevent error reporting loops
@@ -541,7 +587,8 @@ module Sentry
541
587
  app_dirs_pattern: @app_dirs_pattern,
542
588
  linecache: @linecache,
543
589
  context_lines: @context_lines,
544
- backtrace_cleanup_callback: @backtrace_cleanup_callback
590
+ backtrace_cleanup_callback: @backtrace_cleanup_callback,
591
+ strip_backtrace_load_path: @strip_backtrace_load_path
545
592
  )
546
593
  end
547
594
 
@@ -566,12 +613,6 @@ module Sentry
566
613
 
567
614
  private
568
615
 
569
- def check_callable!(name, value)
570
- unless value == nil || value.respond_to?(:call)
571
- raise ArgumentError, "#{name} must be callable (or nil to disable)"
572
- end
573
- end
574
-
575
616
  def init_dsn(dsn_string)
576
617
  return if dsn_string.nil? || dsn_string.empty?
577
618
 
@@ -624,12 +665,12 @@ module Sentry
624
665
  end
625
666
 
626
667
  def environment_from_env
627
- ENV['SENTRY_CURRENT_ENV'] || ENV['SENTRY_ENVIRONMENT'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
668
+ ENV["SENTRY_CURRENT_ENV"] || ENV["SENTRY_ENVIRONMENT"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
628
669
  end
629
670
 
630
671
  def server_name_from_env
631
672
  if running_on_heroku?
632
- ENV['DYNO']
673
+ ENV["DYNO"]
633
674
  else
634
675
  # Try to resolve the hostname to an FQDN, but fall back to whatever
635
676
  # the load name is.
@@ -646,5 +687,10 @@ module Sentry
646
687
  instance_eval(&hook)
647
688
  end
648
689
  end
690
+
691
+ def processor_count
692
+ available_processor_count = Concurrent.available_processor_count if Concurrent.respond_to?(:available_processor_count)
693
+ available_processor_count || Concurrent.processor_count
694
+ end
649
695
  end
650
696
  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 #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  module Cron
3
5
  module MonitorCheckIns
@@ -57,7 +59,7 @@ module Sentry
57
59
 
58
60
  def sentry_monitor_slug(name: self.name)
59
61
  @sentry_monitor_slug ||= begin
60
- slug = name.gsub('::', '-').downcase
62
+ slug = name.gsub("::", "-").downcase
61
63
  slug[-MAX_SLUG_LENGTH..-1] || slug
62
64
  end
63
65
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'sentry/cron/monitor_schedule'
3
+ require "sentry/cron/monitor_schedule"
4
4
 
5
5
  module Sentry
6
6
  module Cron
@@ -26,7 +26,7 @@ module Sentry
26
26
  # @return [Symbol]
27
27
  attr_accessor :unit
28
28
 
29
- VALID_UNITS = %i(year month week day hour minute)
29
+ VALID_UNITS = %i[year month week day hour minute]
30
30
 
31
31
  def initialize(value, unit)
32
32
  @value = value
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?
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ # @api private
5
+ class Envelope::Item
6
+ STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD = 500
7
+ MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 1000
8
+
9
+ SIZE_LIMITS = Hash.new(MAX_SERIALIZED_PAYLOAD_SIZE).update(
10
+ "profile" => 1024 * 1000 * 50
11
+ )
12
+
13
+ attr_reader :size_limit, :headers, :payload, :type, :data_category
14
+
15
+ # rate limits and client reports use the data_category rather than envelope item type
16
+ def self.data_category(type)
17
+ case type
18
+ when "session", "attachment", "transaction", "profile", "span" then type
19
+ when "sessions" then "session"
20
+ when "check_in" then "monitor"
21
+ when "statsd", "metric_meta" then "metric_bucket"
22
+ when "event" then "error"
23
+ when "client_report" then "internal"
24
+ else "default"
25
+ end
26
+ end
27
+
28
+ def initialize(headers, payload)
29
+ @headers = headers
30
+ @payload = payload
31
+ @type = headers[:type] || "event"
32
+ @data_category = self.class.data_category(type)
33
+ @size_limit = SIZE_LIMITS[type]
34
+ end
35
+
36
+ def to_s
37
+ [JSON.generate(@headers), @payload.is_a?(String) ? @payload : JSON.generate(@payload)].join("\n")
38
+ end
39
+
40
+ def serialize
41
+ result = to_s
42
+
43
+ if result.bytesize > size_limit
44
+ remove_breadcrumbs!
45
+ result = to_s
46
+ end
47
+
48
+ if result.bytesize > size_limit
49
+ reduce_stacktrace!
50
+ result = to_s
51
+ end
52
+
53
+ [result, result.bytesize > size_limit]
54
+ end
55
+
56
+ def size_breakdown
57
+ payload.map do |key, value|
58
+ "#{key}: #{JSON.generate(value).bytesize}"
59
+ end.join(", ")
60
+ end
61
+
62
+ private
63
+
64
+ def remove_breadcrumbs!
65
+ if payload.key?(:breadcrumbs)
66
+ payload.delete(:breadcrumbs)
67
+ elsif payload.key?("breadcrumbs")
68
+ payload.delete("breadcrumbs")
69
+ end
70
+ end
71
+
72
+ def reduce_stacktrace!
73
+ if exceptions = payload.dig(:exception, :values) || payload.dig("exception", "values")
74
+ exceptions.each do |exception|
75
+ # in most cases there is only one exception (2 or 3 when have multiple causes), so we won't loop through this double condition much
76
+ traces = exception.dig(:stacktrace, :frames) || exception.dig("stacktrace", "frames")
77
+
78
+ if traces && traces.size > STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD
79
+ size_on_both_ends = STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD / 2
80
+ traces.replace(
81
+ traces[0..(size_on_both_ends - 1)] + traces[-size_on_both_ends..-1],
82
+ )
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -3,74 +3,6 @@
3
3
  module Sentry
4
4
  # @api private
5
5
  class Envelope
6
- class Item
7
- STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD = 500
8
- MAX_SERIALIZED_PAYLOAD_SIZE = 1024 * 1000
9
-
10
- attr_accessor :headers, :payload
11
-
12
- def initialize(headers, payload)
13
- @headers = headers
14
- @payload = payload
15
- end
16
-
17
- def type
18
- @headers[:type] || 'event'
19
- end
20
-
21
- def to_s
22
- [JSON.generate(@headers), JSON.generate(@payload)].join("\n")
23
- end
24
-
25
- def serialize
26
- result = to_s
27
-
28
- if result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE
29
- remove_breadcrumbs!
30
- result = to_s
31
- end
32
-
33
- if result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE
34
- reduce_stacktrace!
35
- result = to_s
36
- end
37
-
38
- [result, result.bytesize > MAX_SERIALIZED_PAYLOAD_SIZE]
39
- end
40
-
41
- def size_breakdown
42
- payload.map do |key, value|
43
- "#{key}: #{JSON.generate(value).bytesize}"
44
- end.join(", ")
45
- end
46
-
47
- private
48
-
49
- def remove_breadcrumbs!
50
- if payload.key?(:breadcrumbs)
51
- payload.delete(:breadcrumbs)
52
- elsif payload.key?("breadcrumbs")
53
- payload.delete("breadcrumbs")
54
- end
55
- end
56
-
57
- def reduce_stacktrace!
58
- if exceptions = payload.dig(:exception, :values) || payload.dig("exception", "values")
59
- exceptions.each do |exception|
60
- # in most cases there is only one exception (2 or 3 when have multiple causes), so we won't loop through this double condition much
61
- traces = exception.dig(:stacktrace, :frames) || exception.dig("stacktrace", "frames")
62
-
63
- if traces && traces.size > STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD
64
- size_on_both_ends = STACKTRACE_FRAME_LIMIT_ON_OVERSIZED_PAYLOAD / 2
65
- traces.replace(
66
- traces[0..(size_on_both_ends - 1)] + traces[-size_on_both_ends..-1],
67
- )
68
- end
69
- end
70
- end
71
- end
72
- end
73
-
74
6
  attr_accessor :headers, :items
75
7
 
76
8
  def initialize(headers = {})
@@ -91,3 +23,5 @@ module Sentry
91
23
  end
92
24
  end
93
25
  end
26
+
27
+ require_relative "envelope/item"
@@ -27,12 +27,12 @@ module Sentry
27
27
  end
28
28
 
29
29
  # @!visibility private
30
- def add_exception_interface(exception)
30
+ def add_exception_interface(exception, mechanism:)
31
31
  if exception.respond_to?(:sentry_context)
32
32
  @extra.merge!(exception.sentry_context)
33
33
  end
34
34
 
35
- @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder)
35
+ @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: @stacktrace_builder, mechanism: mechanism)
36
36
  end
37
37
  end
38
38
  end
data/lib/sentry/event.rb CHANGED
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'socket'
4
- require 'securerandom'
5
- require 'sentry/interface'
6
- require 'sentry/backtrace'
7
- require 'sentry/utils/real_ip'
8
- require 'sentry/utils/request_id'
9
- require 'sentry/utils/custom_inspection'
3
+ require "socket"
4
+ require "securerandom"
5
+ require "sentry/interface"
6
+ require "sentry/backtrace"
7
+ require "sentry/utils/real_ip"
8
+ require "sentry/utils/request_id"
9
+ require "sentry/utils/custom_inspection"
10
10
 
11
11
  module Sentry
12
12
  # This is an abstract class that defines the shared attributes of an event.
@@ -14,16 +14,16 @@ module Sentry
14
14
  class Event
15
15
  TYPE = "event"
16
16
  # These are readable attributes.
17
- SERIALIZEABLE_ATTRIBUTES = %i(
17
+ SERIALIZEABLE_ATTRIBUTES = %i[
18
18
  event_id level timestamp
19
19
  release environment server_name modules
20
20
  message user tags contexts extra
21
21
  fingerprint breadcrumbs transaction transaction_info
22
22
  platform sdk type
23
- )
23
+ ]
24
24
 
25
25
  # These are writable attributes.
26
- WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i(type timestamp level)
26
+ WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i[type timestamp level]
27
27
 
28
28
  MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
29
29
 
@@ -42,6 +42,9 @@ module Sentry
42
42
  # @return [Hash, nil]
43
43
  attr_accessor :dynamic_sampling_context
44
44
 
45
+ # @return [Array<Attachment>]
46
+ attr_accessor :attachments
47
+
45
48
  # @param configuration [Configuration]
46
49
  # @param integration_meta [Hash, nil]
47
50
  # @param message [String, nil]
@@ -57,6 +60,7 @@ module Sentry
57
60
  @extra = {}
58
61
  @contexts = {}
59
62
  @tags = {}
63
+ @attachments = []
60
64
 
61
65
  @fingerprint = []
62
66
  @dynamic_sampling_context = nil
@@ -104,9 +108,7 @@ module Sentry
104
108
  unless request || env.empty?
105
109
  add_request_interface(env)
106
110
 
107
- if @send_default_pii
108
- user[:ip_address] = calculate_real_ip_from_rack(env)
109
- end
111
+ user[:ip_address] ||= calculate_real_ip_from_rack(env) if @send_default_pii
110
112
 
111
113
  if request_id = Utils::RequestId.read_from(env)
112
114
  tags[:request_id] = request_id
@@ -145,11 +147,11 @@ module Sentry
145
147
  # REMOTE_ADDR to determine the Event IP, and must use other headers instead.
146
148
  def calculate_real_ip_from_rack(env)
147
149
  Utils::RealIp.new(
148
- :remote_addr => env["REMOTE_ADDR"],
149
- :client_ip => env["HTTP_CLIENT_IP"],
150
- :real_ip => env["HTTP_X_REAL_IP"],
151
- :forwarded_for => env["HTTP_X_FORWARDED_FOR"],
152
- :trusted_proxies => @trusted_proxies
150
+ remote_addr: env["REMOTE_ADDR"],
151
+ client_ip: env["HTTP_CLIENT_IP"],
152
+ real_ip: env["HTTP_X_REAL_IP"],
153
+ forwarded_for: env["HTTP_X_FORWARDED_FOR"],
154
+ trusted_proxies: @trusted_proxies
153
155
  ).calculate_ip
154
156
  end
155
157
  end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Faraday
5
+ OP_NAME = "http.client"
6
+
7
+ module Connection
8
+ # Since there's no way to preconfigure Faraday connections and add our instrumentation
9
+ # by default, we need to extend the connection constructor and do it there
10
+ #
11
+ # @see https://lostisland.github.io/faraday/#/customization/index?id=configuration
12
+ def initialize(url = nil, options = nil)
13
+ super
14
+
15
+ # Ensure that we attach instrumentation only if the adapter is not net/http
16
+ # because if is is, then the net/http instrumentation will take care of it
17
+ if builder.adapter.name != "Faraday::Adapter::NetHttp"
18
+ # Make sure that it's going to be the first middleware so that it can capture
19
+ # the entire request processing involving other middlewares
20
+ builder.insert(0, ::Faraday::Request::Instrumentation, name: OP_NAME, instrumenter: Instrumenter.new)
21
+ end
22
+ end
23
+ end
24
+
25
+ class Instrumenter
26
+ SPAN_ORIGIN = "auto.http.faraday"
27
+ BREADCRUMB_CATEGORY = "http"
28
+
29
+ include Utils::HttpTracing
30
+
31
+ def instrument(op_name, env, &block)
32
+ return block.call unless Sentry.initialized?
33
+
34
+ Sentry.with_child_span(op: op_name, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |sentry_span|
35
+ request_info = extract_request_info(env)
36
+
37
+ if propagate_trace?(request_info[:url])
38
+ set_propagation_headers(env[:request_headers])
39
+ end
40
+
41
+ res = block.call
42
+ response_status = res.status
43
+
44
+ if record_sentry_breadcrumb?
45
+ record_sentry_breadcrumb(request_info, response_status)
46
+ end
47
+
48
+ if sentry_span
49
+ set_span_info(sentry_span, request_info, response_status)
50
+ end
51
+
52
+ res
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def extract_request_info(env)
59
+ url = env[:url].scheme + "://" + env[:url].host + env[:url].path
60
+ result = { method: env[:method].to_s.upcase, url: url }
61
+
62
+ if Sentry.configuration.send_default_pii
63
+ result[:query] = env[:url].query
64
+ result[:body] = env[:body]
65
+ end
66
+
67
+ result
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ Sentry.register_patch(:faraday) do
74
+ if defined?(::Faraday)
75
+ ::Faraday::Connection.prepend(Sentry::Faraday::Connection)
76
+ end
77
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sentry.register_patch(:graphql) do |config|
4
+ if defined?(::GraphQL::Schema) && defined?(::GraphQL::Tracing::SentryTrace) && ::GraphQL::Schema.respond_to?(:trace_with)
5
+ ::GraphQL::Schema.trace_with(::GraphQL::Tracing::SentryTrace, set_transaction_name: true)
6
+ else
7
+ config.logger.warn(Sentry::LOGGER_PROGNAME) { "You tried to enable the GraphQL integration but no GraphQL gem was detected. Make sure you have the `graphql` gem (>= 2.2.6) in your Gemfile." }
8
+ end
9
+ end