sentry-ruby 5.10.0 → 5.17.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -13
  3. data/README.md +10 -10
  4. data/Rakefile +1 -1
  5. data/lib/sentry/background_worker.rb +9 -2
  6. data/lib/sentry/backpressure_monitor.rb +75 -0
  7. data/lib/sentry/backtrace.rb +7 -3
  8. data/lib/sentry/breadcrumb.rb +8 -2
  9. data/lib/sentry/check_in_event.rb +60 -0
  10. data/lib/sentry/client.rb +88 -17
  11. data/lib/sentry/configuration.rb +66 -12
  12. data/lib/sentry/cron/configuration.rb +23 -0
  13. data/lib/sentry/cron/monitor_check_ins.rb +75 -0
  14. data/lib/sentry/cron/monitor_config.rb +53 -0
  15. data/lib/sentry/cron/monitor_schedule.rb +42 -0
  16. data/lib/sentry/dsn.rb +1 -1
  17. data/lib/sentry/envelope.rb +19 -2
  18. data/lib/sentry/error_event.rb +2 -2
  19. data/lib/sentry/event.rb +14 -36
  20. data/lib/sentry/hub.rb +70 -2
  21. data/lib/sentry/integrable.rb +10 -0
  22. data/lib/sentry/interface.rb +1 -0
  23. data/lib/sentry/interfaces/exception.rb +5 -3
  24. data/lib/sentry/interfaces/mechanism.rb +20 -0
  25. data/lib/sentry/interfaces/request.rb +2 -2
  26. data/lib/sentry/interfaces/single_exception.rb +10 -6
  27. data/lib/sentry/interfaces/stacktrace_builder.rb +8 -0
  28. data/lib/sentry/metrics/aggregator.rb +276 -0
  29. data/lib/sentry/metrics/configuration.rb +47 -0
  30. data/lib/sentry/metrics/counter_metric.rb +25 -0
  31. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  32. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  33. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  34. data/lib/sentry/metrics/metric.rb +19 -0
  35. data/lib/sentry/metrics/set_metric.rb +28 -0
  36. data/lib/sentry/metrics/timing.rb +43 -0
  37. data/lib/sentry/metrics.rb +55 -0
  38. data/lib/sentry/net/http.rb +25 -22
  39. data/lib/sentry/profiler.rb +18 -7
  40. data/lib/sentry/propagation_context.rb +135 -0
  41. data/lib/sentry/puma.rb +12 -5
  42. data/lib/sentry/rack/capture_exceptions.rb +7 -5
  43. data/lib/sentry/rake.rb +3 -14
  44. data/lib/sentry/redis.rb +8 -3
  45. data/lib/sentry/release_detector.rb +1 -1
  46. data/lib/sentry/scope.rb +36 -15
  47. data/lib/sentry/session.rb +2 -2
  48. data/lib/sentry/session_flusher.rb +1 -6
  49. data/lib/sentry/span.rb +54 -3
  50. data/lib/sentry/test_helper.rb +18 -12
  51. data/lib/sentry/transaction.rb +33 -33
  52. data/lib/sentry/transaction_event.rb +5 -3
  53. data/lib/sentry/transport/configuration.rb +73 -1
  54. data/lib/sentry/transport/http_transport.rb +68 -37
  55. data/lib/sentry/transport/spotlight_transport.rb +50 -0
  56. data/lib/sentry/transport.rb +27 -37
  57. data/lib/sentry/utils/argument_checking_helper.rb +12 -0
  58. data/lib/sentry/utils/real_ip.rb +1 -1
  59. data/lib/sentry/utils/request_id.rb +1 -1
  60. data/lib/sentry/version.rb +1 -1
  61. data/lib/sentry-ruby.rb +96 -26
  62. data/sentry-ruby.gemspec +1 -0
  63. metadata +35 -2
