sentry-ruby 5.13.0 → 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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -18
  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 +9 -2
  8. data/lib/sentry/backpressure_monitor.rb +45 -0
  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 +71 -18
  14. data/lib/sentry/configuration.rb +108 -32
  15. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  16. data/lib/sentry/cron/configuration.rb +23 -0
  17. data/lib/sentry/cron/monitor_check_ins.rb +42 -26
  18. data/lib/sentry/cron/monitor_config.rb +1 -1
  19. data/lib/sentry/cron/monitor_schedule.rb +1 -1
  20. data/lib/sentry/dsn.rb +4 -4
  21. data/lib/sentry/envelope/item.rb +88 -0
  22. data/lib/sentry/envelope.rb +2 -68
  23. data/lib/sentry/error_event.rb +2 -2
  24. data/lib/sentry/event.rb +20 -46
  25. data/lib/sentry/faraday.rb +77 -0
  26. data/lib/sentry/graphql.rb +9 -0
  27. data/lib/sentry/hub.rb +25 -5
  28. data/lib/sentry/integrable.rb +4 -0
  29. data/lib/sentry/interface.rb +1 -0
  30. data/lib/sentry/interfaces/exception.rb +5 -3
  31. data/lib/sentry/interfaces/mechanism.rb +20 -0
  32. data/lib/sentry/interfaces/request.rb +7 -7
  33. data/lib/sentry/interfaces/single_exception.rb +10 -7
  34. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  35. data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
  36. data/lib/sentry/logger.rb +1 -1
  37. data/lib/sentry/metrics/aggregator.rb +248 -0
  38. data/lib/sentry/metrics/configuration.rb +47 -0
  39. data/lib/sentry/metrics/counter_metric.rb +25 -0
  40. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  41. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  42. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  43. data/lib/sentry/metrics/metric.rb +19 -0
  44. data/lib/sentry/metrics/set_metric.rb +28 -0
  45. data/lib/sentry/metrics/timing.rb +43 -0
  46. data/lib/sentry/metrics.rb +56 -0
  47. data/lib/sentry/net/http.rb +22 -39
  48. data/lib/sentry/profiler/helpers.rb +46 -0
  49. data/lib/sentry/profiler.rb +25 -56
  50. data/lib/sentry/propagation_context.rb +10 -9
  51. data/lib/sentry/puma.rb +1 -1
  52. data/lib/sentry/rack/capture_exceptions.rb +16 -4
  53. data/lib/sentry/rack.rb +2 -2
  54. data/lib/sentry/rake.rb +4 -15
  55. data/lib/sentry/redis.rb +2 -1
  56. data/lib/sentry/release_detector.rb +5 -5
  57. data/lib/sentry/scope.rb +48 -37
  58. data/lib/sentry/session.rb +2 -2
  59. data/lib/sentry/session_flusher.rb +7 -39
  60. data/lib/sentry/span.rb +46 -5
  61. data/lib/sentry/test_helper.rb +5 -2
  62. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  63. data/lib/sentry/transaction.rb +27 -18
  64. data/lib/sentry/transaction_event.rb +6 -2
  65. data/lib/sentry/transport/configuration.rb +73 -1
  66. data/lib/sentry/transport/http_transport.rb +72 -41
  67. data/lib/sentry/transport/spotlight_transport.rb +50 -0
  68. data/lib/sentry/transport.rb +36 -41
  69. data/lib/sentry/utils/argument_checking_helper.rb +6 -0
  70. data/lib/sentry/utils/env_helper.rb +21 -0
  71. data/lib/sentry/utils/http_tracing.rb +41 -0
  72. data/lib/sentry/utils/logging_helper.rb +0 -4
  73. data/lib/sentry/utils/real_ip.rb +2 -2
  74. data/lib/sentry/utils/request_id.rb +1 -1
  75. data/lib/sentry/vernier/output.rb +89 -0
  76. data/lib/sentry/vernier/profiler.rb +125 -0
  77. data/lib/sentry/version.rb +1 -1
  78. data/lib/sentry-ruby.rb +61 -27
  79. data/sentry-ruby-core.gemspec +3 -1
  80. data/sentry-ruby.gemspec +15 -6
  81. metadata +47 -7
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module Sentry
6
+ class Profiler
7
+ module Helpers
8
+ def in_app?(abs_path)
9
+ abs_path.match?(@in_app_pattern)
10
+ end
11
+
12
+ # copied from stacktrace.rb since I don't want to touch existing code
13
+ # TODO-neel-profiler try to fetch this from stackprof once we patch
14
+ # the native extension
15
+ def compute_filename(abs_path, in_app)
16
+ return nil if abs_path.nil?
17
+
18
+ under_project_root = @project_root && abs_path.start_with?(@project_root)
19
+
20
+ prefix =
21
+ if under_project_root && in_app
22
+ @project_root
23
+ else
24
+ longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
25
+
26
+ if under_project_root
27
+ longest_load_path || @project_root
28
+ else
29
+ longest_load_path
30
+ end
31
+ end
32
+
33
+ prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
34
+ end
35
+
36
+ def split_module(name)
37
+ # last module plus class/instance method
38
+ i = name.rindex("::")
39
+ function = i ? name[(i + 2)..-1] : name
40
+ mod = i ? name[0...i] : nil
41
+
42
+ [function, mod]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
3
+ require "securerandom"
4
+ require_relative "profiler/helpers"
4
5
 
