sentry-ruby-core 5.9.0 → 5.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34b14c30aa0416847e72f2810e3155ca61866438cdc35dcc22ebdb9c15af6bcb
4
- data.tar.gz: 7a17648c7a5d06f22d2d643f6ff1cb25d3fed2348609e372fc5762043ebd538a
3
+ metadata.gz: bdd8eb2494c450be3e5f5fddfad4bf2a03ea8977d389aa289e55e320d5d00608
4
+ data.tar.gz: ff18479058fdcd44227409610251a2c0b014766be1d0641ac9b83e8badd6489a
5
5
  SHA512:
6
- metadata.gz: 9db3e0be215c6fa6082602d1f992fce864c3a2cdd5874e0f409f41e61cca2f15493ec33c497647f64e149098d114c552bf29f36da1d5818002c4be0ea922b21e
7
- data.tar.gz: 6bf0cba7b42dd9bfd5078e4353ad7f92d932b959a87c748cb30e505ffd8d205899c819ea4f72e5f3099f0b861139864062b870219678abdeda0a0629604963d3
6
+ metadata.gz: 77ab7c5172623cf4e48daa4e743ad2d0f6490592d9786498cdc03c9652dff2ec607b5738c7b9bf4b3799f300bfabd51226afe780a6b50a124f312330be66db8e
7
+ data.tar.gz: 4fdbd60228c2fe23e7db4b4d7d1c2e8841eeec561ef3de01f41c0415b997bc5a5d90cc5fe08e6ccc98eb1352043a25c82e98489cd9720db65967c5a126859d78
data/Gemfile CHANGED
@@ -21,8 +21,11 @@ gem "simplecov-cobertura", "~> 1.4"
21
21
  gem "rexml"
22
22
  gem "stackprof" unless RUBY_PLATFORM == "java"
23
23
 
24
- gem "object_tracer"
25
- gem "debug", github: "ruby/debug", platform: :ruby if RUBY_VERSION.to_f >= 2.6
24
+ if RUBY_VERSION.to_f >= 2.6
25
+ gem "debug", github: "ruby/debug", platform: :ruby
26
+ gem "irb"
27
+ end
28
+
26
29
  gem "pry"
27
30
 
28
31
  gem "benchmark-ips"
data/lib/sentry/client.rb CHANGED
@@ -148,6 +148,8 @@ module Sentry
148
148
  raise
149
149
  end
150
150
 
151
+ # @deprecated use Sentry.get_traceparent instead.
152
+ #
151
153
  # Generates a Sentry trace for distribted tracing from the given Span.
152
154
  # Returns `nil` if `config.propagate_traces` is `false`.
153
155
  # @param span [Span] the span to generate trace from.
@@ -160,7 +162,9 @@ module Sentry
160
162
  trace
161
163
  end
162
164
 
163
- # Generates a W3C Baggage header for distribted tracing from the given Span.
165
+ # @deprecated Use Sentry.get_baggage instead.
166
+ #
167
+ # Generates a W3C Baggage header for distributed tracing from the given Span.
164
168
  # Returns `nil` if `config.propagate_traces` is `false`.
165
169
  # @param span [Span] the span to generate trace from.
166
170
  # @return [String, nil]
@@ -14,6 +14,8 @@ module Sentry
14
14
  class Configuration
15
15
  include CustomInspection
16
16
  include LoggingHelper
17
+ include ArgumentCheckingHelper
18
+
17
19
  # Directories to be recognized as part of your app. e.g. if you
18
20
  # have an `engines` dir at the root of your project, you may want
19
21
  # to set this to something like /(app|config|engines|lib)/
@@ -179,7 +181,7 @@ module Sentry
179
181
  # Release tag to be passed with every event sent to Sentry.
180
182
  # We automatically try to set this to a git SHA or Capistrano release.
181
183
  # @return [String]
182
- attr_accessor :release
184
+ attr_reader :release
183
185
 
184
186
  # The sampling factor to apply to events. A value of 0.0 will not send
185
187
  # any events, and a value of 1.0 will send 100% of events.
@@ -214,8 +216,8 @@ module Sentry
214
216
  attr_reader :transport
215
217
 
216
218
  # Take a float between 0.0 and 1.0 as the sample rate for tracing events (transactions).
