sentry-ruby 5.16.1 → 5.20.1

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/README.md +20 -10
  4. data/Rakefile +1 -1
  5. data/bin/console +1 -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 +8 -6
  10. data/lib/sentry/baggage.rb +6 -6
  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 +53 -25
  15. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  16. data/lib/sentry/cron/monitor_check_ins.rb +1 -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.rb +19 -2
  21. data/lib/sentry/error_event.rb +2 -2
  22. data/lib/sentry/event.rb +20 -18
  23. data/lib/sentry/faraday.rb +77 -0
  24. data/lib/sentry/graphql.rb +9 -0
  25. data/lib/sentry/hub.rb +23 -3
  26. data/lib/sentry/integrable.rb +4 -0
  27. data/lib/sentry/interface.rb +1 -0
  28. data/lib/sentry/interfaces/exception.rb +5 -3
  29. data/lib/sentry/interfaces/mechanism.rb +20 -0
  30. data/lib/sentry/interfaces/request.rb +7 -7
  31. data/lib/sentry/interfaces/single_exception.rb +7 -5
  32. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  33. data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
  34. data/lib/sentry/logger.rb +1 -1
  35. data/lib/sentry/metrics/aggregator.rb +248 -0
  36. data/lib/sentry/metrics/configuration.rb +47 -0
  37. data/lib/sentry/metrics/counter_metric.rb +25 -0
  38. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  39. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  40. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  41. data/lib/sentry/metrics/metric.rb +19 -0
  42. data/lib/sentry/metrics/set_metric.rb +28 -0
  43. data/lib/sentry/metrics/timing.rb +43 -0
  44. data/lib/sentry/metrics.rb +56 -0
  45. data/lib/sentry/net/http.rb +18 -39
  46. data/lib/sentry/profiler.rb +19 -20
  47. data/lib/sentry/propagation_context.rb +10 -9
  48. data/lib/sentry/puma.rb +1 -1
  49. data/lib/sentry/rack/capture_exceptions.rb +15 -3
  50. data/lib/sentry/rack.rb +2 -2
  51. data/lib/sentry/rake.rb +4 -2
  52. data/lib/sentry/redis.rb +2 -1
  53. data/lib/sentry/release_detector.rb +4 -4
  54. data/lib/sentry/scope.rb +36 -26
  55. data/lib/sentry/session.rb +2 -2
  56. data/lib/sentry/session_flusher.rb +7 -39
  57. data/lib/sentry/span.rb +46 -5
  58. data/lib/sentry/test_helper.rb +3 -2
  59. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  60. data/lib/sentry/transaction.rb +17 -15
  61. data/lib/sentry/transaction_event.rb +6 -1
  62. data/lib/sentry/transport/configuration.rb +0 -1
  63. data/lib/sentry/transport/http_transport.rb +12 -12
  64. data/lib/sentry/transport.rb +18 -26
  65. data/lib/sentry/utils/argument_checking_helper.rb +6 -0
  66. data/lib/sentry/utils/env_helper.rb +21 -0
  67. data/lib/sentry/utils/http_tracing.rb +41 -0
  68. data/lib/sentry/utils/logging_helper.rb +0 -4
  69. data/lib/sentry/utils/real_ip.rb +2 -2
  70. data/lib/sentry/utils/request_id.rb +1 -1
  71. data/lib/sentry/version.rb +1 -1
  72. data/lib/sentry-ruby.rb +34 -3
  73. data/sentry-ruby-core.gemspec +1 -1
  74. data/sentry-ruby.gemspec +13 -6
  75. metadata +40 -7
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/metrics/metric"
4
+ require "sentry/metrics/counter_metric"
5
+ require "sentry/metrics/distribution_metric"
6
+ require "sentry/metrics/gauge_metric"
7
+ require "sentry/metrics/set_metric"
8
+ require "sentry/metrics/timing"
9
+ require "sentry/metrics/aggregator"
10
+
11
+ module Sentry
12
+ module Metrics
13
+ DURATION_UNITS = %w[nanosecond microsecond millisecond second minute hour day week]
14
+ INFORMATION_UNITS = %w[bit byte kilobyte kibibyte megabyte mebibyte gigabyte gibibyte terabyte tebibyte petabyte pebibyte exabyte exbibyte]
15
+ FRACTIONAL_UNITS = %w[ratio percent]
16
+
17
+ OP_NAME = "metric.timing"
18
+ SPAN_ORIGIN = "auto.metric.timing"
19
+
20
+ class << self
21
+ def increment(key, value = 1.0, unit: "none", tags: {}, timestamp: nil)
22
+ Sentry.metrics_aggregator&.add(:c, key, value, unit: unit, tags: tags, timestamp: timestamp)
23
+ end
24
+
25
+ def distribution(key, value, unit: "none", tags: {}, timestamp: nil)
26
+ Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
27
+ end
28
+
29
+ def set(key, value, unit: "none", tags: {}, timestamp: nil)
30
+ Sentry.metrics_aggregator&.add(:s, key, value, unit: unit, tags: tags, timestamp: timestamp)
31
+ end
32
+
33
+ def gauge(key, value, unit: "none", tags: {}, timestamp: nil)
34
+ Sentry.metrics_aggregator&.add(:g, key, value, unit: unit, tags: tags, timestamp: timestamp)
35
+ end
36
+
37
+ def timing(key, unit: "second", tags: {}, timestamp: nil, &block)
38
+ return unless block_given?
39
+ return yield unless DURATION_UNITS.include?(unit)
40
+
41
+ result, value = Sentry.with_child_span(op: OP_NAME, description: key, origin: SPAN_ORIGIN) do |span|
42
+ tags.each { |k, v| span.set_tag(k, v.is_a?(Array) ? v.join(", ") : v.to_s) } if span
43
+
44
+ start = Timing.send(unit.to_sym)
45
+ result = yield
46
+ value = Timing.send(unit.to_sym) - start
47
+
48
+ [result, value]
49
+ end
50
+
51
+ Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
52
+ result
53
+ end
54
+ end
55
+ end
56
+ end
@@ -2,12 +2,16 @@
2
2
 