5
6
  module Sentry
6
7
  class Profiler
7
- VERSION = '1'
8
- PLATFORM = 'ruby'
8
+ include Profiler::Helpers
9
+
10
+ VERSION = "1"
11
+ PLATFORM = "ruby"
9
12
  # 101 Hz in microseconds
10
13
  DEFAULT_INTERVAL = 1e6 / 101
11
14
  MICRO_TO_NANO_SECONDS = 1e3
@@ -14,14 +17,14 @@ module Sentry
14
17
  attr_reader :sampled, :started, :event_id
15
18
 
16
19
  def initialize(configuration)
17
- @event_id = SecureRandom.uuid.delete('-')
20
+ @event_id = SecureRandom.uuid.delete("-")
18
21
  @started = false
19
22
  @sampled = nil
20
23
 
21
24
  @profiling_enabled = defined?(StackProf) && configuration.profiling_enabled?
22
25
  @profiles_sample_rate = configuration.profiles_sample_rate
23
26
  @project_root = configuration.project_root
24
- @app_dirs_pattern = configuration.app_dirs_pattern || Backtrace::APP_DIRS_PATTERN
27
+ @app_dirs_pattern = configuration.app_dirs_pattern
25
28
  @in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
26
29
  end
27
30
 
@@ -33,7 +36,7 @@ module Sentry
33
36
  raw: true,
34
37
  aggregate: false)
35
38
 
36
- @started ? log('Started') : log('Not started since running elsewhere')
39
+ @started ? log("Started") : log("Not started since running elsewhere")
37
40
  end
38
41
 
39
42
  def stop
@@ -41,7 +44,11 @@ module Sentry
41
44
  return unless @started
42
45
 
43
46
  StackProf.stop
44
- log('Stopped')
47
+ log("Stopped")
48
+ end
49
+
50
+ def active_thread_id
51
+ "0"
45
52
  end
46
53
 
47
54
  # Sets initial sampling decision of the profile.
@@ -54,14 +61,14 @@ module Sentry
54
61
 
55
62
  unless transaction_sampled
56
63
  @sampled = false
57
- log('Discarding profile because transaction not sampled')
64
+ log("Discarding profile because transaction not sampled")
58
65
  return
59
66
  end
60
67
 
61
68
  case @profiles_sample_rate
62
69
  when 0.0
63
70
  @sampled = false
64
- log('Discarding profile because sample_rate is 0')
71
+ log("Discarding profile because sample_rate is 0")
65
72
  return
66
73
  when 1.0
67
74
  @sampled = true
