sentry-ruby 6.5.0 → 6.6.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5320b735843e35af15ec05b6581fac549e52e8afa739adf8f03ef33abda95b14
4
- data.tar.gz: 4d15760d10a1093a747d74caa928b6812d8b5b24c81698c01833bc52b5a775a3
3
+ metadata.gz: 3ac6a6802b328b59cb636ac1d91b5b1ab5710e9c1427c3d910e7bd05f30b618b
4
+ data.tar.gz: a77ee9a574da2de452d962848dbfca19aef525304e1bdbf65b21cb11ec7e9e3b
5
5
  SHA512:
6
- metadata.gz: 339ce7c5290eba0b3055de7a5958d44adc370847baca58a14b75a48ee9ab4b72d28bec3ce4aaae996c8848fa5127bb2c3b42775d1324bc3d3556692bdf0f0d9d
7
- data.tar.gz: f3cca1b504d70ac8f9f6e2083826c4f2fbcc7df5bf8da3438daf0a74ec1c7034ba4c0a98859ad291ac7cd453e88ca7be4af2a5722c1bb432f096f56e014ce83d
6
+ metadata.gz: c1f221cea1a011d2540c666e14823423d12fa37f3ea4ee59f3d68035ad36e33dd62fbc8f23712fee46b8da31e3bbb7c027e664927945b959197c15d10626f4e9
7
+ data.tar.gz: 95f32db537d5fa0a95b3897dcd7fc6d34dff884fe474fc9a43d0567d6a3ef6a84cb181f06c9ef60b0e1e132cb5afa28e65faf427c408c518beeb1305ddf9af1f
data/README.md CHANGED
@@ -21,6 +21,7 @@ Sentry SDK for Ruby
21
21
  | [![Gem Version](https://img.shields.io/gem/v/sentry-delayed_job?label=sentry-delayed_job)](https://rubygems.org/gems/sentry-delayed_job) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml) | [![codecov](https://codecov.io/gh/getsentry/sentry-ruby/graph/badge.svg?token=ZePzrpZFP6&component=sentry-delayed_job)](https://codecov.io/gh/getsentry/sentry-ruby) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-delayed_job) |
22
22
  | [![Gem Version](https://img.shields.io/gem/v/sentry-resque?label=sentry-resque)](https://rubygems.org/gems/sentry-resque) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml) | [![codecov](https://codecov.io/gh/getsentry/sentry-ruby/graph/badge.svg?token=ZePzrpZFP6&component=sentry-resque)](https://codecov.io/gh/getsentry/sentry-ruby) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-resque) |
23
23
  | [![Gem Version](https://img.shields.io/gem/v/sentry-opentelemetry?label=sentry-opentelemetry)](https://rubygems.org/gems/sentry-opentelemetry) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml) | [![codecov](https://codecov.io/gh/getsentry/sentry-ruby/graph/badge.svg?token=ZePzrpZFP6&component=sentry-opentelemetry)](https://codecov.io/gh/getsentry/sentry-ruby) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-opentelemetry) |
24
+ | [![Gem Version](https://img.shields.io/gem/v/sentry-yabeda?label=sentry-yabeda)](https://rubygems.org/gems/sentry-yabeda) | [![Build Status](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml/badge.svg)](https://github.com/getsentry/sentry-ruby/actions/workflows/tests.yml) | [![codecov](https://codecov.io/gh/getsentry/sentry-ruby/graph/badge.svg?token=ZePzrpZFP6&component=sentry-yabeda)](https://codecov.io/gh/getsentry/sentry-ruby) | [![API doc](https://img.shields.io/badge/API%20doc-rubydoc.info-blue)](https://www.rubydoc.info/gems/sentry-yabeda) |
24
25
 
25
26
 
26
27
 
@@ -53,6 +54,7 @@ gem "sentry-sidekiq"
53
54
  gem "sentry-delayed_job"
54
55
  gem "sentry-resque"
55
56
  gem "sentry-opentelemetry"
57
+ gem "sentry-yabeda"
56
58
  ```
57
59
 
58
60
  ### Configuration
@@ -93,6 +95,7 @@ To learn more about sampling transactions, please visit the [official documentat
93
95
  - [DelayedJob](https://docs.sentry.io/platforms/ruby/guides/delayed_job/)
94
96
  - [Resque](https://docs.sentry.io/platforms/ruby/guides/resque/)
95
97
  - [OpenTelemetry](https://docs.sentry.io/platforms/ruby/performance/instrumentation/opentelemetry/)
98
+ - [Yabeda](https://docs.sentry.io/platforms/ruby/guides/yabeda/)
96
99
 
97
100
  ### Enriching Events
98
101
 
@@ -6,6 +6,7 @@ module Sentry
6
6
  # Handles backtrace parsing line by line
7
7
  class Line
8
8
  RB_EXTENSION = ".rb"
9
+ CLASS_EXTENSION = ".class"
9
10
  # regexp (optional leading X: on windows, or JRuby9000 class-prefix)
10
11
  RUBY_INPUT_FORMAT = /
11
12
  ^ \s* (?: [a-zA-Z]: | uri:classloader: )? ([^:]+ | <.*>):
@@ -37,12 +38,21 @@ module Sentry
37
38
  ruby_match = unparsed_line.match(RUBY_INPUT_FORMAT)
38
39
 
39
40
  if ruby_match
40
- _, file, number, _, module_name, method = ruby_match.to_a
41
- file.sub!(/\.class$/, RB_EXTENSION)
42
- module_name = module_name
41
+ file = ruby_match[1]
42
+ number = ruby_match[2]
43
+ module_name = ruby_match[4]
44
+ method = ruby_match[5]
45
+ if file.end_with?(CLASS_EXTENSION)
46
+ file.sub!(/\.class$/, RB_EXTENSION)
47
+ end
43
48
  else
44
49
  java_match = unparsed_line.match(JAVA_INPUT_FORMAT)
45
- _, module_name, method, file, number = java_match.to_a
50
+ if java_match
51
+ module_name = java_match[1]
52
+ method = java_match[2]
53
+ file = java_match[3]
54
+ number = java_match[4]
55
+ end
46
56
  end
47
57
  new(file, number, method, module_name, in_app_pattern)
48
58
  end
@@ -74,12 +84,9 @@ module Sentry
74
84
 
75
85
  def in_app
76
86
  return false unless in_app_pattern
87
+ return false unless file
77
88
 
78
- if file =~ in_app_pattern
79
- true
80
- else
81
- false
82
- end
89
+ file.match?(in_app_pattern)
83
90
  end
84
91
 
85
92
  # Reconstructs the line in a readable fashion
@@ -10,14 +10,16 @@ module Sentry
10
10
  # holder for an Array of Backtrace::Line instances
11
11
  attr_reader :lines
12
12
 
13
- def self.parse(backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback)
13
+ # @deprecated project_root, in_app_pattern passed from outside
14
+ # @deprecated app_dirs_pattern, in_app_pattern passed from outside
15
+ def self.parse(backtrace, project_root, app_dirs_pattern, in_app_pattern: nil, &backtrace_cleanup_callback)
14
16
  ruby_lines = backtrace.is_a?(Array) ? backtrace : backtrace.split(/\n\s*/)
15
17
 
16
18
  ruby_lines = backtrace_cleanup_callback.call(ruby_lines) if backtrace_cleanup_callback
17
19
 
18
- in_app_pattern ||= begin
19
- Regexp.new("^(#{project_root}/)?#{app_dirs_pattern}")
20
- end
20
+ # in_app_pattern is now passed in from StacktraceBuilder, so this regex won't be triggered
21
+ # only here for backwards compat and will be deleted
22
+ in_app_pattern ||= Regexp.new("^(#{project_root}/)?#{app_dirs_pattern}")
21
23
 
22
24
  lines = ruby_lines.to_a.map do |unparsed_line|
23
25
  Line.parse(unparsed_line, in_app_pattern)
@@ -461,7 +461,8 @@ module Sentry
461
461
  def callbacks
462
462
  @callbacks ||= {
463
463
  initialize: { before: [], after: [] },
464
- configured: { before: [], after: [] }
464
+ configured: { before: [], after: [] },
465
+ closed: { before: [], after: [] }
465
466
  }
466
467
  end
467
468
 
@@ -798,6 +799,11 @@ module Sentry
798
799
  @errors.join(", ")
799
800
  end
800
801
 
802
+ # @api private
803
+ def run_after_close_callbacks
804
+ run_callbacks(:after, :closed)
805
+ end
806
+
801
807
  private
802
808
 
803
809
  def init_dsn(dsn_string)
data/lib/sentry/hub.rb CHANGED
@@ -54,6 +54,12 @@ module Sentry
54
54
  current_layer&.client
55
55
  end
56
56
 
57
+ # All clients bound across the hub's scope stack, base layer first.
58
+ # @return [Array<Client>]
59
+ def clients
60
+ @stack.map(&:client).compact
61
+ end
62
+
57
63
  def configuration
58
64
  current_client.configuration
59
65
  end
@@ -11,6 +11,9 @@ module Sentry
11
11
  "HTTP_X_FORWARDED_FOR"
12
12
  ].freeze
13
13
 
14
+ # Regex to detect lowercase chars — match? is allocation-free (no MatchData/String)
15
+ LOWERCASE_PATTERN = /[a-z]/.freeze
16
+
14
17
  # See Sentry server default limits at
15
18
  # https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py
16
19
  MAX_BODY_LIMIT = 4096 * 4
@@ -93,7 +96,7 @@ module Sentry
93
96
  next if key == "HTTP_AUTHORIZATION" && !send_default_pii
94
97
 
95
98
  # Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
96
- key = key.sub(/^HTTP_/, "")
99
+ key = key.delete_prefix("HTTP_")
97
100
  key = key.split("_").map(&:capitalize).join("-")
98
101
 
99
102
  memo[key] = Utils::EncodingHelper.encode_to_utf_8(value.to_s)
@@ -108,7 +111,7 @@ module Sentry
108
111
  end
109
112
 
110
113
  def is_skippable_header?(key)
111
- key.upcase != key || # lower-case envs aren't real http headers
114
+ key.match?(LOWERCASE_PATTERN) || # lower-case envs aren't real http headers
112
115
  key == "HTTP_COOKIE" || # Cookies don't go here, they go somewhere else
113
116
  !(key.start_with?("HTTP_") || CONTENT_HEADERS.include?(key))
114
117
  end
@@ -119,12 +122,18 @@ module Sentry
119
122
  # if the request has legitimately sent a Version header themselves.
120
123
  # See: https://github.com/rack/rack/blob/028438f/lib/rack/handler/cgi.rb#L29
121
124
  def is_server_protocol?(key, value, protocol_version)
122
- rack_version = Gem::Version.new(::Rack.release)
123
- return false if rack_version >= Gem::Version.new("3.0")
125
+ return false if self.class.rack_3_or_above?
124
126
 
125
127
  key == "HTTP_VERSION" && value == protocol_version
126
128
  end
127
129
 
130
+ def self.rack_3_or_above?
131
+ return @rack_3_or_above if defined?(@rack_3_or_above)
132
+
133
+ @rack_3_or_above = defined?(::Rack) &&
134
+ Gem::Version.new(::Rack.release) >= Gem::Version.new("3.0")
135
+ end
136
+
128
137
  def filter_and_format_env(env, rack_env_whitelist)
129
138
  return env if rack_env_whitelist.empty?
130
139
 
@@ -27,38 +27,19 @@ module Sentry
27
27
  attr_accessor :abs_path, :context_line, :function, :in_app, :filename,
28
28
  :lineno, :module, :pre_context, :post_context, :vars
29
29
 
30
- def initialize(project_root, line, strip_backtrace_load_path = true)
31
- @project_root = project_root
32
- @strip_backtrace_load_path = strip_backtrace_load_path
33
-
30
+ def initialize(project_root, line, strip_backtrace_load_path = true, filename_cache: nil)
34
31
  @abs_path = line.file
35
32
  @function = line.method if line.method
36
33
  @lineno = line.number
37
34
  @in_app = line.in_app
38
35
  @module = line.module_name if line.module_name
39
- @filename = compute_filename
36
+ @filename = filename_cache&.compute_filename(@abs_path, @in_app, strip_backtrace_load_path)
40
37
  end
41
38
 
42
39
  def to_s
43
40
  "#{@filename}:#{@lineno}"
44
41
  end
45
42
 
46
- def compute_filename
47
- return if abs_path.nil?
48
- return abs_path unless @strip_backtrace_load_path
49
-
50
- prefix =
51
- if under_project_root? && in_app
52
- @project_root
53
- elsif under_project_root?
54
- longest_load_path || @project_root
55
- else
56
- longest_load_path
57
- end
58
-
59
- prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
60
- end
61
-
62
43
  def set_context(linecache, context_lines)
63
44
  return unless abs_path
64
45
 
@@ -76,14 +57,6 @@ module Sentry
76
57
  end
77
58
 
78
59
  private
79
-
80
- def under_project_root?
81
- @project_root && abs_path.start_with?(@project_root)
82
- end
83
-
84
- def longest_load_path
85
- $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
86
- end
87
60
  end
88
61
  end
89
62
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "sentry/utils/filename_cache"
4
+
3
5
  module Sentry
4
6
  class StacktraceBuilder
5
7
  # @return [String]
@@ -20,6 +22,9 @@ module Sentry
20
22
  # @return [Boolean]
21
23
  attr_reader :strip_backtrace_load_path
22
24
 
25
+ # @return [FilenameCache]
26
+ attr_reader :filename_cache
27
+
23
28
  # @param project_root [String]
24
29
  # @param app_dirs_pattern [Regexp, nil]
25
30
  # @param linecache [LineCache]
@@ -46,6 +51,8 @@ module Sentry
46
51
  @context_lines = context_lines
47
52
  @backtrace_cleanup_callback = backtrace_cleanup_callback
48
53
  @strip_backtrace_load_path = strip_backtrace_load_path
54
+ @in_app_pattern = Regexp.new("^(#{project_root}/)?#{app_dirs_pattern}") if app_dirs_pattern
55
+ @filename_cache = FilenameCache.new(project_root)
49
56
  end
50
57
 
51
58
  # Generates a StacktraceInterface with the given backtrace.
@@ -64,13 +71,21 @@ module Sentry
64
71
  # @yieldparam frame [StacktraceInterface::Frame]
65
72
  # @return [StacktraceInterface]
66
73
  def build(backtrace:, &frame_callback)
67
- parsed_lines = parse_backtrace_lines(backtrace).select(&:file)
74
+ parsed_lines = parse_backtrace_lines(backtrace)
75
+
76
+ # Build frames in reverse order, skipping lines without files
77
+ # Single pass instead of select + reverse + map + compact
78
+ frames = []
79
+ i = parsed_lines.size - 1
80
+ while i >= 0
81
+ line = parsed_lines[i]
82
+ i -= 1
83
+ next unless line.file
68
84
 
69
- frames = parsed_lines.reverse.map do |line|
70
85
  frame = convert_parsed_line_into_frame(line)
71
86
  frame = frame_callback.call(frame) if frame_callback
72
- frame
73
- end.compact
87
+ frames << frame if frame
88
+ end
74
89
 
75
90
  StacktraceInterface.new(frames: frames)
76
91
  end
@@ -78,14 +93,15 @@ module Sentry
78
93
  private
79
94
 
80
95
  def convert_parsed_line_into_frame(line)
81
- frame = StacktraceInterface::Frame.new(project_root, line, strip_backtrace_load_path)
96
+ frame = StacktraceInterface::Frame.new(project_root, line, strip_backtrace_load_path, filename_cache: @filename_cache)
82
97
  frame.set_context(linecache, context_lines) if context_lines
83
98
  frame
84
99
  end
85
100
 
86
101
  def parse_backtrace_lines(backtrace)
87
102
  Backtrace.parse(
88
- backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback
103
+ backtrace, project_root, app_dirs_pattern,
104
+ in_app_pattern: @in_app_pattern, &backtrace_cleanup_callback
89
105
  ).lines
90
106
  end
91
107
  end
@@ -12,36 +12,33 @@ module Sentry
12
12
  # file. The number of lines retrieved is (2 * context) + 1, the middle
13
13
  # line should be the line requested by lineno. See specs for more information.
14
14
  def get_file_context(filename, lineno, context)
15
- return nil, nil, nil unless valid_path?(filename)
15
+ lines = getlines(filename)
16
+ return nil, nil, nil unless lines
16
17
 
17
- lines = Array.new(2 * context + 1) do |i|
18
- getline(filename, lineno - context + i)
19
- end
20
- [lines[0..(context - 1)], lines[context], lines[(context + 1)..-1]]
18
+ first_line = lineno - context
19
+ pre = Array.new(context) { |i| line_at(lines, first_line + i) }
20
+ context_line = line_at(lines, lineno)
21
+ post = Array.new(context) { |i| line_at(lines, lineno + 1 + i) }
22
+
23
+ [pre, context_line, post]
21
24
  end
22
25
 
23
26
  private
24
27
 
25
- def valid_path?(path)
26
- lines = getlines(path)
27
- !lines.nil?
28
+ def line_at(lines, n)
29
+ return nil if n < 1
30
+
31
+ lines[n - 1]
28
32
  end
29
33
 
30
34
  def getlines(path)
31
- @cache[path] ||= begin
32
- File.open(path, "r", &:readlines)
33
- rescue
34
- nil
35
+ @cache.fetch(path) do
36
+ @cache[path] = begin
37
+ File.open(path, "r", &:readlines)
38
+ rescue
39
+ nil
40
+ end
35
41
  end
36
42
  end
37
-
38
- def getline(path, n)
39
- return nil if n < 1
40
-
41
- lines = getlines(path)
42
- return nil if lines.nil?
43
-
44
- lines[n - 1]
45
- end
46
43
  end
47
44
  end
@@ -9,28 +9,8 @@ module Sentry
9
9
  abs_path.match?(@in_app_pattern)
10
10
  end
11
11
 
12
- # copied from stacktrace.rb since I don't want to touch existing code
13
- # TODO-neel-profiler try to fetch this from stackprof once we patch
14
- # the native extension
15
12
  def compute_filename(abs_path, in_app)
16
- return nil if abs_path.nil?
17
-
18
- under_project_root = @project_root && abs_path.start_with?(@project_root)
19
-
20
- prefix =
21
- if under_project_root && in_app
22
- @project_root
23
- else
24
- longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
25
-
26
- if under_project_root
27
- longest_load_path || @project_root
28
- else
29
- longest_load_path
30
- end
31
- end
32
-
33
- prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
13
+ @filename_cache.compute_filename(abs_path, in_app, true)
34
14
  end
35
15
 
36
16
  def split_module(name)
@@ -26,6 +26,7 @@ module Sentry
26
26
  @project_root = configuration.project_root
27
27
  @app_dirs_pattern = configuration.app_dirs_pattern
28
28
  @in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
29
+ @filename_cache = configuration.stacktrace_builder.filename_cache
29
30
  end
30
31
 
31
32
  def start
@@ -6,6 +6,7 @@ module Sentry
6
6
  class << self
7
7
  def detect_release(project_root:, running_on_heroku:)
8
8
  detect_release_from_env ||
9
+ detect_release_from_kamal ||
9
10
  detect_release_from_git ||
10
11
  detect_release_from_capistrano(project_root) ||
11
12
  detect_release_from_heroku(running_on_heroku)
@@ -31,6 +32,10 @@ module Sentry
31
32
  Sentry.sys_command("git rev-parse HEAD") if File.directory?(".git")
32
33
  end
33
34
 
35
+ def detect_release_from_kamal
36
+ ENV["KAMAL_VERSION"]
37
+ end
38
+
34
39
  def detect_release_from_env
35
40
  ENV["SENTRY_RELEASE"]
36
41
  end
@@ -37,13 +37,25 @@ module Sentry
37
37
  # - auto_session_tracking
38
38
  block&.call(dummy_config)
39
39
 
40
+ # Install the testing clients on the *main* hub rather than the current
41
+ # thread's hub. `Sentry.clone_hub_to_current_thread` (used by
42
+ # Sentry::Rack::CaptureExceptions) always clones the main hub, so if we
43
+ # only mutated the thread-local hub a request-time clone would observe a
44
+ # stale transport.
45
+ main_hub = Sentry.get_main_hub
46
+
40
47
  # the base layer's client should already use the dummy config so nothing will be sent by accident
41
48
  base_client = Sentry::Client.new(dummy_config)
42
- Sentry.get_current_hub.bind_client(base_client)
49
+ main_hub.bind_client(base_client)
43
50
  # create a new layer so mutations made to the testing scope or configuration could be simply popped later
44
- Sentry.get_current_hub.push_scope
51
+ main_hub.push_scope
45
52
  test_client = Sentry::Client.new(dummy_config.dup)
46
- Sentry.get_current_hub.bind_client(test_client)
53
+ main_hub.bind_client(test_client)
54
+
55
+ # Realign the current thread's hub with the main hub so direct
56
+ # `sentry_events` reads and any hub the Rack middleware clones from the
57
+ # main hub all observe the same DummyTransport.
58
+ Thread.current.thread_variable_set(Sentry::THREAD_LOCAL, main_hub)
47
59
  end
48
60
 
49
61
  # Clears all stored events and envelopes.
@@ -54,11 +66,15 @@ module Sentry
54
66
 
55
67
  clear_sentry_events
56
68
 
57
- # pop testing layer created by `setup_sentry_test`
58
- # but keep the base layer to avoid nil-pointer errors
69
+ # pop the testing layer created by `setup_sentry_test` off the *main*
70
+ # hub (that is where `setup_sentry_test` pushed it), keeping the base
71
+ # layer to avoid nil-pointer errors. Popping the current thread's hub
72
+ # would leave the test layer dangling on the main hub, which the next
73
+ # request-time clone would inherit.
59
74
  # TODO: find a way to notify users if they somehow popped the test layer before calling this method
60
- if Sentry.get_current_hub.instance_variable_get(:@stack).size > 1
61
- Sentry.get_current_hub.pop_scope
75
+ main_hub = Sentry.get_main_hub
76
+ if main_hub.instance_variable_get(:@stack).size > 1
77
+ main_hub.pop_scope
62
78
  end
63
79
  Sentry::Scope.global_event_processors.clear
64
80
  end
@@ -66,7 +82,13 @@ module Sentry
66
82
  def clear_sentry_events
67
83
  return unless Sentry.initialized?
68
84
 
69
- sentry_transport.clear if sentry_transport.respond_to?(:clear)
85
+ # Clear every transport reachable from the current thread's hub and the
86
+ # main hub (including its base layer). A request-time clone shares the
87
+ # main hub's base-layer transport, so clearing only the current
88
+ # transport would let stale events survive into the next test.
89
+ sentry_test_transports.each do |transport|
90
+ transport.clear if transport.respond_to?(:clear)
91
+ end
70
92
 
71
93
  if Sentry.configuration.enable_logs && sentry_logger.respond_to?(:clear)
72
94
  sentry_logger.clear
@@ -83,6 +105,17 @@ module Sentry
83
105
  Sentry.get_current_client.transport
84
106
  end
85
107
 
108
+ # Every transport reachable from the current thread's hub and the main
109
+ # hub, across all stack layers. Used by `clear_sentry_events` so a stale
110
+ # DummyTransport (e.g. the main hub's base layer that a request-time clone
111
+ # shares) cannot carry leftover events into the next test.
112
+ # @return [Array<Transport>]
113
+ def sentry_test_transports
114
+ [Sentry.get_current_hub, Sentry.get_main_hub].compact.uniq.flat_map do |hub|
115
+ hub.clients.map(&:transport)
116
+ end.compact.uniq
117
+ end
118
+
86
119
  # Returns the captured event objects.
87
120
  # @return [Array<Event>]
88
121
  def sentry_events
@@ -18,5 +18,12 @@ module Sentry
18
18
  def send_envelope(envelope)
19
19
  @envelopes << envelope
20
20
  end
21
+
22
+ # Empties the captured events and envelopes so `TestHelper.clear_sentry_events`
23
+ # also clears the dummy transport instance
24
+ def clear
25
+ @events.clear
26
+ @envelopes.clear
27
+ end
21
28
  end
22
29
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ class FilenameCache
5
+ attr_reader :cache
6
+
7
+ def initialize(project_root)
8
+ @project_root = project_root
9
+ @load_paths = $LOAD_PATH.map(&:to_s).sort_by(&:size).reverse.freeze
10
+ @cache = {}
11
+ end
12
+
13
+ def compute_filename(abs_path, in_app, strip_backtrace_load_path)
14
+ return unless abs_path
15
+ return abs_path unless strip_backtrace_load_path
16
+
17
+ @cache.fetch(abs_path) do
18
+ under_root = @project_root && abs_path.start_with?(@project_root)
19
+ prefix =
20
+ if under_root && in_app
21
+ @project_root
22
+ elsif under_root
23
+ longest_load_path(abs_path) || @project_root
24
+ else
25
+ longest_load_path(abs_path)
26
+ end
27
+
28
+ @cache[abs_path] = if prefix
29
+ offset = if prefix.end_with?(File::SEPARATOR)
30
+ prefix.bytesize
31
+ else
32
+ prefix.bytesize + 1
33
+ end
34
+ abs_path.byteslice(offset, abs_path.bytesize - offset)
35
+ else
36
+ abs_path
37
+ end
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def longest_load_path(abs_path)
44
+ @load_paths.find { |path| abs_path.start_with?(path) }
45
+ end
46
+ end
47
+ end
@@ -12,7 +12,13 @@ module Sentry
12
12
  end
13
13
 
14
14
  def set_propagation_headers(req)
15
- Sentry.get_trace_propagation_headers&.each { |k, v| req[k] = v }
15
+ Sentry.get_trace_propagation_headers&.each do |k, v|
16
+ if k == BAGGAGE_HEADER_NAME && req[k]
17
+ req[k] = "#{v},#{req[k]}"
18
+ else
19
+ req[k] = v
20
+ end
21
+ end
16
22
  end
17
23
 
18
24
  def record_sentry_breadcrumb(request_info, response_status)
@@ -10,11 +10,12 @@ module Sentry
10
10
 
11
11
  attr_reader :profile
12
12
 
13
- def initialize(profile, project_root:, in_app_pattern:, app_dirs_pattern:)
13
+ def initialize(profile, project_root:, in_app_pattern:, app_dirs_pattern:, filename_cache:)
14
14
  @profile = profile
15
15
  @project_root = project_root
16
16
  @in_app_pattern = in_app_pattern
17
17
  @app_dirs_pattern = app_dirs_pattern
18
+ @filename_cache = filename_cache
18
19
  end
19
20
 
20
21
  def to_h
@@ -24,6 +24,7 @@ module Sentry
24
24
  @project_root = configuration.project_root
25
25
  @app_dirs_pattern = configuration.app_dirs_pattern
26
26
  @in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
27
+ @filename_cache = configuration.stacktrace_builder.filename_cache
27
28
  end
28
29
 
29
30
  def set_initial_sample_decision(transaction_sampled)
@@ -125,7 +126,8 @@ module Sentry
125
126
  result,
126
127
  project_root: @project_root,
127
128
  app_dirs_pattern: @app_dirs_pattern,
128
- in_app_pattern: @in_app_pattern
129
+ in_app_pattern: @in_app_pattern,
130
+ filename_cache: @filename_cache
129
131
  )
130
132
  end
131
133
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "6.5.0"
4
+ VERSION = "6.6.0"
5
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -268,6 +268,7 @@ module Sentry
268
268
  end
269
269
 
270
270
  if client = get_current_client
271
+ client.configuration.run_after_close_callbacks
271
272
  client.flush
272
273
 
273
274
  if client.configuration.include_local_variables
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentry-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.5.0
4
+ version: 6.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
@@ -157,6 +157,7 @@ files:
157
157
  - lib/sentry/utils/encoding_helper.rb
158
158
  - lib/sentry/utils/env_helper.rb
159
159
  - lib/sentry/utils/exception_cause_chain.rb
160
+ - lib/sentry/utils/filename_cache.rb
160
161
  - lib/sentry/utils/http_tracing.rb
161
162
  - lib/sentry/utils/logging_helper.rb
162
163
  - lib/sentry/utils/real_ip.rb
@@ -169,15 +170,15 @@ files:
169
170
  - lib/sentry/version.rb
170
171
  - sentry-ruby-core.gemspec
171
172
  - sentry-ruby.gemspec
172
- homepage: https://github.com/getsentry/sentry-ruby/tree/6.5.0/sentry-ruby
173
+ homepage: https://github.com/getsentry/sentry-ruby/tree/6.6.0/sentry-ruby
173
174
  licenses:
174
175
  - MIT
175
176
  metadata:
176
- homepage_uri: https://github.com/getsentry/sentry-ruby/tree/6.5.0/sentry-ruby
177
- source_code_uri: https://github.com/getsentry/sentry-ruby/tree/6.5.0/sentry-ruby
178
- changelog_uri: https://github.com/getsentry/sentry-ruby/blob/6.5.0/CHANGELOG.md
177
+ homepage_uri: https://github.com/getsentry/sentry-ruby/tree/6.6.0/sentry-ruby
178
+ source_code_uri: https://github.com/getsentry/sentry-ruby/tree/6.6.0/sentry-ruby
179
+ changelog_uri: https://github.com/getsentry/sentry-ruby/blob/6.6.0/CHANGELOG.md
179
180
  bug_tracker_uri: https://github.com/getsentry/sentry-ruby/issues
180
- documentation_uri: http://www.rubydoc.info/gems/sentry-ruby/6.5.0
181
+ documentation_uri: http://www.rubydoc.info/gems/sentry-ruby/6.6.0
181
182
  rdoc_options: []
182
183
  require_paths:
183
184
  - lib