sentry-ruby 5.18.2 → 5.28.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -1
  3. data/Gemfile +9 -3
  4. data/README.md +12 -7
  5. data/Rakefile +9 -11
  6. data/bin/console +1 -0
  7. data/lib/sentry/attachment.rb +40 -0
  8. data/lib/sentry/background_worker.rb +2 -3
  9. data/lib/sentry/backpressure_monitor.rb +1 -1
  10. data/lib/sentry/backtrace.rb +7 -8
  11. data/lib/sentry/baggage.rb +7 -7
  12. data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
  13. data/lib/sentry/breadcrumb.rb +5 -4
  14. data/lib/sentry/check_in_event.rb +5 -4
  15. data/lib/sentry/client.rb +121 -19
  16. data/lib/sentry/configuration.rb +207 -38
  17. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  18. data/lib/sentry/cron/monitor_check_ins.rb +6 -4
  19. data/lib/sentry/cron/monitor_config.rb +1 -1
  20. data/lib/sentry/debug_structured_logger.rb +94 -0
  21. data/lib/sentry/dsn.rb +35 -3
  22. data/lib/sentry/envelope/item.rb +88 -0
  23. data/lib/sentry/envelope.rb +2 -85
  24. data/lib/sentry/event.rb +13 -8
  25. data/lib/sentry/excon/middleware.rb +77 -0
  26. data/lib/sentry/excon.rb +10 -0
  27. data/lib/sentry/faraday.rb +77 -0
  28. data/lib/sentry/graphql.rb +1 -1
  29. data/lib/sentry/hub.rb +57 -4
  30. data/lib/sentry/interfaces/mechanism.rb +1 -1
  31. data/lib/sentry/interfaces/request.rb +6 -6
  32. data/lib/sentry/interfaces/single_exception.rb +3 -3
  33. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  34. data/lib/sentry/interfaces/stacktrace_builder.rb +15 -2
  35. data/lib/sentry/linecache.rb +3 -3
  36. data/lib/sentry/log_event.rb +219 -0
  37. data/lib/sentry/log_event_buffer.rb +75 -0
  38. data/lib/sentry/logger.rb +1 -1
  39. data/lib/sentry/metrics/aggregator.rb +13 -13
  40. data/lib/sentry/metrics/configuration.rb +12 -2
  41. data/lib/sentry/metrics/set_metric.rb +2 -2
  42. data/lib/sentry/metrics/timing.rb +8 -0
  43. data/lib/sentry/metrics.rb +27 -15
  44. data/lib/sentry/net/http.rb +17 -38
  45. data/lib/sentry/profiler/helpers.rb +46 -0
  46. data/lib/sentry/profiler.rb +27 -57
  47. data/lib/sentry/propagation_context.rb +60 -23
  48. data/lib/sentry/rack/capture_exceptions.rb +2 -2
  49. data/lib/sentry/rack.rb +2 -2
  50. data/lib/sentry/rake.rb +2 -2
  51. data/lib/sentry/release_detector.rb +4 -4
  52. data/lib/sentry/rspec.rb +91 -0
  53. data/lib/sentry/scope.rb +27 -2
  54. data/lib/sentry/session_flusher.rb +10 -6
  55. data/lib/sentry/span.rb +17 -4
  56. data/lib/sentry/std_lib_logger.rb +55 -0
  57. data/lib/sentry/structured_logger.rb +138 -0
  58. data/lib/sentry/test_helper.rb +45 -1
  59. data/lib/sentry/threaded_periodic_worker.rb +3 -3
  60. data/lib/sentry/transaction.rb +41 -13
  61. data/lib/sentry/transaction_event.rb +5 -3
  62. data/lib/sentry/transport/debug_transport.rb +70 -0
  63. data/lib/sentry/transport/dummy_transport.rb +1 -0
  64. data/lib/sentry/transport/http_transport.rb +21 -18
  65. data/lib/sentry/transport.rb +26 -11
  66. data/lib/sentry/utils/env_helper.rb +21 -0
  67. data/lib/sentry/utils/http_tracing.rb +74 -0
  68. data/lib/sentry/utils/logging_helper.rb +10 -3
  69. data/lib/sentry/utils/real_ip.rb +1 -1
  70. data/lib/sentry/utils/sample_rand.rb +97 -0
  71. data/lib/sentry/utils/uuid.rb +13 -0
  72. data/lib/sentry/vernier/output.rb +89 -0
  73. data/lib/sentry/vernier/profiler.rb +132 -0
  74. data/lib/sentry/version.rb +1 -1
  75. data/lib/sentry-ruby.rb +106 -12
  76. data/sentry-ruby-core.gemspec +3 -1
  77. data/sentry-ruby.gemspec +4 -2
  78. metadata +28 -12
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'securerandom'
3
+ require "securerandom"
4
+ require_relative "profiler/helpers"
5
+ require "sentry/utils/uuid"
4
6
 