@@ -70,7 +77,7 @@ module Sentry
70
77
  @sampled = Random.rand < @profiles_sample_rate
71
78
  end
72
79
 
73
- log('Discarding profile due to sampling decision') unless @sampled
80
+ log("Discarding profile due to sampling decision") unless @sampled
74
81
  end
75
82
 
76
83
  def to_hash
@@ -90,13 +97,12 @@ module Sentry
90
97
 
91
98
  frame_map = {}
92
99
 
93
- frames = results[:frames].to_enum.with_index.map do |frame, idx|
94
- frame_id, frame_data = frame
95
-
100
+ frames = results[:frames].map.with_index do |(frame_id, frame_data), idx|
96
101
  # need to map over stackprof frame ids to ours
97
102
  frame_map[frame_id] = idx
98
103
 
99
104
  file_path = frame_data[:file]
105
+ lineno = frame_data[:line]
100
106
  in_app = in_app?(file_path)
101
107
  filename = compute_filename(file_path, in_app)
102
108
  function, mod = split_module(frame_data[:name])
@@ -109,7 +115,7 @@ module Sentry
109
115
  }
110
116
 
111
117
  frame_hash[:module] = mod if mod
112
- frame_hash[:lineno] = frame_data[:line] if frame_data[:line] && frame_data[:line] >= 0
118
+ frame_hash[:lineno] = lineno if lineno && lineno >= 0
113
119
 
114
120
  frame_hash
115
121
  end
@@ -130,7 +136,7 @@ module Sentry
130
136
  num_seen << results[:raw][idx + len]
131
137
  idx += len + 1
132
138
 
133
- log('Unknown frame in stack') if stack.size != len
139
+ log("Unknown frame in stack") if stack.size != len
134
140
  end
135
141
 
136
142
  idx = 0
@@ -155,16 +161,16 @@ module Sentry
155
161
  # Till then, on multi-threaded servers like puma, we will get frames from other active threads when the one
156
162
  # we're profiling is idle/sleeping/waiting for IO etc.
157
163
  # https://bugs.ruby-lang.org/issues/10602
158
- thread_id: '0',
164
+ thread_id: "0",
159
165
  elapsed_since_start_ns: elapsed_since_start_ns.to_s
160
166
  }
161
167
  end
162
168
  end
163
169
 
164
- log('Some samples thrown away') if samples.size != results[:samples]
170
+ log("Some samples thrown away") if samples.size != results[:samples]
165
171
 
166
172
  if samples.size <= MIN_SAMPLES_REQUIRED
167
- log('Not enough samples, discarding profiler')
173
+ log("Not enough samples, discarding profiler")
168
174
  record_lost_event(:insufficient_data)
169
175
  return {}
170
176
  end
@@ -189,45 +195,8 @@ module Sentry
189
195
  Sentry.logger.debug(LOGGER_PROGNAME) { "[Profiler] #{message}" }
190
196
  end
191
197
 
192
- def in_app?(abs_path)
193
- abs_path.match?(@in_app_pattern)
194
- end
195
-
196
- # copied from stacktrace.rb since I don't want to touch existing code
197
- # TODO-neel-profiler try to fetch this from stackprof once we patch
198
- # the native extension
199
- def compute_filename(abs_path, in_app)
200
- return nil if abs_path.nil?
201
-
202
- under_project_root = @project_root && abs_path.start_with?(@project_root)
203
-
204
- prefix =
205
- if under_project_root && in_app
206
- @project_root
207
- else
208
- longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
209
-
210
- if under_project_root
211
- longest_load_path || @project_root
212
- else
213
- longest_load_path
214
- end
215
- end
216
-
217
- prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
218
- end
219
-
220
- def split_module(name)
221
- # last module plus class/instance method
222
- i = name.rindex('::')
223
- function = i ? name[(i + 2)..-1] : name
224
- mod = i ? name[0...i] : nil
225
-
226
- [function, mod]
227
- end
228
-
229
198
  def record_lost_event(reason)