3
3
  require "net/http"
4
4
  require "resolv"
5
+ require "sentry/utils/http_tracing"
5
6
 
6
7
  module Sentry
7
8
  # @api private
8
9
  module Net
9
10
  module HTTP
11
+ include Utils::HttpTracing
12
+
10
13
  OP_NAME = "http.client"
14
+ SPAN_ORIGIN = "auto.http.net_http"
11
15
  BREADCRUMB_CATEGORY = "net.http"
12
16
 
13
17
  # To explain how the entire thing works, we need to know how the original Net::HTTP#request works
@@ -20,8 +24,7 @@ module Sentry
20
24
  # req['connection'] ||= 'close'
21
25
  # return request(req, body, &block) # <- request will be called for the second time from the first call
22
26
  # }
23
- # end
24
- # # .....
27
+ # end # .....
25
28
  # end
26
29
  # ```
27
30
  #
@@ -30,47 +33,29 @@ module Sentry
30
33
  return super unless started? && Sentry.initialized?
31
34
  return super if from_sentry_sdk?
32
35
 
33
- Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f) do |sentry_span|
36
+ Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |sentry_span|
34
37
  request_info = extract_request_info(req)
35
38
 
36
- if propagate_trace?(request_info[:url], Sentry.configuration)
39
+ if propagate_trace?(request_info[:url])
37
40
  set_propagation_headers(req)
38
41
  end
39
42
 
40
- super.tap do |res|
41
- record_sentry_breadcrumb(request_info, res)
43
+ res = super
44
+ response_status = res.code.to_i
42
45
 
43
- if sentry_span
44
- sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
45
- sentry_span.set_data(Span::DataConventions::URL, request_info[:url])
46
- sentry_span.set_data(Span::DataConventions::HTTP_METHOD, request_info[:method])
47
- sentry_span.set_data(Span::DataConventions::HTTP_QUERY, request_info[:query]) if request_info[:query]
48
- sentry_span.set_data(Span::DataConventions::HTTP_STATUS_CODE, res.code.to_i)
49
- end
46
+ if record_sentry_breadcrumb?
47
+ record_sentry_breadcrumb(request_info, response_status)
50
48
  end
51
- end
52
- end
53
49
 
54
- private
50
+ if sentry_span
51
+ set_span_info(sentry_span, request_info, response_status)
52
+ end
55
53
 
56
- def set_propagation_headers(req)
57
- Sentry.get_trace_propagation_headers&.each { |k, v| req[k] = v }
54
+ res
55
+ end
58
56
  end
59
57
 