5
7
  module Sentry
6
8
  class Profiler
7
- VERSION = '1'
8
- PLATFORM = 'ruby'
9
+ include Profiler::Helpers
10
+
11
+ VERSION = "1"
12
+ PLATFORM = "ruby"
9
13
  # 101 Hz in microseconds
10
14
  DEFAULT_INTERVAL = 1e6 / 101
11
15
  MICRO_TO_NANO_SECONDS = 1e3
@@ -14,14 +18,14 @@ module Sentry
14
18
  attr_reader :sampled, :started, :event_id
15
19
 
16
20
  def initialize(configuration)
17
- @event_id = SecureRandom.uuid.delete('-')
21
+ @event_id = Utils.uuid
18
22
  @started = false
19
23
  @sampled = nil
20
24
 
21
25
  @profiling_enabled = defined?(StackProf) && configuration.profiling_enabled?
22
26
  @profiles_sample_rate = configuration.profiles_sample_rate
23
27
  @project_root = configuration.project_root
24
- @app_dirs_pattern = configuration.app_dirs_pattern || Backtrace::APP_DIRS_PATTERN
28
+ @app_dirs_pattern = configuration.app_dirs_pattern
25
29
  @in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
26
30
  end
27
31
 
@@ -33,7 +37,7 @@ module Sentry
33
37
  raw: true,
34
38
  aggregate: false)
35
39
 
36
- @started ? log('Started') : log('Not started since running elsewhere')
40
+ @started ? log("Started") : log("Not started since running elsewhere")
37
41
  end
38
42
 
39
43
  def stop
@@ -41,7 +45,11 @@ module Sentry
41
45
  return unless @started
42
46
 
43
47
  StackProf.stop
44
- log('Stopped')
48
+ log("Stopped")
49
+ end
50
+
51
+ def active_thread_id
52
+ "0"
45
53
  end
46
54
 
47
55
  # Sets initial sampling decision of the profile.
@@ -54,14 +62,14 @@ module Sentry
54
62
 
55
63
  unless transaction_sampled
56
64
  @sampled = false
57
- log('Discarding profile because transaction not sampled')
65
+ log("Discarding profile because transaction not sampled")
58
66
  return
59
67
  end
60
68
 
61
69
  case @profiles_sample_rate
62
70
  when 0.0
63
71
  @sampled = false
64
- log('Discarding profile because sample_rate is 0')
72
+ log("Discarding profile because sample_rate is 0")
65
73
  return
66
74
  when 1.0
67
75
  @sampled = true
@@ -70,7 +78,7 @@ module Sentry
70
78
  @sampled = Random.rand < @profiles_sample_rate
71
79
  end
72
80
 
73
- log('Discarding profile due to sampling decision') unless @sampled
81
+ log("Discarding profile due to sampling decision") unless @sampled
74
82
  end
75
83
 
76
84
  def to_hash
@@ -90,13 +98,12 @@ module Sentry
90
98
 