217
- # @return [Float]
218
- attr_accessor :traces_sample_rate
219
+ # @return [Float, nil]
220
+ attr_reader :traces_sample_rate
219
221
 
220
222
  # Take a Proc that controls the sample rate for every tracing event, e.g.
221
223
  # @example
@@ -241,6 +243,11 @@ module Sentry
241
243
  # @return [Boolean]
242
244
  attr_accessor :auto_session_tracking
243
245
 
246
+ # Allowlist of outgoing request targets to which sentry-trace and baggage headers are attached.
247
+ # Default is all (/.*/)
248
+ # @return [Array<String, Regexp>]
249
+ attr_accessor :trace_propagation_targets
250
+
244
251
  # The instrumenter to use, :sentry or :otel
245
252
  # @return [Symbol]
246
253
  attr_reader :instrumenter
@@ -255,6 +262,15 @@ module Sentry
255
262
  # @!visibility private
256
263
  attr_reader :errors, :gem_specs
257
264
 
265
+ # These exceptions could enter Puma's `lowlevel_error_handler` callback and the SDK's Puma integration
266
+ # But they are mostly considered as noise and should be ignored by default
267
+ # Please see https://github.com/getsentry/sentry-ruby/pull/2026 for more information
268
+ PUMA_IGNORE_DEFAULT = [
269
+ 'Puma::MiniSSL::SSLError',
270
+ 'Puma::HttpParserError',
271
+ 'Puma::HttpParserError501'
272
+ ].freeze
273
+
258
274
  # Most of these errors generate 4XX responses. In general, Sentry clients
259
275
  # only automatically report 5xx responses.