230
- Sentry.get_current_client&.transport&.record_lost_event(reason, 'profile')
199
+ Sentry.get_current_client&.transport&.record_lost_event(reason, "profile")
231
200
  end
232
201
  end
233
202
  end
@@ -50,14 +50,15 @@ module Sentry
50
50
  if sentry_trace_data
51
51
  @trace_id, @parent_span_id, @parent_sampled = sentry_trace_data
52
52
 
53
- @baggage = if baggage_header && !baggage_header.empty?
54
- Baggage.from_incoming_header(baggage_header)
55
- else
56
- # If there's an incoming sentry-trace but no incoming baggage header,
57
- # for instance in traces coming from older SDKs,
58
- # baggage will be empty and frozen and won't be populated as head SDK.
59
- Baggage.new({})
60
- end
53
+ @baggage =
54
+ if baggage_header && !baggage_header.empty?
55
+ Baggage.from_incoming_header(baggage_header)
56
+ else
57
+ # If there's an incoming sentry-trace but no incoming baggage header,
58
+ # for instance in traces coming from older SDKs,
59
+ # baggage will be empty and frozen and won't be populated as head SDK.
60
+ Baggage.new({})
61
+ end
61
62
 
62
63
  @baggage.freeze!
63
64
  @incoming_trace = true
@@ -107,7 +108,7 @@ module Sentry
107
108
  end
108
109
 
109
110
  # Returns the Dynamic Sampling Context from the baggage.
110
- # @return [String, nil]
111
+ # @return [Hash, nil]
111
112
  def get_dynamic_sampling_context
112
113
  get_baggage&.dynamic_sampling_context
113
114
  end
data/lib/sentry/puma.rb CHANGED
@@ -7,7 +7,7 @@ module Sentry
7
7
  module Server
8
8
  PUMA_4_AND_PRIOR = Gem::Version.new(::Puma::Const::PUMA_VERSION) < Gem::Version.new("5.0.0")
9
9
 
10
- def lowlevel_error(e, env, status=500)
10
+ def lowlevel_error(e, env, status = 500)
11
11
  result =
12
12
  if PUMA_4_AND_PRIOR
13
13
  super(e, env)
@@ -4,6 +4,8 @@ module Sentry
4
4
  module Rack
5
5
  class CaptureExceptions
6
6
  ERROR_EVENT_ID_KEY = "sentry.error_event_id"
7
+ MECHANISM_TYPE = "rack"
8
+ SPAN_ORIGIN = "auto.http.rack"
7
9
 
8
10
  def initialize(app)
9
11
  @app = app
@@ -48,21 +50,27 @@ module Sentry
48
50
  private
49
51
 
50
52
  def collect_exception(env)
51
- env['rack.exception'] || env['sinatra.error']
53
+ env["rack.exception"] || env["sinatra.error"]
52
54
  end
53
55
 
54
56
  def transaction_op
55
- "http.server".freeze
57
+ "http.server"
56
58
  end
57
59
 
58
60
  def capture_exception(exception, env)
59
- Sentry.capture_exception(exception).tap do |event|
61
+ Sentry.capture_exception(exception, hint: { mechanism: mechanism }).tap do |event|
60
62
  env[ERROR_EVENT_ID_KEY] = event.event_id if event
61
63
  end
62
64
  end
63
65
 
64
66
  def start_transaction(env, scope)
65
- options = { name: scope.transaction_name, source: scope.transaction_source, op: transaction_op }
67
+ options = {
68
+ name: scope.transaction_name,
69
+ source: scope.transaction_source,
70
+ op: transaction_op,
71
+ origin: SPAN_ORIGIN
72
+ }
73
+
66
74
  transaction = Sentry.continue_trace(env, **options)
67
75
  Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
68
76
  end
@@ -74,6 +82,10 @@ module Sentry
74
82
  transaction.set_http_status(status_code)
75
83
  transaction.finish
76
84
  end
85
+
86
+ def mechanism
87
+ Sentry::Mechanism.new(type: MECHANISM_TYPE, handled: false)
88
+ end
77
89
  end