91
99
  frame_map = {}
92
100
 
93
- frames = results[:frames].to_enum.with_index.map do |frame, idx|
94
- frame_id, frame_data = frame
95
-
101
+ frames = results[:frames].map.with_index do |(frame_id, frame_data), idx|
96
102
  # need to map over stackprof frame ids to ours
97
103
  frame_map[frame_id] = idx
98
104
 
99
105
  file_path = frame_data[:file]
106
+ lineno = frame_data[:line]
100
107
  in_app = in_app?(file_path)
101
108
  filename = compute_filename(file_path, in_app)
102
109
  function, mod = split_module(frame_data[:name])
@@ -109,7 +116,7 @@ module Sentry
109
116
  }
110
117
 
111
118
  frame_hash[:module] = mod if mod
112
- frame_hash[:lineno] = frame_data[:line] if frame_data[:line] && frame_data[:line] >= 0
119
+ frame_hash[:lineno] = lineno if lineno && lineno >= 0
113
120
 
114
121
  frame_hash
115
122
  end
@@ -130,7 +137,7 @@ module Sentry
130
137
  num_seen << results[:raw][idx + len]
131
138
  idx += len + 1
132
139
 
133
- log('Unknown frame in stack') if stack.size != len
140
+ log("Unknown frame in stack") if stack.size != len
134
141
  end
135
142
 
136
143
  idx = 0
@@ -155,16 +162,16 @@ module Sentry
155
162
  # Till then, on multi-threaded servers like puma, we will get frames from other active threads when the one
156
163
  # we're profiling is idle/sleeping/waiting for IO etc.
157
164
  # https://bugs.ruby-lang.org/issues/10602
158
- thread_id: '0',
165
+ thread_id: "0",
159
166
  elapsed_since_start_ns: elapsed_since_start_ns.to_s
160
167
  }
161
168
  end
162
169
  end
163
170
 
164
- log('Some samples thrown away') if samples.size != results[:samples]
171
+ log("Some samples thrown away") if samples.size != results[:samples]
165
172
 
166
173
  if samples.size <= MIN_SAMPLES_REQUIRED
167
- log('Not enough samples, discarding profiler')
174
+ log("Not enough samples, discarding profiler")
168
175
  record_lost_event(:insufficient_data)
169
176
  return {}
170
177
  end
@@ -186,48 +193,11 @@ module Sentry
186
193
  private
187
194
 
188
195
  def log(message)
189
- Sentry.logger.debug(LOGGER_PROGNAME) { "[Profiler] #{message}" }
190
- end
191
-
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]
196
+ Sentry.sdk_logger.debug(LOGGER_PROGNAME) { "[Profiler] #{message}" }
227
197
  end
228
198
 
229
199
  def record_lost_event(reason)
230
- Sentry.get_current_client&.transport&.record_lost_event(reason, 'profile')
200
+ Sentry.get_current_client&.transport&.record_lost_event(reason, "profile")
231
201
  end
232
202
  end
233
203
  end
@@ -2,15 +2,15 @@
2
2
 
3
3
  require "securerandom"
4
4
  require "sentry/baggage"
5
+ require "sentry/utils/uuid"
6
+ require "sentry/utils/sample_rand"
5
7
 
6
8
  module Sentry
7
9
  class PropagationContext
8
10
  SENTRY_TRACE_REGEXP = Regexp.new(
9
- "^[ \t]*" + # whitespace
10
- "([0-9a-f]{32})?" + # trace_id
11
+ "\\A([0-9a-f]{32})?" + # trace_id
11
12
  "-?([0-9a-f]{16})?" + # span_id
12
- "-?([01])?" + # sampled
13
- "[ \t]*$" # whitespace
13
+ "-?([01])?\\z" # sampled
14
14
  )
15
15
 
16
16
  # An uuid that can be used to identify a trace.
@@ -32,6 +32,53 @@ module Sentry
32
32
  # Please use the #get_baggage method for interfacing outside this class.
