sentry-ruby 5.16.1 → 5.20.1

Sign up to get free protection for your applications and to get access to all the features.
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