78
90
  end
79
91
  end
data/lib/sentry/rack.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rack'
3
+ require "rack"
4
4
 
5
- require 'sentry/rack/capture_exceptions'
5
+ require "sentry/rack/capture_exceptions"
data/lib/sentry/rake.rb CHANGED
@@ -8,8 +8,10 @@ module Sentry
8
8
  module Application
9
9
  # @api private
10
10
  def display_error_message(ex)
11
- Sentry.capture_exception(ex) do |scope|
12
- task_name = top_level_tasks.join(' ')
11
+ mechanism = Sentry::Mechanism.new(type: "rake", handled: false)
12
+
13
+ Sentry.capture_exception(ex, hint: { mechanism: mechanism }) do |scope|
14
+ task_name = top_level_tasks.join(" ")
13
15
  scope.set_transaction_name(task_name, source: :task)
14
16
  scope.set_tag("rake_task", task_name)
15
17
  end if Sentry.initialized? && !Sentry.configuration.skip_rake_integration
@@ -17,15 +19,6 @@ module Sentry
17
19
  super
18
20
  end
19
21
  end
20
-
21
- module Task
22
- # @api private
23
- def execute(args=nil)
24
- return super unless Sentry.initialized? && Sentry.get_current_hub
25
-
26
- super
27
- end
28
- end
29
22
  end
30
23
  end
31
24
 
@@ -34,8 +27,4 @@ module Rake
34
27
  class Application
35
28
  prepend(Sentry::Rake::Application)
36
29
  end
37
-
38
- class Task
39
- prepend(Sentry::Rake::Task)
40
- end
41
30
  end
data/lib/sentry/redis.rb CHANGED
@@ -4,6 +4,7 @@ module Sentry
4
4
  # @api private
5
5
  class Redis
6
6
  OP_NAME = "db.redis"
7
+ SPAN_ORIGIN = "auto.db.redis"
7
8
  LOGGER_NAME = :redis_logger
8
9
 
9
10
  def initialize(commands, host, port, db)
@@ -13,7 +14,7 @@ module Sentry
13
14
  def instrument
14
15
  return yield unless Sentry.initialized?
15
16
 
16
- Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f) do |span|
17
+ Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |span|
17
18
  yield.tap do
18
19
  record_breadcrumb
19
20
 
@@ -13,12 +13,12 @@ module Sentry
13
13
 
14
14
  def detect_release_from_heroku(running_on_heroku)
15
15
  return unless running_on_heroku
16
- ENV['HEROKU_SLUG_COMMIT']
16
+ ENV["HEROKU_SLUG_COMMIT"]
17
17
  end
18
18
 
19
19
  def detect_release_from_capistrano(project_root)
20
- revision_file = File.join(project_root, 'REVISION')
21
- revision_log = File.join(project_root, '..', 'revisions.log')
20
+ revision_file = File.join(project_root, "REVISION")
21
+ revision_log = File.join(project_root, "..", "revisions.log")
22
22
 
23
23
  if File.exist?(revision_file)
24
24
  File.read(revision_file).strip
@@ -28,11 +28,11 @@ module Sentry
28
28
  end
29
29
 
30
30
  def detect_release_from_git
31
- Sentry.sys_command("git rev-parse --short HEAD") if File.directory?(".git")
31
+ Sentry.sys_command("git rev-parse HEAD") if File.directory?(".git")
32
32
  end
33
33
 
34
34
  def detect_release_from_env
35
- ENV['SENTRY_RELEASE']
35
+ ENV["SENTRY_RELEASE"]
36
36
  end
37
37
  end
38
38
  end
data/lib/sentry/scope.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "sentry/breadcrumb_buffer"
4
4
  require "sentry/propagation_context"
5
+ require "sentry/attachment"
5
6
  require "etc"
6
7
 
7
8
  module Sentry
@@ -9,8 +10,8 @@ module Sentry
9
10
  include ArgumentCheckingHelper
10
11
 