260
276
  IGNORE_DEFAULT = [
@@ -279,6 +295,8 @@ module Sentry
279
295
 
280
296
  INSTRUMENTERS = [:sentry, :otel]
281
297
 
298
+ PROPAGATION_TARGETS_MATCH_ALL = /.*/.freeze
299
+
282
300
  class << self
283
301
  # Post initialization callbacks are called at the end of initialization process
284
302
  # allowing extending the configuration of sentry-ruby by multiple extensions
@@ -304,7 +322,7 @@ module Sentry
304
322
  self.environment = environment_from_env
305
323
  self.enabled_environments = []
306
324
  self.exclude_loggers = []
307
- self.excluded_exceptions = IGNORE_DEFAULT.dup
325
+ self.excluded_exceptions = IGNORE_DEFAULT + PUMA_IGNORE_DEFAULT
308
326
  self.inspect_exception_causes_for_exclusion = true
309
327
  self.linecache = ::Sentry::LineCache.new
310
328
  self.logger = ::Sentry::Logger.new(STDOUT)
@@ -321,11 +339,11 @@ module Sentry
321
339
  self.dsn = ENV['SENTRY_DSN']
322
340
  self.server_name = server_name_from_env
323
341
  self.instrumenter = :sentry
342
+ self.trace_propagation_targets = [PROPAGATION_TARGETS_MATCH_ALL]
324
343
 
325
344
  self.before_send = nil
326
345
  self.before_send_transaction = nil
327
346
  self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
328
- self.traces_sample_rate = nil
329
347
  self.traces_sampler = nil
330
348
  self.enable_tracing = nil
331
349
 
@@ -341,6 +359,12 @@ module Sentry
341
359
 
342
360
  alias server= dsn=
343
361
 
362
+ def release=(value)
363
+ check_argument_type!(value, String, NilClass)
364
+
365
+ @release = value
366
+ end
367
+
344
368
  def async=(value)
345
369
  check_callable!("async", value)
346
370
 
@@ -401,7 +425,17 @@ module Sentry
401
425
  @traces_sample_rate ||= 1.0 if enable_tracing
402
426
  end
403
427
 
428
+ def is_numeric_or_nil?(value)
429
+ value.is_a?(Numeric) || value.nil?
430
+ end
431
+
432
+ def traces_sample_rate=(traces_sample_rate)
433
+ raise ArgumentError, "traces_sample_rate must be a Numeric or nil" unless is_numeric_or_nil?(traces_sample_rate)
434
+ @traces_sample_rate = traces_sample_rate
435
+ end
436
+
404
437
  def profiles_sample_rate=(profiles_sample_rate)
438
+ raise ArgumentError, "profiles_sample_rate must be a Numeric or nil" unless is_numeric_or_nil?(profiles_sample_rate)
405
439
  log_info("Please make sure to include the 'stackprof' gem in your Gemfile to use Profiling with Sentry.") unless defined?(StackProf)
406
440
  @profiles_sample_rate = profiles_sample_rate
407
441
  end
@@ -435,19 +469,19 @@ module Sentry
435
469
  enabled_environments.empty? || enabled_environments.include?(environment)
436
470
  end
437
471
 
472
+ def valid_sample_rate?(sample_rate)
473
+ return false unless sample_rate.is_a?(Numeric)
474
+ sample_rate >= 0.0 && sample_rate <= 1.0
475
+ end
476
+
438
477
  def tracing_enabled?
439
- valid_sampler = !!((@traces_sample_rate &&
440
- @traces_sample_rate >= 0.0 &&
441
- @traces_sample_rate <= 1.0) ||
442
- @traces_sampler)
478
+ valid_sampler = !!((valid_sample_rate?(@traces_sample_rate)) || @traces_sampler)
443
479
 
444
480
  (@enable_tracing != false) && valid_sampler && sending_allowed?
445
481
  end
446
482
 
447
483
  def profiling_enabled?
448
- valid_sampler = !!(@profiles_sample_rate &&
449
- @profiles_sample_rate >= 0.0 &&
450
- @profiles_sample_rate <= 1.0)
484
+ valid_sampler = !!(valid_sample_rate?(@profiles_sample_rate))
451
485
 
452
486
  tracing_enabled? && valid_sampler && sending_allowed?
453
487
  end
@@ -477,7 +511,7 @@ module Sentry
477
511
  def detect_release
478
512
  return unless sending_allowed?
479
513
 
480
- self.release ||= ReleaseDetector.detect_release(project_root: project_root, running_on_heroku: running_on_heroku?)
514
+ @release ||= ReleaseDetector.detect_release(project_root: project_root, running_on_heroku: running_on_heroku?)
481
515
 
482
516
  if running_on_heroku? && release.nil?
483
517
  log_warn(HEROKU_DYNO_METADATA_MESSAGE)
data/lib/sentry/event.rb CHANGED
@@ -37,6 +37,11 @@ module Sentry
37
37
  # @return [RequestInterface]
38
38
  attr_reader :request
39
39
 
40
+ # Dynamic Sampling Context (DSC) that gets attached
41
+ # as the trace envelope header in the transport.
42
+ # @return [Hash, nil]
43
+ attr_accessor :dynamic_sampling_context
44
+
40
45
  # @param configuration [Configuration]
41
46
  # @param integration_meta [Hash, nil]
42
47
  # @param message [String, nil]
@@ -54,6 +59,7 @@ module Sentry
54
59
  @tags = {}
55
60
 
56
61
  @fingerprint = []
62
+ @dynamic_sampling_context = nil
57
63
 
58
64
  # configuration data that's directly used by events
59
65
  @server_name = configuration.server_name
data/lib/sentry/hub.rb CHANGED
@@ -116,7 +116,11 @@ module Sentry
116
116
  end
117
117
 
118
118
  def capture_exception(exception, **options, &block)
119
- check_argument_type!(exception, ::Exception)
119
+ if RUBY_PLATFORM == "java"
120
+ check_argument_type!(exception, ::Exception, ::Java::JavaLang::Throwable)
121
+ else
122
+ check_argument_type!(exception, ::Exception)
123
+ end
120
124
 
121
125
  return if Sentry.exception_captured?(exception)
122
126
 
@@ -225,6 +229,50 @@ module Sentry
225
229
  end_session
226
230
  end
227
231
 
232
+ def get_traceparent
233
+ return nil unless current_scope
234
+
235
+ current_scope.get_span&.to_sentry_trace ||
236
+ current_scope.propagation_context.get_traceparent
237
+ end
238
+
239
+ def get_baggage
240
+ return nil unless current_scope
241
+
242
+ current_scope.get_span&.to_baggage ||
243
+ current_scope.propagation_context.get_baggage&.serialize
244
+ end
245
+
246
+ def get_trace_propagation_headers
247
+ headers = {}
248
+
249
+ traceparent = get_traceparent
250
+ headers[SENTRY_TRACE_HEADER_NAME] = traceparent if traceparent
251
+
252
+ baggage = get_baggage
253
+ headers[BAGGAGE_HEADER_NAME] = baggage if baggage && !baggage.empty?
254
+
255
+ headers
256
+ end
257
+
258
+ def continue_trace(env, **options)
259
+ configure_scope { |s| s.generate_propagation_context(env) }
260
+
261
+ return nil unless configuration.tracing_enabled?
262
+
263
+ propagation_context = current_scope.propagation_context
264
+ return nil unless propagation_context.incoming_trace
265
+
266
+ Transaction.new(
267
+ hub: self,
268
+ trace_id: propagation_context.trace_id,
269
+ parent_span_id: propagation_context.parent_span_id,
270
+ parent_sampled: propagation_context.parent_sampled,
271
+ baggage: propagation_context.baggage,
272
+ **options
273
+ )
274
+ end
275
+
228
276
  private
229
277
 
230
278
  def current_layer
@@ -11,7 +11,8 @@ module Sentry
11
11
  OMISSION_MARK = "...".freeze
12
12
  MAX_LOCAL_BYTES = 1024
13
13
 
14
- attr_reader :type, :value, :module, :thread_id, :stacktrace
14
+ attr_reader :type, :module, :thread_id, :stacktrace
15
+ attr_accessor :value
15
16
 
16
17
  def initialize(exception:, stacktrace: nil)
17
18
  @type = exception.class.to_s
@@ -30,15 +30,21 @@ module Sentry
30
30
  return super if from_sentry_sdk?
31
31
 
32
32
  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)