@@ -0,0 +1,55 @@
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
+
19
+ class << self
20
+ def increment(key, value = 1.0, unit: 'none', tags: {}, timestamp: nil)
21
+ Sentry.metrics_aggregator&.add(:c, key, value, unit: unit, tags: tags, timestamp: timestamp)
22
+ end
23
+
24
+ def distribution(key, value, unit: 'none', tags: {}, timestamp: nil)
25
+ Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
26
+ end
27
+
28
+ def set(key, value, unit: 'none', tags: {}, timestamp: nil)
29
+ Sentry.metrics_aggregator&.add(:s, key, value, unit: unit, tags: tags, timestamp: timestamp)
30
+ end
31
+
32
+ def gauge(key, value, unit: 'none', tags: {}, timestamp: nil)
33
+ Sentry.metrics_aggregator&.add(:g, key, value, unit: unit, tags: tags, timestamp: timestamp)
34
+ end
35
+
36
+ def timing(key, unit: 'second', tags: {}, timestamp: nil, &block)
37
+ return unless block_given?
38
+ return yield unless DURATION_UNITS.include?(unit)
39
+
40
+ result, value = Sentry.with_child_span(op: OP_NAME, description: key) do |span|
41
+ tags.each { |k, v| span.set_tag(k, v.is_a?(Array) ? v.join(', ') : v.to_s) } if span
42
+
43
+ start = Timing.send(unit.to_sym)
44
+ result = yield
45
+ value = Timing.send(unit.to_sym) - start
46
+
47
+ [result, value]
48
+ end
49
+
50
+ Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
51
+ result
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "net/http"
4
+ require "resolv"
4
5
 
5
6
  module Sentry
6
7
  # @api private
@@ -30,18 +31,21 @@ module Sentry
30
31
  return super if from_sentry_sdk?
31
32
 
32
33
  Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f) do |sentry_span|
33
- set_sentry_trace_header(req, sentry_span)
34
+ request_info = extract_request_info(req)
35
+
36
+ if propagate_trace?(request_info[:url], Sentry.configuration)
37
+ set_propagation_headers(req)
38
+ end
34
39
 
35
40
  super.tap do |res|
36
- record_sentry_breadcrumb(req, res)
41
+ record_sentry_breadcrumb(request_info, res)
37
42
 
38
43
  if sentry_span
39
- request_info = extract_request_info(req)
40
44
  sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
41
- sentry_span.set_data('url', request_info[:url])
42
- sentry_span.set_data('http.method', request_info[:method])
43
- sentry_span.set_data('http.query', request_info[:query]) if request_info[:query]
44
- sentry_span.set_data('status', res.code.to_i)
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)
45
49
  end
46
50
  end
47
51
  end
@@ -49,23 +53,13 @@ module Sentry
49
53
 
50
54
  private
51
55
 
52
- def set_sentry_trace_header(req, sentry_span)
53
- return unless sentry_span
54
-
55
- client = Sentry.get_current_client
56
-
57
- trace = client.generate_sentry_trace(sentry_span)
58
- req[SENTRY_TRACE_HEADER_NAME] = trace if trace
59
-
60
- baggage = client.generate_baggage(sentry_span)
61
- req[BAGGAGE_HEADER_NAME] = baggage if baggage && !baggage.empty?
56
+ def set_propagation_headers(req)
57
+ Sentry.get_trace_propagation_headers&.each { |k, v| req[k] = v }
62
58
  end
63
59
 
64
- def record_sentry_breadcrumb(req, res)
60
+ def record_sentry_breadcrumb(request_info, res)
65
61
  return unless Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
66
62
 