11
12
  ATTRIBUTES = [
12
- :transaction_names,
13
- :transaction_sources,
13
+ :transaction_name,
14
+ :transaction_source,
14
15
  :contexts,
15
16
  :extra,
16
17
  :tags,
@@ -22,6 +23,7 @@ module Sentry
22
23
  :rack_env,
23
24
  :span,
24
25
  :session,
26
+ :attachments,
25
27
  :propagation_context
26
28
  ]
27
29
 
@@ -44,25 +46,28 @@ module Sentry
44
46
  # @param hint [Hash] the hint data that'll be passed to event processors.
45
47
  # @return [Event]
46
48
  def apply_to_event(event, hint = nil)
47
- event.tags = tags.merge(event.tags)
48
- event.user = user.merge(event.user)
49
- event.extra = extra.merge(event.extra)
50
- event.contexts = contexts.merge(event.contexts)
51
- event.transaction = transaction_name if transaction_name
52
- event.transaction_info = { source: transaction_source } if transaction_source
49
+ unless event.is_a?(CheckInEvent)
50
+ event.tags = tags.merge(event.tags)
51
+ event.user = user.merge(event.user)
52
+ event.extra = extra.merge(event.extra)
53
+ event.contexts = contexts.merge(event.contexts)
54
+ event.transaction = transaction_name if transaction_name
55
+ event.transaction_info = { source: transaction_source } if transaction_source
56
+ event.fingerprint = fingerprint
57
+ event.level = level
58
+ event.breadcrumbs = breadcrumbs
59
+ event.rack_env = rack_env if rack_env
60
+ event.attachments = attachments
61
+ end
53
62
 
54
63
  if span
55
64
  event.contexts[:trace] ||= span.get_trace_context
65
+ event.dynamic_sampling_context ||= span.get_dynamic_sampling_context
56
66
  else
57
67
  event.contexts[:trace] ||= propagation_context.get_trace_context
58
68
  event.dynamic_sampling_context ||= propagation_context.get_dynamic_sampling_context
59
69
  end
60
70
 
61
- event.fingerprint = fingerprint
62
- event.level = level
63
- event.breadcrumbs = breadcrumbs
64
- event.rack_env = rack_env if rack_env
65
-
66
71
  all_event_processors = self.class.global_event_processors + @event_processors
67
72
 
68
73
  unless all_event_processors.empty?
@@ -95,12 +100,13 @@ module Sentry
95
100
  copy.extra = extra.deep_dup
96
101
  copy.tags = tags.deep_dup
97
102
  copy.user = user.deep_dup
98
- copy.transaction_names = transaction_names.dup
99
- copy.transaction_sources = transaction_sources.dup
103
+ copy.transaction_name = transaction_name.dup
104
+ copy.transaction_source = transaction_source.dup
100
105
  copy.fingerprint = fingerprint.deep_dup
101
106
  copy.span = span.deep_dup
102
107
  copy.session = session.deep_dup
103
108
  copy.propagation_context = propagation_context.deep_dup
109
+ copy.attachments = attachments.dup
104
110
  copy
105
111
  end
106
112
 
@@ -113,11 +119,12 @@ module Sentry
113
119
  self.extra = scope.extra
114
120
  self.tags = scope.tags
115
121
  self.user = scope.user
116
- self.transaction_names = scope.transaction_names
117
- self.transaction_sources = scope.transaction_sources
122
+ self.transaction_name = scope.transaction_name
123
+ self.transaction_source = scope.transaction_source
118
124
  self.fingerprint = scope.fingerprint
119
125
  self.span = scope.span
120
126
  self.propagation_context = scope.propagation_context
127
+ self.attachments = scope.attachments
121
128
  end
122
129
 
123
130
  # Updates the scope's data from the given options.
@@ -127,14 +134,17 @@ module Sentry
127
134
  # @param user [Hash]
128
135
  # @param level [String, Symbol]
129
136
  # @param fingerprint [Array]