33
+ request_info = extract_request_info(req)
34
+
35
+ if propagate_trace?(request_info[:url], Sentry.configuration)
36
+ set_propagation_headers(req)
37
+ end
34
38
 
35
39
  super.tap do |res|
36
- record_sentry_breadcrumb(req, res)
40
+ record_sentry_breadcrumb(request_info, res)
37
41
 
38
42
  if sentry_span
39
- request_info = extract_request_info(req)
40
43
  sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
41
- sentry_span.set_data(:status, res.code.to_i)
44
+ sentry_span.set_data(Span::DataConventions::URL, request_info[:url])
45
+ sentry_span.set_data(Span::DataConventions::HTTP_METHOD, request_info[:method])
46
+ sentry_span.set_data(Span::DataConventions::HTTP_QUERY, request_info[:query]) if request_info[:query]
47
+ sentry_span.set_data(Span::DataConventions::HTTP_STATUS_CODE, res.code.to_i)
42
48
  end
43
49
  end
44
50
  end
@@ -46,23 +52,13 @@ module Sentry
46
52
 
47
53
  private
48
54
 
49
- def set_sentry_trace_header(req, sentry_span)
50
- return unless sentry_span
51
-
52
- client = Sentry.get_current_client
53
-
54
- trace = client.generate_sentry_trace(sentry_span)
55
- req[SENTRY_TRACE_HEADER_NAME] = trace if trace
56
-
57
- baggage = client.generate_baggage(sentry_span)
58
- req[BAGGAGE_HEADER_NAME] = baggage if baggage && !baggage.empty?
55
+ def set_propagation_headers(req)
56
+ Sentry.get_trace_propagation_headers&.each { |k, v| req[k] = v }
59
57
  end
60
58
 
61
- def record_sentry_breadcrumb(req, res)
59
+ def record_sentry_breadcrumb(request_info, res)
62
60
  return unless Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
63
61
 