60
- def record_sentry_breadcrumb(request_info, res)
61
- return unless Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
62
-
63
- crumb = Sentry::Breadcrumb.new(
64
- level: :info,
65
- category: BREADCRUMB_CATEGORY,
66
- type: :info,
67
- data: {
68
- status: res.code.to_i,
69
- **request_info
70
- }
71
- )
72
- Sentry.add_breadcrumb(crumb)
73
- end
58
+ private
74
59
 
75
60
  def from_sentry_sdk?
76
61
  dsn = Sentry.configuration.dsn
@@ -81,7 +66,7 @@ module Sentry
81
66
  # IPv6 url could look like '::1/path', and that won't parse without
82
67
  # wrapping it in square brackets.
83
68
  hostname = address =~ Resolv::IPv6::Regex ? "[#{address}]" : address
84
- uri = req.uri || URI.parse("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}")
69
+ uri = req.uri || URI.parse(URI::DEFAULT_PARSER.escape("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}"))
85
70
  url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
86
71
 
87
72
  result = { method: req.method, url: url }
@@ -93,12 +78,6 @@ module Sentry
93
78
 
94
79
  result
95
80
  end
96
-
97
- def propagate_trace?(url, configuration)
98
- url &&
99
- configuration.propagate_traces &&
100
- configuration.trace_propagation_targets.any? { |target| url.match?(target) }
101
- end
102
81
  end
103
82
  end
104
83
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
3
+ require "securerandom"
4
4
 
5
5
  module Sentry
6
6
  class Profiler
7
- VERSION = '1'
8
- PLATFORM = 'ruby'
7
+ VERSION = "1"
8
+ PLATFORM = "ruby"
9
9
  # 101 Hz in microseconds
10
10
  DEFAULT_INTERVAL = 1e6 / 101
11
11
  MICRO_TO_NANO_SECONDS = 1e3
@@ -14,14 +14,14 @@ module Sentry
14
14
  attr_reader :sampled, :started, :event_id
15
15
 
16
16
  def initialize(configuration)
17
- @event_id = SecureRandom.uuid.delete('-')
17
+ @event_id = SecureRandom.uuid.delete("-")
18
18
  @started = false
19
19
  @sampled = nil
20
20
 
21
21
  @profiling_enabled = defined?(StackProf) && configuration.profiling_enabled?
22
22
  @profiles_sample_rate = configuration.profiles_sample_rate
23
23
  @project_root = configuration.project_root
24
- @app_dirs_pattern = configuration.app_dirs_pattern || Backtrace::APP_DIRS_PATTERN
24
+ @app_dirs_pattern = configuration.app_dirs_pattern
25
25
  @in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
26
26
  end
27
27
 
@@ -33,7 +33,7 @@ module Sentry
33
33
  raw: true,
34
34
  aggregate: false)
35
35
 
36
- @started ? log('Started') : log('Not started since running elsewhere')
36
+ @started ? log("Started") : log("Not started since running elsewhere")
37
37
  end
38
38
 
39
39
  def stop
@@ -41,7 +41,7 @@ module Sentry
41
41
  return unless @started
42
42
 
43
43
  StackProf.stop
44
- log('Stopped')
44
+ log("Stopped")
45
45
  end
46
46
 
47
47
  # Sets initial sampling decision of the profile.
@@ -54,14 +54,14 @@ module Sentry
54
54
 
55
55
  unless transaction_sampled
56
56
  @sampled = false
57
- log('Discarding profile because transaction not sampled')
57
+ log("Discarding profile because transaction not sampled")
58
58
  return
59
59
  end
60
60
 
61
61
  case @profiles_sample_rate
62
62
  when 0.0
63
63
  @sampled = false
64
- log('Discarding profile because sample_rate is 0')
64
+ log("Discarding profile because sample_rate is 0")
65
65
  return
66
66
  when 1.0
67
67
  @sampled = true
@@ -70,7 +70,7 @@ module Sentry
70
70
  @sampled = Random.rand < @profiles_sample_rate
71
71
  end
72
72
 
73
- log('Discarding profile due to sampling decision') unless @sampled
73
+ log("Discarding profile due to sampling decision") unless @sampled
74
74
  end
75
75
 
76
76
  def to_hash
@@ -90,13 +90,12 @@ module Sentry
90
90
 
91
91
  frame_map = {}