67
- request_info = extract_request_info(req)
68
-
69
63
  crumb = Sentry::Breadcrumb.new(
70
64
  level: :info,
71
65
  category: BREADCRUMB_CATEGORY,
@@ -84,7 +78,10 @@ module Sentry
84
78
  end
85
79
 
86
80
  def extract_request_info(req)
87
- uri = req.uri || URI.parse("#{use_ssl? ? 'https' : 'http'}://#{address}#{req.path}")
81
+ # IPv6 url could look like '::1/path', and that won't parse without
82
+ # wrapping it in square brackets.
83
+ hostname = address =~ Resolv::IPv6::Regex ? "[#{address}]" : address
84
+ uri = req.uri || URI.parse("#{use_ssl? ? 'https' : 'http'}://#{hostname}#{req.path}")
88
85
  url = "#{uri.scheme}://#{uri.host}#{uri.path}" rescue uri.to_s
89
86
 
90
87
  result = { method: req.method, url: url }
@@ -96,8 +93,14 @@ module Sentry
96
93
 
97
94
  result
98
95
  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
99
102
  end
100
103
  end
101
104
  end
102
105
 
103
- Sentry.register_patch(Sentry::Net::HTTP, Net::HTTP)
106
+ Sentry.register_patch(:http, Sentry::Net::HTTP, Net::HTTP)
@@ -9,6 +9,7 @@ module Sentry
9
9
  # 101 Hz in microseconds
10
10
  DEFAULT_INTERVAL = 1e6 / 101
11
11
  MICRO_TO_NANO_SECONDS = 1e3
12
+ MIN_SAMPLES_REQUIRED = 2
12
13
 
13
14
  attr_reader :sampled, :started, :event_id
14
15
 
@@ -73,14 +74,19 @@ module Sentry
73
74
  end
74
75
 
75
76
  def to_hash
76
- return {} unless @sampled
77
+ unless @sampled
78
+ record_lost_event(:sample_rate)
79
+ return {}
80
+ end
81
+
77
82
  return {} unless @started
78
83
 
79
84
  results = StackProf.results
80
- return {} unless results
81
- return {} if results.empty?
82
- return {} if results[:samples] == 0
83
- return {} unless results[:raw]
85
+
86
+ if !results || results.empty? || results[:samples] == 0 || !results[:raw]
87
+ record_lost_event(:insufficient_data)
88
+ return {}
89
+ end
84
90
 
85
91
  frame_map = {}
86
92
 
@@ -103,7 +109,7 @@ module Sentry
103
109
  }
104
110
 
105
111
  frame_hash[:module] = mod if mod
106
- frame_hash[:lineno] = frame_data[:line] if frame_data[:line]
112
+ frame_hash[:lineno] = frame_data[:line] if frame_data[:line] && frame_data[:line] >= 0
107
113
 
108
114
  frame_hash
109
115
  end
@@ -157,8 +163,9 @@ module Sentry
157
163
 
158
164
  log('Some samples thrown away') if samples.size != results[:samples]
159
165
 
160
- if samples.size <= 2
166
+ if samples.size <= MIN_SAMPLES_REQUIRED
161
167
  log('Not enough samples, discarding profiler')
168
+ record_lost_event(:insufficient_data)
162
169
  return {}
163
170
  end
164
171
 
@@ -218,5 +225,9 @@ module Sentry
218
225
 
219
226
  [function, mod]
220
227
  end
228
+
229
+ def record_lost_event(reason)
230
+ Sentry.get_current_client&.transport&.record_lost_event(reason, 'profile')
231
+ end
221
232
  end
222
233
  end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "sentry/baggage"