64
- request_info = extract_request_info(req)
65
-
66
62
  crumb = Sentry::Breadcrumb.new(
67
63
  level: :info,
68
64
  category: BREADCRUMB_CATEGORY,
@@ -87,12 +83,18 @@ module Sentry
87
83
  result = { method: req.method, url: url }
88
84
 
89
85
  if Sentry.configuration.send_default_pii
90
- result[:url] = result[:url] + "?#{uri.query}"
86
+ result[:query] = uri.query
91
87
  result[:body] = req.body
92
88
  end
93
89
 
94
90
  result
95
91
  end
92
+
93
+ def propagate_trace?(url, configuration)
94
+ url &&
95
+ configuration.propagate_traces &&
96
+ configuration.trace_propagation_targets.any? { |target| url.match?(target) }
97
+ end
96
98
  end
97
99
  end
98
100
  end
@@ -0,0 +1,134 @@
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 = 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
61
+
62
+ @baggage.freeze!
63
+ @incoming_trace = true
64
+ end
65
+ end
66
+ end
67
+
68
+ @trace_id ||= SecureRandom.uuid.delete("-")
69
+ @span_id = SecureRandom.uuid.delete("-").slice(0, 16)
70
+ end
71
+
72
+ # Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
73
+ #
74
+ # @param sentry_trace [String] the sentry-trace header value from the previous transaction.
75
+ # @return [Array, nil]
76
+ def self.extract_sentry_trace(sentry_trace)
77
+ match = SENTRY_TRACE_REGEXP.match(sentry_trace)
78
+ return nil if match.nil?
79
+
80
+ trace_id, parent_span_id, sampled_flag = match[1..3]
81
+ parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
82
+
83
+ [trace_id, parent_span_id, parent_sampled]
84
+ end
85
+
86
+ # Returns the trace context that can be used to embed in an Event.
87
+ # @return [Hash]
88
+ def get_trace_context
89
+ {
90
+ trace_id: trace_id,
91
+ span_id: span_id,
92
+ parent_span_id: parent_span_id
93
+ }
94
+ end
95
+
96
+ # Returns the sentry-trace header from the propagation context.
97
+ # @return [String]
98
+ def get_traceparent
99
+ "#{trace_id}-#{span_id}"
100
+ end
101
+
102
+ # Returns the Baggage from the propagation context or populates as head SDK if empty.
103
+ # @return [Baggage, nil]
104
+ def get_baggage
105
+ populate_head_baggage if @baggage.nil? || @baggage.mutable
106
+ @baggage
107
+ end
108
+
109
+ # Returns the Dynamic Sampling Context from the baggage.
110
+ # @return [String, nil]
111
+ def get_dynamic_sampling_context
112
+ get_baggage&.dynamic_sampling_context
113
+ end
114
+
115
+ private
116
+
117
+ def populate_head_baggage
118
+ return unless Sentry.initialized?
119
+
120
+ configuration = Sentry.configuration
121
+
122
+ items = {
123
+ "trace_id" => trace_id,
124
+ "environment" => configuration.environment,
125
+ "release" => configuration.release,
126
+ "public_key" => configuration.dsn&.public_key,
127
+ "user_segment" => @scope.user && @scope.user["segment"]
128
+ }
129
+
130
+ items.compact!
131
+ @baggage = Baggage.new(items, mutable: false)
132
+ end
133
+ end
134
+ end
@@ -62,11 +62,8 @@ module Sentry
62
62
  end
63
63
 
64
64
  def start_transaction(env, scope)
65
- sentry_trace = env["HTTP_SENTRY_TRACE"]
66
- baggage = env["HTTP_BAGGAGE"]
67
-
68
65
  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
66
+ transaction = Sentry.continue_trace(env, **options)
70
67
  Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
71
68
  end
72
69
 
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
@@ -30,6 +33,7 @@ module Sentry
30
33
  attr_reader :commands, :host, :port, :db
31
34
 
32
35
  def record_breadcrumb
36
+ return unless Sentry.initialized?
33
37
  return unless Sentry.configuration.breadcrumbs_logger.include?(LOGGER_NAME)
34
38
 
35
39
  Sentry.add_breadcrumb(
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)
@@ -50,7 +52,10 @@ module Sentry
50
52
  event.transaction_info = { source: transaction_source } if transaction_source
51
53
 
52
54
  if span
53
- event.contexts[:trace] = span.get_trace_context
55
+ event.contexts[:trace] ||= span.get_trace_context
56
+ else
57
+ event.contexts[:trace] ||= propagation_context.get_trace_context
58
+ event.dynamic_sampling_context ||= propagation_context.get_dynamic_sampling_context
54
59
  end
55
60
 
56
61
  event.fingerprint = fingerprint
@@ -95,6 +100,7 @@ module Sentry
95
100
  copy.fingerprint = fingerprint.deep_dup
96
101
  copy.span = span.deep_dup
97
102
  copy.session = session.deep_dup
103
+ copy.propagation_context = propagation_context.deep_dup
98
104
  copy
99
105
  end
100
106
 
@@ -111,6 +117,7 @@ module Sentry
111
117
  self.transaction_sources = scope.transaction_sources
112
118
  self.fingerprint = scope.fingerprint
113
119
  self.span = scope.span
120
+ self.propagation_context = scope.propagation_context
114
121
  end
115
122
 
116
123
  # Updates the scope's data from the given options.
@@ -272,6 +279,13 @@ module Sentry
272
279
  @event_processors << block
273
280
  end
274
281
 
282
+ # Generate a new propagation context either from the incoming env headers or from scratch.
283
+ # @param env [Hash, nil]
284
+ # @return [void]
285
+ def generate_propagation_context(env = nil)
286
+ @propagation_context = PropagationContext.new(self, env)
287
+ end
288
+
275
289
  protected
276
290
 
277
291
  # for duplicating scopes internally
@@ -292,6 +306,7 @@ module Sentry
292
306
  @rack_env = {}
293
307
  @span = nil
294
308
  @session = nil
309
+ generate_propagation_context
295
310
  set_new_breadcrumb_buffer
296
311
  end
297
312
 
data/lib/sentry/span.rb CHANGED
@@ -4,6 +4,43 @@ require "securerandom"
4
4
 
5
5
  module Sentry
6
6
  class Span
7
+
8
+ # We will try to be consistent with OpenTelemetry on this front going forward.
9
+ # https://develop.sentry.dev/sdk/performance/span-data-conventions/
10
+ module DataConventions
11
+ URL = "url"
12
+ HTTP_STATUS_CODE = "http.response.status_code"
13
+ HTTP_QUERY = "http.query"
14
+ HTTP_METHOD = "http.method"
15
+
16
+ # An identifier for the database management system (DBMS) product being used.
17
+ # Example: postgresql
18
+ DB_SYSTEM = "db.system"
19
+
20
+ # The name of the database being accessed.
21
+ # For commands that switch the database, this should be set to the target database
22
+ # (even if the command fails).
23
+ # Example: myDatabase
24
+ DB_NAME = "db.name"
25
+
26
+ # Name of the database host.
27
+ # Example: example.com
28
+ SERVER_ADDRESS = "server.address"
29
+
30
+ # Logical server port number
31
+ # Example: 80; 8080; 443
32
+ SERVER_PORT = "server.port"
33
+
34
+ # Physical server IP address or Unix socket address.
35
+ # Example: 10.5.3.2
36
+ SERVER_SOCKET_ADDRESS = "server.socket.address"
37
+
38
+ # Physical server port.
39
+ # Recommended: If different than server.port.
40
+ # Example: 16456
41
+ SERVER_SOCKET_PORT = "server.socket.port"
42
+ end
43
+
7
44
  STATUS_MAP = {
8
45
  400 => "invalid_argument",
9
46
  401 => "unauthenticated",
@@ -75,7 +112,7 @@ module Sentry
75
112
  timestamp: nil
76
113
  )
77
114
  @trace_id = trace_id || SecureRandom.uuid.delete("-")
78
- @span_id = span_id || SecureRandom.hex(8)
115
+ @span_id = span_id || SecureRandom.uuid.delete("-").slice(0, 16)
79
116
  @parent_span_id = parent_span_id
80
117
  @sampled = sampled
81
118
  @start_timestamp = start_timestamp || Sentry.utc_now.to_f
@@ -208,7 +245,7 @@ module Sentry
208
245
  # @param status_code [String] example: "500".
209
246
  def set_http_status(status_code)
210
247
  status_code = status_code.to_i
211
- set_data("status_code", status_code)
248
+ set_data(DataConventions::HTTP_STATUS_CODE, status_code)
212
249
 
213
250
  status =
214
251
  if status_code >= 200 && status_code < 299
@@ -2,16 +2,13 @@
2
2
 
3
3
  require "sentry/baggage"
4
4
  require "sentry/profiler"
5
+ require "sentry/propagation_context"
5
6
 
6
7
  module Sentry
7
8
  class Transaction < Span
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
- )
9
+ # @deprecated Use Sentry::PropagationContext::SENTRY_TRACE_REGEXP instead.
10
+ SENTRY_TRACE_REGEXP = PropagationContext::SENTRY_TRACE_REGEXP
11
+
15
12
  UNLABELD_NAME = "<unlabeled transaction>".freeze