92
92
 
93
- frames = results[:frames].to_enum.with_index.map do |frame, idx|
94
- frame_id, frame_data = frame
95
-
93
+ frames = results[:frames].map.with_index do |(frame_id, frame_data), idx|
96
94
  # need to map over stackprof frame ids to ours
97
95
  frame_map[frame_id] = idx
98
96
 
99
97
  file_path = frame_data[:file]
98
+ lineno = frame_data[:line]
100
99
  in_app = in_app?(file_path)
101
100
  filename = compute_filename(file_path, in_app)
102
101
  function, mod = split_module(frame_data[:name])
@@ -109,7 +108,7 @@ module Sentry
109
108
  }
110
109
 
111
110
  frame_hash[:module] = mod if mod
112
- frame_hash[:lineno] = frame_data[:line] if frame_data[:line] && frame_data[:line] >= 0
111
+ frame_hash[:lineno] = lineno if lineno && lineno >= 0
113
112
 
114
113
  frame_hash
115
114
  end
@@ -130,7 +129,7 @@ module Sentry
130
129
  num_seen << results[:raw][idx + len]
131
130
  idx += len + 1
132
131
 
133
- log('Unknown frame in stack') if stack.size != len
132
+ log("Unknown frame in stack") if stack.size != len
134
133
  end
135
134
 
136
135
  idx = 0
@@ -155,16 +154,16 @@ module Sentry
155
154
  # Till then, on multi-threaded servers like puma, we will get frames from other active threads when the one
156
155
  # we're profiling is idle/sleeping/waiting for IO etc.
157
156
  # https://bugs.ruby-lang.org/issues/10602
158
- thread_id: '0',
157
+ thread_id: "0",
159
158
  elapsed_since_start_ns: elapsed_since_start_ns.to_s
160
159
  }
161
160
  end
162
161
  end
163
162
 
164
- log('Some samples thrown away') if samples.size != results[:samples]
163
+ log("Some samples thrown away") if samples.size != results[:samples]
165
164
 
166
165
  if samples.size <= MIN_SAMPLES_REQUIRED
167
- log('Not enough samples, discarding profiler')
166
+ log("Not enough samples, discarding profiler")
168
167
  record_lost_event(:insufficient_data)
169
168
  return {}
170
169
  end
@@ -219,7 +218,7 @@ module Sentry
219
218
 
220
219
  def split_module(name)
221
220
  # last module plus class/instance method
222
- i = name.rindex('::')
221
+ i = name.rindex("::")
223
222
  function = i ? name[(i + 2)..-1] : name
224
223
  mod = i ? name[0...i] : nil
225
224
 
@@ -227,7 +226,7 @@ module Sentry
227
226
  end
228
227
 
229
228
  def record_lost_event(reason)
230
- Sentry.get_current_client&.transport&.record_lost_event(reason, 'profile')
229
+ Sentry.get_current_client&.transport&.record_lost_event(reason, "profile")
231
230
  end
232
231
  end
233
232
  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,7 +50,7 @@ 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
@@ -56,13 +58,19 @@ module Sentry
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
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
@@ -32,7 +32,7 @@ module Sentry
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
 
@@ -55,10 +57,12 @@ module Sentry
55
57
  event.level = level
56
58
  event.breadcrumbs = breadcrumbs
57
59
  event.rack_env = rack_env if rack_env
60
+ event.attachments = attachments
58
61
  end
59
62
 
60
63
  if span
61
64
  event.contexts[:trace] ||= span.get_trace_context
65
+ event.dynamic_sampling_context ||= span.get_dynamic_sampling_context
62
66
  else
63
67
  event.contexts[:trace] ||= propagation_context.get_trace_context
64
68
  event.dynamic_sampling_context ||= propagation_context.get_dynamic_sampling_context
@@ -96,12 +100,13 @@ module Sentry
96
100
  copy.extra = extra.deep_dup
97
101
  copy.tags = tags.deep_dup
98
102
  copy.user = user.deep_dup
99
- copy.transaction_names = transaction_names.dup
100
- copy.transaction_sources = transaction_sources.dup
103
+ copy.transaction_name = transaction_name.dup
104
+ copy.transaction_source = transaction_source.dup
101
105
  copy.fingerprint = fingerprint.deep_dup
102
106
  copy.span = span.deep_dup
103
107
  copy.session = session.deep_dup
