sentry-ruby-core 5.21.0 → 5.22.1

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: 890c37ed857f90c7d0b5afbdb4fb07d223b8b3d1a820c1888d680bd653f28688
4
- data.tar.gz: 348af55b8f56829cbf5fc5569ddc8308d4cd6ae508480de206443349e1221235
3
+ metadata.gz: f8bbd67a379767b17c92face4aece900b5f559ba676e5531ff30cc7c94b770ec
4
+ data.tar.gz: f347cb6245d5d37f2d883870b26d846244d33ef6cbb67b838fe231d1c17e95f8
5
5
  SHA512:
6
- metadata.gz: a5b8ad23c029b84648a38095ddd52b289983f9ad18096eeb3fb718a5b8106c6d2513476bc9e3c8ddcad35e66e3f1b20567edbce5f12b661448a811764f9ba4de
7
- data.tar.gz: 8eacb9b0e326b529c743af650920b17e382b379032895d112a83689965a53fe42ae48eb9c070cd86085b1d7d5d9cc7300e0d9199b975edc25ee4aca1f093ecd6
6
+ metadata.gz: 6b4d0189ef85dc508e12fe28642037e38f87dae95039586b4f6369e4ba45526601a5b9e9e9bfe79dc0ec20deb14a1b6f6b7f4cd04b07b379ca654d6f1ba24b14
7
+ data.tar.gz: b17875017e20c0126c8ddc5d360873430c92d69f0633134743198140c2258f828b3b444748c7a4cd8e0bb9137a9990f2dc394182ffcfe6a19d1e0f3795beebcd
data/Gemfile CHANGED
@@ -28,5 +28,6 @@ gem "benchmark-memory"
28
28
  gem "yard", github: "lsegal/yard"
29
29
  gem "webrick"
30
30
  gem "faraday"
31
+ gem "excon"
31
32
 
32
33
  eval_gemfile File.expand_path("../Gemfile", __dir__)
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Excon
5
+ OP_NAME = "http.client"
6
+
7
+ class Middleware < ::Excon::Middleware::Base
8
+ def initialize(stack)
9
+ super
10
+ @instrumenter = Instrumenter.new
11
+ end
12
+
13
+ def request_call(datum)
14
+ @instrumenter.start_transaction(datum)
15
+ @stack.request_call(datum)
16
+ end
17
+
18
+ def response_call(datum)
19
+ @instrumenter.finish_transaction(datum)
20
+ @stack.response_call(datum)
21
+ end
22
+ end
23
+
24
+ class Instrumenter
25
+ SPAN_ORIGIN = "auto.http.excon"
26
+ BREADCRUMB_CATEGORY = "http"
27
+
28
+ include Utils::HttpTracing
29
+
30
+ def start_transaction(env)
31
+ return unless Sentry.initialized?
32
+
33
+ current_span = Sentry.get_current_scope&.span
34
+ @span = current_span&.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN)
35
+
36
+ request_info = extract_request_info(env)
37
+
38
+ if propagate_trace?(request_info[:url])
39
+ set_propagation_headers(env[:headers])
40
+ end
41
+ end
42
+
43
+ def finish_transaction(response)
44
+ return unless @span
45
+
46
+ response_status = response[:response][:status]
47
+ request_info = extract_request_info(response)
48
+
49
+ if record_sentry_breadcrumb?
50
+ record_sentry_breadcrumb(request_info, response_status)
51
+ end
52
+
53
+ set_span_info(@span, request_info, response_status)
54
+ ensure
55
+ @span&.finish
56
+ end
57
+
58
+ private
59
+
60
+ def extract_request_info(env)
61
+ url = env[:scheme] + "://" + env[:hostname] + env[:path]
62
+ result = { method: env[:method].to_s.upcase, url: url }
63
+
64
+ if Sentry.configuration.send_default_pii
65
+ result[:query] = env[:query]
66
+
67
+ # Handle excon 1.0.0+
68
+ result[:query] = build_nested_query(result[:query]) unless result[:query].is_a?(String)
69
+
70
+ result[:body] = env[:body]
71
+ end
72
+
73
+ result
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sentry.register_patch(:excon) do
4
+ if defined?(::Excon)
5
+ require "sentry/excon/middleware"
6
+ if Excon.defaults[:middlewares]
7
+ Excon.defaults[:middlewares] << Sentry::Excon::Middleware unless Excon.defaults[:middlewares].include?(Sentry::Excon::Middleware)
8
+ end
9
+ end
10
+ end
data/lib/sentry/hub.rb CHANGED
@@ -255,7 +255,11 @@ module Sentry
255
255
 
256
256
  return unless session
257
257
  session.close
258
- Sentry.session_flusher.add_session(session)
258
+
259
+ # NOTE: Under some circumstances, session_flusher nilified out of sync
260
+ # See: https://github.com/getsentry/sentry-ruby/issues/2378
261
+ # See: https://github.com/getsentry/sentry-ruby/pull/2396
262
+ Sentry.session_flusher&.add_session(session)
259
263
  end
260
264
 
261
265
  def with_session_tracking(&block)
@@ -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
@@ -10,6 +10,7 @@ module Sentry
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
45
+ aggregates = @mutex.synchronize do
46
+ aggregates = @pending_aggregates.values
47
+ @pending_aggregates = {}
48
+ aggregates
49
+ end
46
50
 
51
+ envelope = Envelope.new
47
52
  header = { type: "sessions" }
48
- payload = { attrs: attrs, aggregates: @pending_aggregates.values }
49
-
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
@@ -44,6 +44,11 @@ module Sentry
44
44
  LINENO = "code.lineno"