33
33
  # @return [Baggage, nil]
34
34
  attr_reader :baggage
35
+ # The propagated random value used for sampling decisions.
36
+ # @return [Float, nil]
37
+ attr_reader :sample_rand
38
+
39
+ # Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
40
+ #
41
+ # @param sentry_trace [String] the sentry-trace header value from the previous transaction.
42
+ # @return [Array, nil]
43
+ def self.extract_sentry_trace(sentry_trace)
44
+ value = sentry_trace.to_s.strip
45
+ return if value.empty?
46
+
47
+ match = SENTRY_TRACE_REGEXP.match(value)
48
+ return if match.nil?
49
+
50
+ trace_id, parent_span_id, sampled_flag = match[1..3]
51
+ parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
52
+
53
+ [trace_id, parent_span_id, parent_sampled]
54
+ end
55
+
56
+ def self.extract_sample_rand_from_baggage(baggage, trace_id = nil)
57
+ return unless baggage&.items
58
+
59
+ sample_rand_str = baggage.items["sample_rand"]
60
+ return unless sample_rand_str
61
+
62
+ generator = Utils::SampleRand.new(trace_id: trace_id)
63
+ generator.generate_from_value(sample_rand_str)
64
+ end
65
+
66
+ def self.generate_sample_rand(baggage, trace_id, parent_sampled)
67
+ generator = Utils::SampleRand.new(trace_id: trace_id)
68
+
69
+ if baggage&.items && !parent_sampled.nil?
70
+ sample_rate_str = baggage.items["sample_rate"]
71
+ sample_rate = sample_rate_str&.to_f
72
+
73
+ if sample_rate && !parent_sampled.nil?
74
+ generator.generate_from_sampling_decision(parent_sampled, sample_rate)
75
+ else
76
+ generator.generate_from_trace_id
77
+ end
78
+ else
79
+ generator.generate_from_trace_id
80
+ end
81
+ end
35
82
 
36
83
  def initialize(scope, env = nil)
37
84
  @scope = scope
@@ -39,6 +86,7 @@ module Sentry
39
86
  @parent_sampled = nil
40
87
  @baggage = nil
41
88
  @incoming_trace = false
89
+ @sample_rand = nil
42
90
 
43
91
  if env
44
92
  sentry_trace_header = env["HTTP_SENTRY_TRACE"] || env[SENTRY_TRACE_HEADER_NAME]
@@ -60,28 +108,17 @@ module Sentry
60
108
  Baggage.new({})
61
109
  end
62
110
 
111
+ @sample_rand = self.class.extract_sample_rand_from_baggage(@baggage, @trace_id)
112
+
63
113
  @baggage.freeze!
64
114
  @incoming_trace = true
65
115
  end
66
116
  end
67
117
  end
68
118
 
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]
119
+ @trace_id ||= Utils.uuid
120
+ @span_id = Utils.uuid.slice(0, 16)
121
+ @sample_rand ||= self.class.generate_sample_rand(@baggage, @trace_id, @parent_sampled)
85
122
  end
86
123
 
87
124
  # Returns the trace context that can be used to embed in an Event.
@@ -108,7 +145,7 @@ module Sentry
108
145
  end
109
146
 
110
147
  # Returns the Dynamic Sampling Context from the baggage.
111
- # @return [String, nil]
148
+ # @return [Hash, nil]
112
149
  def get_dynamic_sampling_context
113
150
  get_baggage&.dynamic_sampling_context
114
151
  end
@@ -122,10 +159,10 @@ module Sentry
122
159
 
123
160
  items = {
124
161
  "trace_id" => trace_id,
162
+ "sample_rand" => Utils::SampleRand.format(@sample_rand),
125
163
  "environment" => configuration.environment,
126
164
  "release" => configuration.release,
127
- "public_key" => configuration.dsn&.public_key,
128
- "user_segment" => @scope.user && @scope.user["segment"]
165
+ "public_key" => configuration.dsn&.public_key
129
166
  }