104
108
  copy.propagation_context = propagation_context.deep_dup
109
+ copy.attachments = attachments.dup
105
110
  copy
106
111
  end
107
112
 
@@ -114,11 +119,12 @@ module Sentry
114
119
  self.extra = scope.extra
115
120
  self.tags = scope.tags
116
121
  self.user = scope.user
117
- self.transaction_names = scope.transaction_names
118
- self.transaction_sources = scope.transaction_sources
122
+ self.transaction_name = scope.transaction_name
123
+ self.transaction_source = scope.transaction_source
119
124
  self.fingerprint = scope.fingerprint
120
125
  self.span = scope.span
121
126
  self.propagation_context = scope.propagation_context
127
+ self.attachments = scope.attachments
122
128
  end
123
129
 
124
130
  # Updates the scope's data from the given options.
@@ -128,14 +134,17 @@ module Sentry
128
134
  # @param user [Hash]
129
135
  # @param level [String, Symbol]
130
136
  # @param fingerprint [Array]
131
- # @return [void]
137
+ # @param attachments [Array<Attachment>]
138
+ # @return [Array]
132
139
  def update_from_options(
133
140
  contexts: nil,
134
141
  extra: nil,
135
142
  tags: nil,
136
143
  user: nil,
137
144
  level: nil,
138
- fingerprint: nil
145
+ fingerprint: nil,
146
+ attachments: nil,
147
+ **options
139
148
  )
140
149
  self.contexts.merge!(contexts) if contexts
141
150
  self.extra.merge!(extra) if extra
@@ -143,6 +152,9 @@ module Sentry
143
152
  self.user = user if user
144
153
  self.level = level if level
145
154
  self.fingerprint = fingerprint if fingerprint
155
+
156
+ # Returns unsupported option keys so we can notify users.
157
+ options.keys
146
158
  end
147
159
 
148
160
  # Sets the scope's rack_env attribute.
@@ -227,8 +239,8 @@ module Sentry
227
239
  # @param transaction_name [String]
228
240
  # @return [void]
229
241
  def set_transaction_name(transaction_name, source: :custom)
230
- @transaction_names << transaction_name
231
- @transaction_sources << source
242
+ @transaction_name = transaction_name
243
+ @transaction_source = source
232
244
  end
233
245
 
234
246
  # Sets the currently active session on the scope.
@@ -238,18 +250,10 @@ module Sentry
238
250
  @session = session
239
251
  end
240
252
 
241
- # Returns current transaction name.
242
- # The "transaction" here does not refer to `Transaction` objects.
243
- # @return [String, nil]
244
- def transaction_name
245
- @transaction_names.last
246
- end
247
-
248
- # Returns current transaction source.
249
- # The "transaction" here does not refer to `Transaction` objects.
250
- # @return [String, nil]
251
- def transaction_source
252
- @transaction_sources.last
253
+ # These are high cardinality and thus bad.
254
+ # @return [Boolean]
255
+ def transaction_source_low_quality?
256
+ transaction_source == :url
253
257
  end
254
258
 
255
259
  # Returns the associated Transaction object.
@@ -287,6 +291,12 @@ module Sentry
287
291
  @propagation_context = PropagationContext.new(self, env)
288
292
  end
289
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
+
290
300
  protected
291
301
 
292
302
  # for duplicating scopes internally
@@ -295,18 +305,19 @@ module Sentry
295
305
  private
296
306
 
297
307
  def set_default_value
298
- @contexts = { :os => self.class.os_context, :runtime => self.class.runtime_context }
308
+ @contexts = { os: self.class.os_context, runtime: self.class.runtime_context }
299
309
  @extra = {}
300
310
  @tags = {}
301
311
  @user = {}
302
312
  @level = :error
303
313
  @fingerprint = []
304
- @transaction_names = []
305
- @transaction_sources = []
314
+ @transaction_name = nil
315
+ @transaction_source = nil
306
316
  @event_processors = []
307
317
  @rack_env = {}
308
318
  @span = nil
309
319
  @session = nil
320
+ @attachments = []
310
321
  generate_propagation_context
311
322
  set_new_breadcrumb_buffer
312
323
  end
@@ -355,6 +366,5 @@ module Sentry
355
366
  global_event_processors << block
356
367
  end
357
368
  end
358
-
359
369
  end
360
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