45
45
  FUNCTION = "code.function"
46
46
  NAMESPACE = "code.namespace"
47
+
48
+ MESSAGING_MESSAGE_ID = "messaging.message.id"
49
+ MESSAGING_DESTINATION_NAME = "messaging.destination.name"
50
+ MESSAGING_MESSAGE_RECEIVE_LATENCY = "messaging.message.receive.latency"
51
+ MESSAGING_MESSAGE_RETRY_COUNT = "messaging.message.retry.count"
47
52
  end
48
53
 
49
54
  STATUS_MAP = {
@@ -19,7 +19,7 @@ module Sentry
19
19
  crumb = Sentry::Breadcrumb.new(
20
20
  level: :info,
21
21
  category: self.class::BREADCRUMB_CATEGORY,
22
- type: :info,
22
+ type: "info",
23
23
  data: { status: response_status, **request_info }
24
24
  )
25
25
 
@@ -36,6 +36,25 @@ module Sentry
36
36
  Sentry.configuration.propagate_traces &&
37
37
  Sentry.configuration.trace_propagation_targets.any? { |target| url.match?(target) }
38
38
  end
39
+
40
+ # Kindly borrowed from Rack::Utils
41
+ def build_nested_query(value, prefix = nil)
42
+ case value
43
+ when Array
44
+ value.map { |v|
45
+ build_nested_query(v, "#{prefix}[]")
46
+ }.join("&")
47
+ when Hash
48
+ value.map { |k, v|
49
+ build_nested_query(v, prefix ? "#{prefix}[#{k}]" : k)
50
+ }.delete_if(&:empty?).join("&")
51
+ when nil
52
+ URI.encode_www_form_component(prefix)
53
+ else
54
+ raise ArgumentError, "value must be a Hash" if prefix.nil?
55
+ "#{URI.encode_www_form_component(prefix)}=#{URI.encode_www_form_component(value)}"
56
+ end
57
+ end
39
58
  end
40
59
  end
41
60
  end
@@ -55,8 +55,7 @@ module Sentry
55
55
  return unless @sampled
56
56
  return if @started
57
57
 
58
- ::Vernier.start_profile
59
- @started = true
58
+ @started = ::Vernier.start_profile
60
59
 
61
60
  log("Started")
62
61
 
@@ -77,6 +76,12 @@ module Sentry
77
76
  @result = ::Vernier.stop_profile
78
77
 
79
78
  log("Stopped")
79
+ rescue RuntimeError => e
80
+ if e.message.include?("Profile not started")
81
+ log("Not stopped since not started")
82
+ else
83
+ log("Failed to stop Vernier: #{e.message}")
84
+ end
80
85
  end
81
86
 
82
87
  def active_thread_id
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.21.0"
4
+ VERSION = "5.22.1"
5
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -50,6 +50,8 @@ module Sentry
50
50
 
51
51
  THREAD_LOCAL = :sentry_hub
52
52
 
53
+ MUTEX = Mutex.new
54
+
53
55
  class << self
54
56
  # @!visibility private
55
57
  def exception_locals_tp
@@ -275,8 +277,10 @@ module Sentry
275
277
 
276
278
  @background_worker.shutdown
277
279
 
278
- @main_hub = nil
279
- Thread.current.thread_variable_set(THREAD_LOCAL, nil)
280
+ MUTEX.synchronize do
281
+ @main_hub = nil
282
+ Thread.current.thread_variable_set(THREAD_LOCAL, nil)
283
+ end
280
284
  end
281
285
 
282
286
  # Returns true if the SDK is initialized.
@@ -303,7 +307,7 @@ module Sentry
303
307
  #
304
308
  # @return [Hub]
305
309
  def get_main_hub
306
- @main_hub
310
+ MUTEX.synchronize { @main_hub }
307
311
  end
308
312
 
309
313
  # Takes an instance of Sentry::Breadcrumb and stores it to the current active scope.
@@ -610,3 +614,4 @@ require "sentry/redis"
610
614
  require "sentry/puma"
611
615
  require "sentry/graphql"
612
616
  require "sentry/faraday"
617
+ require "sentry/excon"
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.21.0
4
+ version: 5.22.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-07 00:00:00.000000000 Z
11
+ date: 2024-12-16 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.21.0
19
+ version: 5.22.1
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.21.0
26
+ version: 5.22.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: concurrent-ruby
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -81,6 +81,8 @@ files:
81
81
  - lib/sentry/error_event.rb
82
82
  - lib/sentry/event.rb
83
83
  - lib/sentry/exceptions.rb
84
+ - lib/sentry/excon.rb
85
+ - lib/sentry/excon/middleware.rb
84
86
  - lib/sentry/faraday.rb
85
87
  - lib/sentry/graphql.rb
86
88
  - lib/sentry/hub.rb
@@ -115,6 +117,7 @@ files:
115
117
  - lib/sentry/rake.rb
116
118
  - lib/sentry/redis.rb
117
119
  - lib/sentry/release_detector.rb
120
+ - lib/sentry/rspec.rb
118
121
  - lib/sentry/scope.rb
119
122
  - lib/sentry/session.rb
120
123
  - lib/sentry/session_flusher.rb
@@ -164,7 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
167
  - !ruby/object:Gem::Version
165
168
  version: '0'
166
169
  requirements: []
167
- rubygems_version: 3.5.16
170
+ rubygems_version: 3.5.22
168
171
  signing_key:
169
172
  specification_version: 4
170
173
  summary: A gem that provides a client interface for the Sentry error logger