130
167
 
131
168
  items.compact!
@@ -50,11 +50,11 @@ module Sentry
50
50
  private
51
51
 
52
52
  def collect_exception(env)
53
- env['rack.exception'] || env['sinatra.error']
53
+ env["rack.exception"] || env["sinatra.error"]
54
54
  end
55
55
 
56
56
  def transaction_op
57
- "http.server".freeze
57
+ "http.server"
58
58
  end
59
59
 
60
60
  def capture_exception(exception, env)
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,10 +8,10 @@ module Sentry
8
8
  module Application
9
9
  # @api private
10
10
  def display_error_message(ex)
11
- mechanism = Sentry::Mechanism.new(type: 'rake', handled: false)
11
+ mechanism = Sentry::Mechanism.new(type: "rake", handled: false)
12
12
 
13
13
  Sentry.capture_exception(ex, hint: { mechanism: mechanism }) do |scope|
14
- task_name = top_level_tasks.join(' ')
14
+ task_name = top_level_tasks.join(" ")
15
15
  scope.set_transaction_name(task_name, source: :task)
16
16
  scope.set_tag("rake_task", task_name)
17
17
  end if Sentry.initialized? && !Sentry.configuration.skip_rake_integration
@@ -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
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec::Matchers.define :include_sentry_event do |event_message = "", **opts|
4
+ match do |sentry_events|
5
+ @expected_exception = expected_exception(**opts)
6
+ @context = context(**opts)
7
+ @tags = tags(**opts)
8
+
9
+ @expected_event = expected_event(event_message)
10
+ @matched_event = find_matched_event(event_message, sentry_events)
11
+
12
+ return false unless @matched_event
13
+
14
+ [verify_context(), verify_tags()].all?
15
+ end
16
+
17
+ chain :with_context do |context|
18
+ @context = context
19
+ end
20
+
21
+ chain :with_tags do |tags|
22
+ @tags = tags
23
+ end
24
+
25
+ failure_message do |sentry_events|
26
+ info = ["Failed to find event matching:\n"]
27
+ info << " message: #{@expected_event.message.inspect}"
28
+ info << " exception: #{@expected_exception.inspect}"
29
+ info << " context: #{@context.inspect}"
30
+ info << " tags: #{@tags.inspect}"
31
+ info << "\n"
32
+ info << "Captured events:\n"
33
+ info << dump_events(sentry_events)
34
+ info.join("\n")
35
+ end
36
+
37
+ def expected_event(event_message)
38
+ if @expected_exception
39
+ Sentry.get_current_client.event_from_exception(@expected_exception)
40
+ else
41
+ Sentry.get_current_client.event_from_message(event_message)
42
+ end
43
+ end
44
+
45
+ def expected_exception(**opts)
46
+ opts[:exception].new(opts[:message]) if opts[:exception]
47
+ end
48
+
49
+ def context(**opts)
50
+ opts.fetch(:context, @context || {})
51
+ end
52
+
53
+ def tags(**opts)
54
+ opts.fetch(:tags, @tags || {})
55
+ end
56
+
57
+ def find_matched_event(event_message, sentry_events)
58
+ @matched_event ||= sentry_events
59
+ .find { |event|
60
+ if @expected_exception
61
+ # Is it OK that we only compare the first exception?
62
+ event_exception = event.exception.values.first
63
+ expected_event_exception = @expected_event.exception.values.first
64
+
65
+ event_exception.type == expected_event_exception.type && event_exception.value == expected_event_exception.value
66
+ else
67
+ event.message == @expected_event.message
68
+ end
69
+ }
70
+ end
71
+
72
+ def dump_events(sentry_events)
73
+ sentry_events.map(&Kernel.method(:Hash)).map do |hash|
74
+ hash.select { |k, _| [:message, :contexts, :tags, :exception].include?(k) }
75
+ end.map do |hash|
76
+ JSON.pretty_generate(hash)
77
+ end.join("\n\n")
78
+ end
79
+
80
+ def verify_context
81
+ return true if @context.empty?
82
+
83
+ @matched_event.contexts.any? { |key, value| value == @context[key] }
84
+ end
85
+
86
+ def verify_tags
87
+ return true if @tags.empty?
88
+
89
+ @tags.all? { |key, value| @matched_event.tags.include?(key) && @matched_event.tags[key] == value }
90
+ end
91
+ 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
@@ -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,7 +46,7 @@ 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
- unless event.is_a?(CheckInEvent)
49
+ unless event.is_a?(CheckInEvent) || event.is_a?(LogEvent)
48
50
  event.tags = tags.merge(event.tags)
