sentry-ruby 5.16.1 → 5.21.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 (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