16
13
  MESSAGE_PREFIX = "[Tracing]"
17
14
 
@@ -92,6 +89,8 @@ module Sentry
92
89
  init_span_recorder
93
90
  end
94
91
 
92
+ # @deprecated use Sentry.continue_trace instead.
93
+ #
95
94
  # Initalizes a Transaction instance with a Sentry trace string from another transaction (usually from an external request).
96
95
  #
97
96
  # The original transaction will become the parent of the new Transaction instance. And they will share the same `trace_id`.
@@ -132,18 +131,10 @@ module Sentry
132
131
  )
133
132
  end
134
133
 
135
- # Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
136
- #
137
- # @param sentry_trace [String] the sentry-trace header value from the previous transaction.
134
+ # @deprecated Use Sentry::PropagationContext.extract_sentry_trace instead.
138
135
  # @return [Array, nil]
139
136
  def self.extract_sentry_trace(sentry_trace)
140
- match = SENTRY_TRACE_REGEXP.match(sentry_trace)
141
- return nil if match.nil?
142
-
143
- trace_id, parent_span_id, sampled_flag = match[1..3]
144
- parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
145
-
146
- [trace_id, parent_span_id, parent_sampled]
137
+ PropagationContext.extract_sentry_trace(sentry_trace)
147
138
  end