5
+
6
+ module Sentry
7
+ class PropagationContext
8
+ SENTRY_TRACE_REGEXP = Regexp.new(
9
+ "^[ \t]*" + # whitespace
10
+ "([0-9a-f]{32})?" + # trace_id
11
+ "-?([0-9a-f]{16})?" + # span_id
12
+ "-?([01])?" + # sampled
13
+ "[ \t]*$" # whitespace
14
+ )
15
+
16
+ # An uuid that can be used to identify a trace.
17
+ # @return [String]
18
+ attr_reader :trace_id
19
+ # An uuid that can be used to identify the span.
20
+ # @return [String]
21
+ attr_reader :span_id
22
+ # Span parent's span_id.
23
+ # @return [String, nil]
24
+ attr_reader :parent_span_id
25
+ # The sampling decision of the parent transaction.
26
+ # @return [Boolean, nil]
27
+ attr_reader :parent_sampled
28
+ # Is there an incoming trace or not?
29
+ # @return [Boolean]
30
+ attr_reader :incoming_trace
31
+ # This is only for accessing the current baggage variable.
32
+ # Please use the #get_baggage method for interfacing outside this class.
33
+ # @return [Baggage, nil]
34
+ attr_reader :baggage
35
+
36
+ def initialize(scope, env = nil)
37
+ @scope = scope
38
+ @parent_span_id = nil
39
+ @parent_sampled = nil
40
+ @baggage = nil
41
+ @incoming_trace = false
42
+
43
+ if env
44
+ sentry_trace_header = env["HTTP_SENTRY_TRACE"] || env[SENTRY_TRACE_HEADER_NAME]
45
+ baggage_header = env["HTTP_BAGGAGE"] || env[BAGGAGE_HEADER_NAME]
46
+
47
+ if sentry_trace_header
48
+ sentry_trace_data = self.class.extract_sentry_trace(sentry_trace_header)
49
+
50
+ if sentry_trace_data
51
+ @trace_id, @parent_span_id, @parent_sampled = sentry_trace_data
52
+
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
62
+
63
+ @baggage.freeze!
64
+ @incoming_trace = true
65
+ end
66
+ end
67
+ end
68
+
69
+ @trace_id ||= SecureRandom.uuid.delete("-")
70
+ @span_id = SecureRandom.uuid.delete("-").slice(0, 16)
71
+ end
72
+
73
+ # Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
74
+ #
75
+ # @param sentry_trace [String] the sentry-trace header value from the previous transaction.
76
+ # @return [Array, nil]
77
+ def self.extract_sentry_trace(sentry_trace)
78
+ match = SENTRY_TRACE_REGEXP.match(sentry_trace)
79
+ return nil if match.nil?
80
+
81
+ trace_id, parent_span_id, sampled_flag = match[1..3]
82
+ parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
83
+
84
+ [trace_id, parent_span_id, parent_sampled]
85
+ end
86
+
87
+ # Returns the trace context that can be used to embed in an Event.
88
+ # @return [Hash]
89
+ def get_trace_context
90
+ {
91
+ trace_id: trace_id,
92
+ span_id: span_id,
93
+ parent_span_id: parent_span_id
94
+ }
95
+ end
96
+
97
+ # Returns the sentry-trace header from the propagation context.
98
+ # @return [String]
99
+ def get_traceparent
100
+ "#{trace_id}-#{span_id}"
101
+ end
102
+
103
+ # Returns the Baggage from the propagation context or populates as head SDK if empty.
104
+ # @return [Baggage, nil]
105
+ def get_baggage
106
+ populate_head_baggage if @baggage.nil? || @baggage.mutable
107
+ @baggage
108
+ end
109
+
110
+ # Returns the Dynamic Sampling Context from the baggage.
111
+ # @return [String, nil]
112
+ def get_dynamic_sampling_context
113
+ get_baggage&.dynamic_sampling_context
114
+ end
115
+
116
+ private
117
+
118
+ def populate_head_baggage
119
+ return unless Sentry.initialized?
120
+
121
+ configuration = Sentry.configuration
122
+
123
+ items = {
124
+ "trace_id" => trace_id,
125
+ "environment" => configuration.environment,
126
+ "release" => configuration.release,
127
+ "public_key" => configuration.dsn&.public_key,
128
+ "user_segment" => @scope.user && @scope.user["segment"]
129
+ }
130
+
131
+ items.compact!
132
+ @baggage = Baggage.new(items, mutable: false)
133
+ end
134
+ end
135
+ end
data/lib/sentry/puma.rb CHANGED
@@ -1,10 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ return unless defined?(Puma::Server)
4
+
3
5
  module Sentry
4
6
  module Puma
5
7
  module Server
6
- def lowlevel_error(e, env, status=500)
7
- result = super
8
+ PUMA_4_AND_PRIOR = Gem::Version.new(::Puma::Const::PUMA_VERSION) < Gem::Version.new("5.0.0")
9
+
10
+ def lowlevel_error(e, env, status = 500)
11
+ result =
12
+ if PUMA_4_AND_PRIOR
13
+ super(e, env)
14
+ else
15
+ super
16
+ end
8
17
 
9
18
  begin
10
19
  Sentry.capture_exception(e) do |scope|
@@ -20,6 +29,4 @@ module Sentry
20
29
  end
21
30
  end
22
31
 
23
- if defined?(Puma::Server)
24
- Sentry.register_patch(Sentry::Puma::Server, Puma::Server)
25
- end
32
+ Sentry.register_patch(:puma, Sentry::Puma::Server, Puma::Server)
@@ -4,6 +4,7 @@ module Sentry
4
4
  module Rack
5
5
  class CaptureExceptions
6
6
  ERROR_EVENT_ID_KEY = "sentry.error_event_id"
7
+ MECHANISM_TYPE = "rack"
7
8
 