49
51
  event.user = user.merge(event.user)
50
52
  event.extra = extra.merge(event.extra)
@@ -55,13 +57,25 @@ 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
61
+ end
62
+
63
+ if event.is_a?(LogEvent)
64
+ event.user = user.merge(event.user)
58
65
  end
59
66
 
60
67
  if span
61
68
  event.contexts[:trace] ||= span.get_trace_context
69
+
70
+ if event.respond_to?(:dynamic_sampling_context)
71
+ event.dynamic_sampling_context ||= span.get_dynamic_sampling_context
72
+ end
62
73
  else
63
74
  event.contexts[:trace] ||= propagation_context.get_trace_context
64
- event.dynamic_sampling_context ||= propagation_context.get_dynamic_sampling_context
75
+
76
+ if event.respond_to?(:dynamic_sampling_context)
77
+ event.dynamic_sampling_context ||= propagation_context.get_dynamic_sampling_context
78
+ end
65
79
  end
66
80
 
67
81
  all_event_processors = self.class.global_event_processors + @event_processors
@@ -102,6 +116,7 @@ module Sentry
102
116
  copy.span = span.deep_dup
103
117
  copy.session = session.deep_dup
104
118
  copy.propagation_context = propagation_context.deep_dup
119
+ copy.attachments = attachments.dup
105
120
  copy
106
121
  end
107
122
 
@@ -119,6 +134,7 @@ module Sentry
119
134
  self.fingerprint = scope.fingerprint
120
135
  self.span = scope.span
121
136
  self.propagation_context = scope.propagation_context
137
+ self.attachments = scope.attachments
122
138
  end
123
139
 
124
140
  # Updates the scope's data from the given options.
@@ -128,6 +144,7 @@ module Sentry
128
144
  # @param user [Hash]
129
145
  # @param level [String, Symbol]
130
146
  # @param fingerprint [Array]
147
+ # @param attachments [Array<Attachment>]
131
148
  # @return [Array]
132
149
  def update_from_options(
133
150
  contexts: nil,
@@ -136,6 +153,7 @@ module Sentry
136
153
  user: nil,
137
154
  level: nil,
138
155
  fingerprint: nil,
156
+ attachments: nil,
139
157
  **options
140
158
  )
141
159
  self.contexts.merge!(contexts) if contexts
@@ -283,6 +301,12 @@ module Sentry
283
301
  @propagation_context = PropagationContext.new(self, env)
284
302
  end
285
303
 
304
+ # Add a new attachment to the scope.
305
+ def add_attachment(**opts)
306
+ attachments << (attachment = Attachment.new(**opts))
307
+ attachment
308
+ end
309
+
286
310
  protected
287
311
 
288
312
  # for duplicating scopes internally
@@ -303,6 +327,7 @@ module Sentry
303
327
  @rack_env = {}
304
328
  @span = nil
305
329
  @session = nil
330
+ @attachments = []
306
331
  generate_propagation_context
307
332
  set_new_breadcrumb_buffer
308
333
  end
@@ -5,11 +5,12 @@ module Sentry
5
5
  FLUSH_INTERVAL = 60
6
6
 
7
7
  def initialize(configuration, client)