130
- # @return [void]
137
+ # @param attachments [Array<Attachment>]
138
+ # @return [Array]
131
139
  def update_from_options(
132
140
  contexts: nil,
133
141
  extra: nil,
134
142
  tags: nil,
135
143
  user: nil,
136
144
  level: nil,
137
- fingerprint: nil
145
+ fingerprint: nil,
146
+ attachments: nil,
147
+ **options
138
148
  )
139
149
  self.contexts.merge!(contexts) if contexts
140
150
  self.extra.merge!(extra) if extra
@@ -142,6 +152,9 @@ module Sentry
142
152
  self.user = user if user
143
153
  self.level = level if level
144
154
  self.fingerprint = fingerprint if fingerprint
155
+
156
+ # Returns unsupported option keys so we can notify users.
157
+ options.keys
145
158
  end
146
159
 
147
160
  # Sets the scope's rack_env attribute.
@@ -226,8 +239,8 @@ module Sentry
226
239
  # @param transaction_name [String]
227
240
  # @return [void]
228
241
  def set_transaction_name(transaction_name, source: :custom)
229
- @transaction_names << transaction_name
230
- @transaction_sources << source
242
+ @transaction_name = transaction_name
243
+ @transaction_source = source
231
244
  end
232
245
 
233
246
  # Sets the currently active session on the scope.
@@ -237,18 +250,10 @@ module Sentry
237
250
  @session = session
238
251
  end
239
252
 
240
- # Returns current transaction name.
241
- # The "transaction" here does not refer to `Transaction` objects.
242
- # @return [String, nil]
243
- def transaction_name
244
- @transaction_names.last
245
- end
246
-
247
- # Returns current transaction source.
248
- # The "transaction" here does not refer to `Transaction` objects.
249
- # @return [String, nil]
250
- def transaction_source
251
- @transaction_sources.last
253
+ # These are high cardinality and thus bad.
254
+ # @return [Boolean]
255
+ def transaction_source_low_quality?
256
+ transaction_source == :url
252
257
  end
253
258
 
254
259
  # Returns the associated Transaction object.
@@ -286,6 +291,12 @@ module Sentry
286
291
  @propagation_context = PropagationContext.new(self, env)
287
292
  end
288
293
 
294
+ # Add a new attachment to the scope.
295
+ def add_attachment(**opts)
296
+ attachments << (attachment = Attachment.new(**opts))
297
+ attachment
298
+ end
299
+
289
300
  protected
290
301
 
291
302
  # for duplicating scopes internally
@@ -294,18 +305,19 @@ module Sentry
294
305
  private
295
306
 
296
307
  def set_default_value
297
- @contexts = { :os => self.class.os_context, :runtime => self.class.runtime_context }
308
+ @contexts = { os: self.class.os_context, runtime: self.class.runtime_context }
298
309
  @extra = {}
299
310
  @tags = {}
300
311
  @user = {}
301
312
  @level = :error
302
313
  @fingerprint = []
303
- @transaction_names = []
304
- @transaction_sources = []
314
+ @transaction_name = nil
315
+ @transaction_source = nil
305
316
  @event_processors = []
306
317
  @rack_env = {}
307
318
  @span = nil
308
319
  @session = nil
320
+ @attachments = []
309
321
  generate_propagation_context
310
322
  set_new_breadcrumb_buffer
311
323
  end
@@ -354,6 +366,5 @@ module Sentry
354
366
  global_event_processors << block
355
367
  end
356
368
  end
357
-
358
369
  end
359
370
  end
@@ -5,8 +5,8 @@ module Sentry
5
5
  attr_reader :started, :status, :aggregation_key
6
6
 
7
7
  # TODO-neel add :crashed after adding handled mechanism
8
- STATUSES = %i(ok errored exited)
9
- AGGREGATE_STATUSES = %i(errored exited)
8
+ STATUSES = %i[ok errored exited]
9
+ AGGREGATE_STATUSES = %i[errored exited]
10
10
 
11
11
  def initialize
12
12
  @started = Sentry.utc_now