8
9
  def initialize(app)
9
10
  @app = app
@@ -56,17 +57,14 @@ module Sentry
56
57
  end
57
58
 
58
59
  def capture_exception(exception, env)
59
- Sentry.capture_exception(exception).tap do |event|
60
+ Sentry.capture_exception(exception, hint: { mechanism: mechanism }).tap do |event|
60
61
  env[ERROR_EVENT_ID_KEY] = event.event_id if event
61
62
  end
62
63
  end
63
64
 
64
65
  def start_transaction(env, scope)
65
- sentry_trace = env["HTTP_SENTRY_TRACE"]
66
- baggage = env["HTTP_BAGGAGE"]
67
-
68
66
  options = { name: scope.transaction_name, source: scope.transaction_source, op: transaction_op }
69
- transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, baggage: baggage, **options) if sentry_trace
67
+ transaction = Sentry.continue_trace(env, **options)
70
68
  Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
71
69
  end
72
70
 
@@ -77,6 +75,10 @@ module Sentry
77
75
  transaction.set_http_status(status_code)
78
76
  transaction.finish
79
77
  end
78
+
79
+ def mechanism
80
+ Sentry::Mechanism.new(type: MECHANISM_TYPE, handled: false)
81
+ end
80
82
  end
81
83
  end
82
84
  end
data/lib/sentry/rake.rb CHANGED
@@ -8,7 +8,9 @@ module Sentry
8
8
  module Application
9
9
  # @api private
10
10
  def display_error_message(ex)
11
- Sentry.capture_exception(ex) do |scope|
11
+ mechanism = Sentry::Mechanism.new(type: 'rake', handled: false)
12
+
13
+ Sentry.capture_exception(ex, hint: { mechanism: mechanism }) do |scope|
12
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)
@@ -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
@@ -19,7 +19,10 @@ module Sentry
19
19
 
20
20
  if span
21
21
  span.set_description(commands_description)
22
- span.set_data(:server, server_description)
22
+ span.set_data(Span::DataConventions::DB_SYSTEM, "redis")
23
+ span.set_data(Span::DataConventions::DB_NAME, db)
24
+ span.set_data(Span::DataConventions::SERVER_ADDRESS, host)
25
+ span.set_data(Span::DataConventions::SERVER_PORT, port)
23
26
  end
24
27
  end
25
28
  end
@@ -96,8 +99,10 @@ end
96
99
 
97
100
  if defined?(::Redis::Client)
98
101
  if Gem::Version.new(::Redis::VERSION) < Gem::Version.new("5.0")
99
- Sentry.register_patch(Sentry::Redis::OldClientPatch, ::Redis::Client)
102
+ Sentry.register_patch(:redis, Sentry::Redis::OldClientPatch, ::Redis::Client)
100
103
  elsif defined?(RedisClient)
101
- RedisClient.register(Sentry::Redis::GlobalRedisInstrumentation)
104
+ Sentry.register_patch(:redis) do
105
+ RedisClient.register(Sentry::Redis::GlobalRedisInstrumentation)
106
+ end
102
107
  end
103
108
  end
@@ -28,7 +28,7 @@ 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
data/lib/sentry/scope.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "sentry/breadcrumb_buffer"
4
+ require "sentry/propagation_context"
4
5
  require "etc"
5
6
 
6
7
  module Sentry
@@ -20,7 +21,8 @@ module Sentry
20
21
  :event_processors,
21
22
  :rack_env,
22
23
  :span,
23
- :session
24
+ :session,
25
+ :propagation_context
24
26
  ]
25
27
 
26
28
  attr_reader(*ATTRIBUTES)
@@ -42,22 +44,26 @@ module Sentry
42
44
  # @param hint [Hash] the hint data that'll be passed to event processors.
43
45
  # @return [Event]
44
46
  def apply_to_event(event, hint = nil)