8
- super(configuration.logger, FLUSH_INTERVAL)
8
+ super(configuration.sdk_logger, FLUSH_INTERVAL)
9
9
  @client = client
10
10
  @pending_aggregates = {}
11
11
  @release = configuration.release
12
12
  @environment = configuration.environment
13
+ @mutex = Mutex.new
13
14
 
14
15
  log_debug("[Sessions] Sessions won't be captured without a valid release") unless @release
15
16
  end
@@ -18,7 +19,6 @@ module Sentry
18
19
  return if @pending_aggregates.empty?
19
20
 
20
21
  @client.capture_envelope(pending_envelope)
21
- @pending_aggregates = {}
22
22
  end
23
23
 
24
24
  alias_method :run, :flush
@@ -42,11 +42,15 @@ module Sentry
42
42
  end
43
43
 
44
44
  def pending_envelope
45
- envelope = Envelope.new
46
-
47
- header = { type: 'sessions' }
48
- payload = { attrs: attrs, aggregates: @pending_aggregates.values }
45
+ aggregates = @mutex.synchronize do
46
+ aggregates = @pending_aggregates.values
47
+ @pending_aggregates = {}
48
+ aggregates
49
+ end
49
50
 
51
+ envelope = Envelope.new
52
+ header = { type: "sessions" }
53
+ payload = { attrs: attrs, aggregates: aggregates }
50
54
  envelope.add_item(header, payload)
51
55
  envelope
52
56
  end
data/lib/sentry/span.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "securerandom"
4
4
  require "sentry/metrics/local_aggregator"
5
+ require "sentry/utils/uuid"
5
6
 
6
7
  module Sentry
7
8
  class Span
@@ -44,6 +45,11 @@ module Sentry
44
45
  LINENO = "code.lineno"
45
46
  FUNCTION = "code.function"
46
47
  NAMESPACE = "code.namespace"
48
+
49
+ MESSAGING_MESSAGE_ID = "messaging.message.id"
50
+ MESSAGING_DESTINATION_NAME = "messaging.destination.name"
51
+ MESSAGING_MESSAGE_RECEIVE_LATENCY = "messaging.message.receive.latency"
52
+ MESSAGING_MESSAGE_RETRY_COUNT = "messaging.message.retry.count"
47
53
  end
48
54
 
49
55
  STATUS_MAP = {
@@ -122,8 +128,8 @@ module Sentry
122
128
  timestamp: nil,
123
129
  origin: nil
124
130
  )
125
- @trace_id = trace_id || SecureRandom.uuid.delete("-")
126
- @span_id = span_id || SecureRandom.uuid.delete("-").slice(0, 16)
131
+ @trace_id = trace_id || Utils.uuid
132
+ @span_id = span_id || Utils.uuid.slice(0, 16)
127
133
  @parent_span_id = parent_span_id
128
134
  @sampled = sampled
129
135
  @start_timestamp = start_timestamp || Sentry.utc_now.to_f
@@ -160,6 +166,12 @@ module Sentry
160
166
  transaction.get_baggage&.serialize
161
167
  end
162
168
 
169
+ # Returns the Dynamic Sampling Context from the transaction baggage.
170
+ # @return [Hash, nil]
171
+ def get_dynamic_sampling_context
172
+ transaction.get_baggage&.dynamic_sampling_context
173
+ end
174
+
163
175
  # @return [Hash]
164
176
  def to_hash
165
177
  hash = {
@@ -192,7 +204,8 @@ module Sentry
192
204
  description: @description,
193
205
  op: @op,
194
206
  status: @status,
195
- origin: @origin
207
+ origin: @origin,
208
+ data: @data
196
209
  }
197
210
  end
198
211
 
@@ -249,7 +262,7 @@ module Sentry
249
262
 
250
263
 
251
264
  # Sets the span's status.
252
- # @param satus [String] status of the span.
265
+ # @param status [String] status of the span.
253
266
  def set_status(status)
254
267
  @status = status
255
268
  end