148
139
 
149
140
  # @return [Hash]
@@ -323,6 +314,7 @@ module Sentry
323
314
  items = {
324
315
  "trace_id" => trace_id,
325
316
  "sample_rate" => effective_sample_rate&.to_s,
317
+ "sampled" => sampled&.to_s,
326
318
  "environment" => @environment,
327
319
  "release" => @release,
328
320
  "public_key" => @dsn&.public_key
@@ -8,9 +8,6 @@ module Sentry
8
8
  # @return [<Array[Span]>]
9
9
  attr_accessor :spans
10
10
 
11
- # @return [Hash, nil]
12
- attr_accessor :dynamic_sampling_context
13
-
14
11
  # @return [Hash]
15
12
  attr_accessor :measurements
16
13
 
@@ -4,9 +4,9 @@ module Sentry
4
4
  module ArgumentCheckingHelper
5
5
  private
6
6
 
7
- def check_argument_type!(argument, expected_type)
8
- unless argument.is_a?(expected_type)
9
- raise ArgumentError, "expect the argument to be a #{expected_type}, got #{argument.class} (#{argument.inspect})"
7
+ def check_argument_type!(argument, *expected_types)
8
+ unless expected_types.any? { |t| argument.is_a?(t) }
9
+ raise ArgumentError, "expect the argument to be a #{expected_types.join(' or ')}, got #{argument.class} (#{argument.inspect})"
10
10
  end
11
11
  end
12
12
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.9.0"
4
+ VERSION = "5.11.0"
5
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -489,6 +489,42 @@ module Sentry
489
489
  Scope.add_global_event_processor(&block)
490
490
  end
491
491
 
492
+ # Returns the traceparent (sentry-trace) header for distributed tracing.
493
+ # Can be either from the currently active span or the propagation context.
494
+ #
495
+ # @return [String, nil]
496
+ def get_traceparent
497
+ return nil unless initialized?
498
+ get_current_hub.get_traceparent
499
+ end
500
+
501
+ # Returns the baggage header for distributed tracing.
502
+ # Can be either from the currently active span or the propagation context.
503
+ #
504
+ # @return [String, nil]
505
+ def get_baggage
506
+ return nil unless initialized?
507
+ get_current_hub.get_baggage
508
+ end
509
+
510
+ # Returns the a Hash containing sentry-trace and baggage.
511
+ # Can be either from the currently active span or the propagation context.
512
+ #
513
+ # @return [Hash, nil]
514
+ def get_trace_propagation_headers
515
+ return nil unless initialized?
516
+ get_current_hub.get_trace_propagation_headers
517
+ end
518
+
519
+ # Continue an incoming trace from a rack env like hash.
520
+ #
521
+ # @param env [Hash]
522
+ # @return [Transaction, nil]
523
+ def continue_trace(env, **options)
524
+ return nil unless initialized?
525
+ get_current_hub.continue_trace(env, **options)
526
+ end
527
+
492
528
  ##### Helpers #####
493
529
 
494
530
  # @!visibility private
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentry-ruby-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.9.0
4
+ version: 5.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-19 00:00:00.000000000 Z
11
+ date: 2023-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sentry-ruby
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 5.9.0
19
+ version: 5.11.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 5.9.0
26
+ version: 5.11.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: concurrent-ruby
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -50,7 +50,6 @@ files:
50
50
  - ".rspec"
51
51
  - ".yardopts"
52
52
  - CHANGELOG.md
53
- - CODE_OF_CONDUCT.md
54
53
  - Gemfile
55
54
  - LICENSE.txt
56
55
  - Makefile
@@ -87,6 +86,7 @@ files:
87
86
  - lib/sentry/logger.rb
88
87
  - lib/sentry/net/http.rb
89
88
  - lib/sentry/profiler.rb
89
+ - lib/sentry/propagation_context.rb
90
90
  - lib/sentry/puma.rb
91
91
  - lib/sentry/rack.rb
92
92
  - lib/sentry/rack/capture_exceptions.rb
data/CODE_OF_CONDUCT.md DELETED
@@ -1,74 +0,0 @@
1
- # Contributor Covenant Code of Conduct
2
-
3
- ## Our Pledge
4
-
5
- In the interest of fostering an open and welcoming environment, we as
6
- contributors and maintainers pledge to making participation in our project and
7
- our community a harassment-free experience for everyone, regardless of age, body
8
- size, disability, ethnicity, gender identity and expression, level of experience,
9
- nationality, personal appearance, race, religion, or sexual identity and
10
- orientation.
11
-
12
- ## Our Standards
13
-
14
- Examples of behavior that contributes to creating a positive environment
15
- include:
16
-
17
- * Using welcoming and inclusive language
18
- * Being respectful of differing viewpoints and experiences
19
- * Gracefully accepting constructive criticism
20
- * Focusing on what is best for the community
21
- * Showing empathy towards other community members
22
-
23
- Examples of unacceptable behavior by participants include:
24
-
25
- * The use of sexualized language or imagery and unwelcome sexual attention or
26
- advances
27
- * Trolling, insulting/derogatory comments, and personal or political attacks
28
- * Public or private harassment
29
- * Publishing others' private information, such as a physical or electronic
30
- address, without explicit permission
31
- * Other conduct which could reasonably be considered inappropriate in a
32
- professional setting
33
-
34
- ## Our Responsibilities
35
-
36
- Project maintainers are responsible for clarifying the standards of acceptable
37
- behavior and are expected to take appropriate and fair corrective action in
38
- response to any instances of unacceptable behavior.
39
-
40
- Project maintainers have the right and responsibility to remove, edit, or
41
- reject comments, commits, code, wiki edits, issues, and other contributions
42
- that are not aligned to this Code of Conduct, or to ban temporarily or
43
- permanently any contributor for other behaviors that they deem inappropriate,
44
- threatening, offensive, or harmful.
45
-
46
- ## Scope
47
-
48
- This Code of Conduct applies both within project spaces and in public spaces
49
- when an individual is representing the project or its community. Examples of
50
- representing a project or community include using an official project e-mail
51
- address, posting via an official social media account, or acting as an appointed
52
- representative at an online or offline event. Representation of a project may be
53
- further defined and clarified by project maintainers.
54
-
55
- ## Enforcement
56
-
57
- Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
- reported by contacting the project team at stan001212@gmail.com. All
59
- complaints will be reviewed and investigated and will result in a response that
60
- is deemed necessary and appropriate to the circumstances. The project team is
61
- obligated to maintain confidentiality with regard to the reporter of an incident.
62
- Further details of specific enforcement policies may be posted separately.
63
-
64
- Project maintainers who do not follow or enforce the Code of Conduct in good
65
- faith may face temporary or permanent repercussions as determined by other
66
- members of the project's leadership.
67
-
68
- ## Attribution
69
-
70
- This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
- available at [https://contributor-covenant.org/version/1/4][version]
72
-
73
- [homepage]: https://contributor-covenant.org
74
- [version]: https://contributor-covenant.org/version/1/4/