45
- event.tags = tags.merge(event.tags)
46
- event.user = user.merge(event.user)
47
- event.extra = extra.merge(event.extra)
48
- event.contexts = contexts.merge(event.contexts)
49
- event.transaction = transaction_name if transaction_name
50
- event.transaction_info = { source: transaction_source } if transaction_source
47
+ unless event.is_a?(CheckInEvent)
48
+ event.tags = tags.merge(event.tags)
49
+ event.user = user.merge(event.user)
50
+ event.extra = extra.merge(event.extra)
51
+ event.contexts = contexts.merge(event.contexts)
52
+ event.transaction = transaction_name if transaction_name
53
+ event.transaction_info = { source: transaction_source } if transaction_source
54
+ event.fingerprint = fingerprint
55
+ event.level = level
56
+ event.breadcrumbs = breadcrumbs
57
+ event.rack_env = rack_env if rack_env
58
+ end
51
59
 
52
60
  if span
53
- event.contexts[:trace] = span.get_trace_context
61
+ event.contexts[:trace] ||= span.get_trace_context
62
+ else
63
+ event.contexts[:trace] ||= propagation_context.get_trace_context
64
+ event.dynamic_sampling_context ||= propagation_context.get_dynamic_sampling_context
54
65
  end
55
66
 
56
- event.fingerprint = fingerprint
57
- event.level = level
58
- event.breadcrumbs = breadcrumbs
59
- event.rack_env = rack_env if rack_env
60
-
61
67
  all_event_processors = self.class.global_event_processors + @event_processors
62
68
 
63
69
  unless all_event_processors.empty?
@@ -95,6 +101,7 @@ module Sentry
95
101
  copy.fingerprint = fingerprint.deep_dup
96
102
  copy.span = span.deep_dup
97
103
  copy.session = session.deep_dup
104
+ copy.propagation_context = propagation_context.deep_dup
98
105
  copy
99
106
  end
100
107
 
@@ -111,6 +118,7 @@ module Sentry
111
118
  self.transaction_sources = scope.transaction_sources
112
119
  self.fingerprint = scope.fingerprint
113
120
  self.span = scope.span
121
+ self.propagation_context = scope.propagation_context
114
122
  end
115
123
 
116
124
  # Updates the scope's data from the given options.
@@ -244,6 +252,12 @@ module Sentry
244
252
  @transaction_sources.last
245
253
  end
246
254
 
255
+ # These are high cardinality and thus bad.
256
+ # @return [Boolean]
257
+ def transaction_source_low_quality?
258
+ transaction_source == :url
259
+ end
260
+
247
261
  # Returns the associated Transaction object.
248
262
  # @return [Transaction, nil]
249
263
  def get_transaction
@@ -272,6 +286,13 @@ module Sentry
272
286
  @event_processors << block
273
287
  end
274
288
 
289
+ # Generate a new propagation context either from the incoming env headers or from scratch.
290
+ # @param env [Hash, nil]
291
+ # @return [void]
292
+ def generate_propagation_context(env = nil)
293
+ @propagation_context = PropagationContext.new(self, env)
294
+ end
295
+
275
296
  protected
276
297
 
277
298
  # for duplicating scopes internally
@@ -280,7 +301,7 @@ module Sentry
280
301
  private
281
302
 
282
303
  def set_default_value
283
- @contexts = { :os => self.class.os_context, :runtime => self.class.runtime_context }
304
+ @contexts = { os: self.class.os_context, runtime: self.class.runtime_context }
284
305
  @extra = {}
285
306
  @tags = {}
286
307
  @user = {}
@@ -292,6 +313,7 @@ module Sentry
292
313
  @rack_env = {}
293
314
  @span = nil
294
315
  @session = nil
316
+ generate_propagation_context
295
317
  set_new_breadcrumb_buffer
296
318
  end
297
319
 
@@ -339,6 +361,5 @@ module Sentry
339
361
  global_event_processors << block
340
362
  end
341
363
  end
342
-
343
364
  end
344
365
  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
@@ -20,12 +20,8 @@ module Sentry
20
20
 
21
21
  def flush
22
22
  return if @pending_aggregates.empty?
23
- envelope = pending_envelope
24
-
25
- Sentry.background_worker.perform do
26
- @client.transport.send_envelope(envelope)
27
- end
28
23
 
24
+ @client.capture_envelope(pending_envelope)
29
25
  @pending_aggregates = {}
30
26
  end
31
27
 
@@ -85,6 +81,5 @@ module Sentry
85
81
  end
86
82
  end
87
83
  end
88
-
89
84
  end
90
85
  end