sentry-ruby-core 4.2.2 → 4.3.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: 927808cee7826c8aa7535511858722699330cd16b430ad68b7a7b79aaf04546d
4
- data.tar.gz: 779f0e49611a24e1149fa551ff8988e15ac684bb2e9eb0c70a6c96b33501086d
3
+ metadata.gz: 66ef212c06425a29b56b64935c1fb662633fa80fb9f58b9680b1d95220ee8b5d
4
+ data.tar.gz: 18e211d0c93d18717795b23290c2cb7da503b0dcd1690c1c8b51861c3a37ef98
5
5
  SHA512:
6
- metadata.gz: d629d892096a9de47f89f62c62487eb536c059c0ce14f6263aa605b50c2eddd0d5ecb48ed5da094444e58a889b8800b3333456cfa3c5e3e6afd4b6ea0512f081
7
- data.tar.gz: 19a781d62efd5af40367146c976e2a1d886c59fbeddc6d67ad3e4efee15744ef34efd0769abb39317ee6b94d768394f7a6cf468a539d44894e595bd384ffc4cb
6
+ metadata.gz: e334875a26a04e45a78c37e7bb0014604cd2b400b89eb01faa3ae21adc1f9072e46227ce880c1406148e32d6a125504dc08ffab5e1ac61c0b3befbd31e5352ea
7
+ data.tar.gz: 4c01fb8d489a3d143a738180f3f68ba62d3882267ecf17b82c3cf0e509ab36a8b458699d022d5d55699baacaccd2acc3bfede0d332ae5b50776128ac5c164a2a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.3.0
4
+
5
+ ### Features
6
+
7
+ - Allow configuring BreadcrumbBuffer's size limit [#1310](https://github.com/getsentry/sentry-ruby/pull/1310)
8
+
9
+ ```ruby
10
+ # the SDK will only store 10 breadcrumbs (default is 100)
11
+ config.max_breadcrumbs = 10
12
+ ```
13
+
14
+ - Compress event payload by default [#1314](https://github.com/getsentry/sentry-ruby/pull/1314)
15
+
16
+ ### Refatorings
17
+
18
+ - Refactor interface construction [#1296](https://github.com/getsentry/sentry-ruby/pull/1296)
19
+ - Refactor tracing implementation [#1309](https://github.com/getsentry/sentry-ruby/pull/1309)
20
+
21
+ ### Bug Fixes
22
+ - Improve SDK's error handling [#1298](https://github.com/getsentry/sentry-ruby/pull/1298)
23
+ - Fixes [#1246](https://github.com/getsentry/sentry-ruby/issues/1246) and [#1289](https://github.com/getsentry/sentry-ruby/issues/1289)
24
+ - Please read [#1290](https://github.com/getsentry/sentry-ruby/issues/1290) to see the full specification
25
+ - Treat query string as pii too [#1302](https://github.com/getsentry/sentry-ruby/pull/1302)
26
+ - Fixes [#1301](https://github.com/getsentry/sentry-ruby/issues/1301)
27
+ - Ignore sentry-trace when tracing is not enabled [#1308](https://github.com/getsentry/sentry-ruby/pull/1308)
28
+ - Fixes [#1307](https://github.com/getsentry/sentry-ruby/issues/1307)
29
+ - Return nil from logger methods instead of breadcrumb buffer [#1299](https://github.com/getsentry/sentry-ruby/pull/1299)
30
+ - Exceptions with nil message shouldn't cause issues [#1327](https://github.com/getsentry/sentry-ruby/pull/1327)
31
+ - Fixes [#1323](https://github.com/getsentry/sentry-ruby/issues/1323)
32
+ - Fix sampling decision with sentry-trace and add more tests [#1326](https://github.com/getsentry/sentry-ruby/pull/1326)
33
+
3
34
  ## 4.2.2
4
35
 
5
36
  - Add thread_id to Exception interface [#1291](https://github.com/getsentry/sentry-ruby/pull/1291)
data/README.md CHANGED
@@ -2,10 +2,14 @@
2
2
  <a href="https://sentry.io" target="_blank" align="center">
3
3
  <img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" width="280">
4
4
  </a>
5
- <br>
5
+ <br />
6
6
  </p>
7
7
 
8
- # sentry-ruby, the Ruby Client for Sentry
8
+ _Bad software is everywhere, and we're tired of it. Sentry is on a mission to help developers write better software faster, so we can get back to enjoying technology. If you want to join us [<kbd>**Check out our open positions**</kbd>](https://sentry.io/careers/)_
9
+
10
+ Sentry SDK for Ruby
11
+ ===========
12
+
9
13
 
10
14
  **The old `sentry-raven` client has entered maintenance mode and was moved to [here](https://github.com/getsentry/sentry-ruby/tree/master/sentry-raven).**
11
15
 
data/lib/sentry-ruby.rb CHANGED
@@ -3,6 +3,7 @@ require "forwardable"
3
3
  require "time"
4
4
 
5
5
  require "sentry/version"
6
+ require "sentry/exceptions"
6
7
  require "sentry/core_ext/object/deep_dup"
7
8
  require "sentry/utils/argument_checking_helper"
8
9
  require "sentry/configuration"
@@ -26,9 +27,6 @@ require "sentry/background_worker"
26
27
  end
27
28
 
28
29
  module Sentry
29
- class Error < StandardError
30
- end
31
-
32
30
  META = { "name" => "sentry.ruby", "version" => Sentry::VERSION }.freeze
33
31
 
34
32
  LOGGER_PROGNAME = "sentry".freeze
@@ -68,7 +66,7 @@ module Sentry
68
66
  config = Configuration.new
69
67
  yield(config) if block_given?
70
68
  client = Client.new(config)
71
- scope = Scope.new
69
+ scope = Scope.new(max_breadcrumbs: config.max_breadcrumbs)
72
70
  hub = Hub.new(client, scope)
73
71
  Thread.current[THREAD_LOCAL] = hub
74
72
  @main_hub = hub
@@ -1,5 +1,6 @@
1
1
  require "concurrent/executor/thread_pool_executor"
2
2
  require "concurrent/executor/immediate_executor"
3
+ require "concurrent/configuration"
3
4
 
4
5
  module Sentry
5
6
  class BackgroundWorker
@@ -14,6 +14,7 @@ module Sentry
14
14
  def add(*args, &block)
15
15
  super
16
16
  add_breadcrumb(*args, &block)
17
+ nil
17
18
  end
18
19
 
19
20
  def add_breadcrumb(severity, message = nil, progname = nil)
@@ -2,12 +2,13 @@ require "sentry/breadcrumb"
2
2
 
3
3
  module Sentry
4
4
  class BreadcrumbBuffer
5
+ DEFAULT_SIZE = 100
5
6
  include Enumerable
6
7
 
7
8
  attr_accessor :buffer
8
9
 
9
- def initialize(size = 100)
10
- @buffer = Array.new(size)
10
+ def initialize(size = nil)
11
+ @buffer = Array.new(size || DEFAULT_SIZE)
11
12
  end
12
13
 
13
14
  def record(crumb)
data/lib/sentry/client.rb CHANGED
@@ -2,10 +2,11 @@ require "sentry/transport"
2
2
 
3
3
  module Sentry
4
4
  class Client
5
- attr_reader :transport, :configuration
5
+ attr_reader :transport, :configuration, :logger
6
6
 
7
7
  def initialize(configuration)
8
8
  @configuration = configuration
9
+ @logger = configuration.logger
9
10
 
10
11
  if transport_class = configuration.transport.transport_class
11
12
  @transport = transport_class.new(configuration)
@@ -26,32 +27,17 @@ module Sentry
26
27
  scope.apply_to_event(event, hint)
27
28
 
28
29
  if async_block = configuration.async
29
- begin
30
- # We have to convert to a JSON-like hash, because background job
31
- # processors (esp ActiveJob) may not like weird types in the event hash
32
- event_hash = event.to_json_compatible
33
-
34
- if async_block.arity == 2
35
- hint = JSON.parse(JSON.generate(hint))
36
- async_block.call(event_hash, hint)
37
- else
38
- async_block.call(event_hash)
39
- end
40
- rescue => e
41
- configuration.logger.error(LOGGER_PROGNAME) { "async event sending failed: #{e.message}" }
42
- send_event(event, hint)
43
- end
30
+ dispatch_async_event(async_block, event, hint)
31
+ elsif hint.fetch(:background, true)
32
+ dispatch_background_event(event, hint)
44
33
  else
45
- if hint.fetch(:background, true)
46
- Sentry.background_worker.perform do
47
- send_event(event, hint)
48
- end
49
- else
50
- send_event(event, hint)
51
- end
34
+ send_event(event, hint)
52
35
  end
53
36
 
54
37
  event
38
+ rescue => e
39
+ logger.error(LOGGER_PROGNAME) { "Event capturing failed: #{e.message}" }
40
+ nil
55
41
  end
56
42
 
57
43
  def event_from_exception(exception, hint = {})
@@ -85,16 +71,49 @@ module Sentry
85
71
 
86
72
  def send_event(event, hint = nil)
87
73
  event_type = event.is_a?(Event) ? event.type : event["type"]
88
- event = configuration.before_send.call(event, hint) if configuration.before_send && event_type == "event"
89
74
 
90
- if event.nil?
91
- configuration.logger.info(LOGGER_PROGNAME) { "Discarded event because before_send returned nil" }
92
- return
75
+ if event_type == "event" && configuration.before_send
76
+ event = configuration.before_send.call(event, hint)
77
+
78
+ if event.nil?
79
+ logger.info(LOGGER_PROGNAME) { "Discarded event because before_send returned nil" }
80
+ return
81
+ end
93
82
  end
94
83
 
95
84
  transport.send_event(event)
96
85
 
97
86
  event
87
+ rescue => e
88
+ logger.error(LOGGER_PROGNAME) { "#{event_type.capitalize} sending failed: #{e.message}" }
89
+ logger.error(LOGGER_PROGNAME) { "Unreported #{event_type.capitalize}: #{Event.get_log_message(event.to_hash)}" }
90
+ raise
98
91
  end
92
+
93
+ private
94
+
95
+ def dispatch_background_event(event, hint)
96
+ Sentry.background_worker.perform do
97
+ send_event(event, hint)
98
+ end
99
+ end
100
+
101
+ def dispatch_async_event(async_block, event, hint)
102
+ # We have to convert to a JSON-like hash, because background job
103
+ # processors (esp ActiveJob) may not like weird types in the event hash
104
+ event_hash = event.to_json_compatible
105
+
106
+ if async_block.arity == 2
107
+ hint = JSON.parse(JSON.generate(hint))
108
+ async_block.call(event_hash, hint)
109
+ else
110
+ async_block.call(event_hash)
111
+ end
112
+ rescue => e
113
+ event_type = event_hash["type"]
114
+ logger.error(LOGGER_PROGNAME) { "Async #{event_type} sending failed: #{e.message}" }
115
+ send_event(event, hint)
116
+ end
117
+
99
118
  end
100
119
  end
@@ -4,6 +4,7 @@ require "sentry/utils/exception_cause_chain"
4
4
  require "sentry/dsn"
5
5
  require "sentry/transport/configuration"
6
6
  require "sentry/linecache"
7
+ require "sentry/interfaces/stacktrace_builder"
7
8
 
8
9
  module Sentry
9
10
  class Configuration
@@ -61,6 +62,9 @@ module Sentry
61
62
  # - :active_support_logger
62
63
  attr_reader :breadcrumbs_logger
63
64
 
65
+ # Max number of breadcrumbs a breadcrumb buffer can hold
66
+ attr_accessor :max_breadcrumbs
67
+
64
68
  # Number of lines of code context to capture, or nil for none
65
69
  attr_accessor :context_lines
66
70
 
@@ -171,6 +175,7 @@ module Sentry
171
175
 
172
176
  def initialize
173
177
  self.background_worker_threads = Concurrent.processor_count
178
+ self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
174
179
  self.breadcrumbs_logger = []
175
180
  self.context_lines = 3
176
181
  self.environment = environment_from_env
@@ -195,6 +200,7 @@ module Sentry
195
200
 
196
201
  @transport = Transport::Configuration.new
197
202
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
203
+
198
204
  run_post_initialization_callbacks
199
205
  end
200
206
 
@@ -290,6 +296,16 @@ module Sentry
290
296
  !!((@traces_sample_rate && @traces_sample_rate > 0.0) || @traces_sampler)
291
297
  end
292
298
 
299
+ def stacktrace_builder
300
+ @stacktrace_builder ||= StacktraceBuilder.new(
301
+ project_root: @project_root.to_s,
302
+ app_dirs_pattern: @app_dirs_pattern,
303
+ linecache: @linecache,
304
+ context_lines: @context_lines,
305
+ backtrace_cleanup_callback: @backtrace_cleanup_callback
306
+ )
307
+ end
308
+
293
309
  private
294
310
 
295
311
  def detect_release
data/lib/sentry/event.rb CHANGED
@@ -20,7 +20,7 @@ module Sentry
20
20
  MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
21
21
 
22
22
  attr_accessor(*ATTRIBUTES)
23
- attr_reader :configuration, :request, :exception, :stacktrace, :threads
23
+ attr_reader :configuration, :request, :exception, :threads
24
24
 
25
25
  def initialize(configuration:, integration_meta: nil, message: nil)
26
26
  # this needs to go first because some setters rely on configuration
@@ -52,19 +52,26 @@ module Sentry
52
52
  class << self
53
53
  def get_log_message(event_hash)
54
54
  message = event_hash[:message] || event_hash['message']
55
- message = get_message_from_exception(event_hash) if message.nil? || message.empty?
56
- message = '<no message value>' if message.nil? || message.empty?
57
- message
55
+
56
+ return message unless message.nil? || message.empty?
57
+
58
+ message = get_message_from_exception(event_hash)
59
+
60
+ return message unless message.nil? || message.empty?
61
+
62
+ message = event_hash[:transaction] || event_hash["transaction"]
63
+
64
+ return message unless message.nil? || message.empty?
65
+
66
+ '<no message value>'
58
67
  end
59
68
 
60
69
  def get_message_from_exception(event_hash)
61
- (
62
- event_hash &&
63
- event_hash[:exception] &&
64
- event_hash[:exception][:values] &&
65
- event_hash[:exception][:values][0] &&
66
- "#{event_hash[:exception][:values][0][:type]}: #{event_hash[:exception][:values][0][:value]}"
67
- )
70
+ if exception = event_hash.dig(:exception, :values, 0)
71
+ "#{exception[:type]}: #{exception[:value]}"
72
+ elsif exception = event_hash.dig("exception", "values", 0)
73
+ "#{exception["type"]}: #{exception["value"]}"
74
+ end
68
75
  end
69
76
  end
70
77
 
@@ -99,7 +106,6 @@ module Sentry
99
106
  def to_hash
100
107
  data = serialize_attributes
101
108
  data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
102
- data[:stacktrace] = stacktrace.to_hash if stacktrace
103
109
  data[:request] = request.to_hash if request
104
110
  data[:exception] = exception.to_hash if exception
105
111
  data[:threads] = threads.to_hash if threads
@@ -112,48 +118,23 @@ module Sentry
112
118
  end
113
119
 
114
120
  def add_request_interface(env)
115
- @request = Sentry::RequestInterface.from_rack(env)
121
+ @request = Sentry::RequestInterface.build(env: env)
116
122
  end
117
123
 
118
124
  def add_threads_interface(backtrace: nil, **options)
119
- @threads = ThreadsInterface.new(**options)
120
- @threads.stacktrace = initialize_stacktrace_interface(backtrace) if backtrace
125
+ @threads = ThreadsInterface.build(
126
+ backtrace: backtrace,
127
+ stacktrace_builder: configuration.stacktrace_builder,
128
+ **options
129
+ )
121
130
  end
122
131
 
123
- def add_exception_interface(exc)
124
- if exc.respond_to?(:sentry_context)
125
- @extra.merge!(exc.sentry_context)
132
+ def add_exception_interface(exception)
133
+ if exception.respond_to?(:sentry_context)
134
+ @extra.merge!(exception.sentry_context)
126
135
  end
127
136
 
128
- @exception = Sentry::ExceptionInterface.new.tap do |exc_int|
129
- exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exc).reverse
130
- backtraces = Set.new
131
- exc_int.values = exceptions.map do |e|
132
- SingleExceptionInterface.new.tap do |int|
133
- int.type = e.class.to_s
134
- int.value = e.message.byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
135
- int.module = e.class.to_s.split('::')[0...-1].join('::')
136
- int.thread_id = Thread.current.object_id
137
-
138
- int.stacktrace =
139
- if e.backtrace && !backtraces.include?(e.backtrace.object_id)
140
- backtraces << e.backtrace.object_id
141
- initialize_stacktrace_interface(e.backtrace)
142
- end
143
- end
144
- end
145
- end
146
- end
147
-
148
- def initialize_stacktrace_interface(backtrace)
149
- StacktraceInterface.new(
150
- backtrace: backtrace,
151
- project_root: configuration.project_root.to_s,
152
- app_dirs_pattern: configuration.app_dirs_pattern,
153
- linecache: configuration.linecache,
154
- context_lines: configuration.context_lines,
155
- backtrace_cleanup_callback: configuration.backtrace_cleanup_callback
156
- )
137
+ @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: configuration.stacktrace_builder)
157
138
  end
158
139
 
159
140
  private
@@ -0,0 +1,7 @@
1
+ module Sentry
2
+ class Error < StandardError
3
+ end
4
+
5
+ class ExternalError < StandardError
6
+ end
7
+ end
data/lib/sentry/hub.rb CHANGED
@@ -69,9 +69,11 @@ module Sentry
69
69
  @stack.pop
70
70
  end
71
71
 
72
- def start_transaction(transaction: nil, **options)
72
+ def start_transaction(transaction: nil, configuration: Sentry.configuration, **options)
73
+ return unless configuration.tracing_enabled?
74
+
73
75
  transaction ||= Transaction.new(**options)
74
- transaction.set_initial_sample_desicion
76
+ transaction.set_initial_sample_decision(configuration: current_client.configuration)
75
77
  transaction
76
78
  end
77
79
 
@@ -1,11 +1,29 @@
1
1
  module Sentry
2
2
  class ExceptionInterface < Interface
3
- attr_accessor :values
3
+ def initialize(values:)
4
+ @values = values
5
+ end
4
6
 
5
7
  def to_hash
6
8
  data = super
7
9
  data[:values] = data[:values].map(&:to_hash) if data[:values]
8
10
  data
9
11
  end
12
+
13
+ def self.build(exception:, stacktrace_builder:)
14
+ exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exception).reverse
15
+ processed_backtrace_ids = Set.new
16
+
17
+ exceptions = exceptions.map do |e|
18
+ if e.backtrace && !processed_backtrace_ids.include?(e.backtrace.object_id)
19
+ processed_backtrace_ids << e.backtrace.object_id
20
+ SingleExceptionInterface.build_with_stacktrace(exception: e, stacktrace_builder: stacktrace_builder)
21
+ else
22
+ SingleExceptionInterface.new(exception: exception)
23
+ end
24
+ end
25
+
26
+ new(values: exceptions)
27
+ end
10
28
  end
11
29
  end
@@ -17,10 +17,10 @@ module Sentry
17
17
 
18
18
  attr_accessor :url, :method, :data, :query_string, :cookies, :headers, :env
19
19
 
20
- def self.from_rack(env)
20
+ def self.build(env:)
21
21
  env = clean_env(env)
22
- req = ::Rack::Request.new(env)
23
- self.new(req)
22
+ request = ::Rack::Request.new(env)
23
+ self.new(request: request)
24
24
  end
25
25
 
26
26
  def self.clean_env(env)
@@ -34,17 +34,17 @@ module Sentry
34
34
  env
35
35
  end
36
36
 
37
- def initialize(req)
38
- env = req.env
37
+ def initialize(request:)
38
+ env = request.env
39
39
 
40
40
  if Sentry.configuration.send_default_pii
41
- self.data = read_data_from(req)
42
- self.cookies = req.cookies
41
+ self.data = read_data_from(request)
42
+ self.cookies = request.cookies
43
+ self.query_string = request.query_string
43
44
  end
44
45
 
45
- self.url = req.scheme && req.url.split('?').first
46
- self.method = req.request_method
47
- self.query_string = req.query_string
46
+ self.url = request.scheme && request.url.split('?').first
47
+ self.method = request.request_method
48
48
 
49
49
  self.headers = filter_and_format_headers(env)
50
50
  self.env = filter_and_format_env(env)
@@ -1,15 +1,26 @@
1
1
  module Sentry
2
2
  class SingleExceptionInterface < Interface
3
- attr_accessor :type
4
- attr_accessor :value
5
- attr_accessor :module
6
- attr_accessor :thread_id
7
- attr_accessor :stacktrace
3
+ attr_reader :type, :value, :module, :thread_id, :stacktrace
4
+
5
+ def initialize(exception:, stacktrace: nil)
6
+ @type = exception.class.to_s
7
+ @value = (exception.message || "").byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
8
+ @module = exception.class.to_s.split('::')[0...-1].join('::')
9
+ @thread_id = Thread.current.object_id
10
+ @stacktrace = stacktrace
11
+ end
8
12
 
9
13
  def to_hash
10
14
  data = super
11
15
  data[:stacktrace] = data[:stacktrace].to_hash if data[:stacktrace]
12
16
  data
13
17
  end
18
+
19
+ # patch this method if you want to change an exception's stacktrace frames
20
+ # also see `StacktraceBuilder.build`.
21
+ def self.build_with_stacktrace(exception:, stacktrace_builder:)
22
+ stacktrace = stacktrace_builder.build(backtrace: exception.backtrace)
23
+ new(exception: exception, stacktrace: stacktrace)
24
+ end
14
25
  end
15
26
  end
@@ -2,18 +2,8 @@ module Sentry
2
2
  class StacktraceInterface
3
3
  attr_reader :frames
4
4
 
5
- def initialize(backtrace:, project_root:, app_dirs_pattern:, linecache:, context_lines:, backtrace_cleanup_callback: nil)
6
- @project_root = project_root
7
- @frames = []
8
-
9
- parsed_backtrace_lines = Backtrace.parse(
10
- backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback
11
- ).lines
12
-
13
- parsed_backtrace_lines.reverse.each_with_object(@frames) do |line, frames|
14
- frame = convert_parsed_line_into_frame(line, project_root, linecache, context_lines)
15
- frames << frame if frame.filename
16
- end
5
+ def initialize(frames:)
6
+ @frames = frames
17
7
  end
18
8
 
19
9
  def to_hash
@@ -22,30 +12,24 @@ module Sentry
22
12
 
23
13
  private
24
14
 
25
- def convert_parsed_line_into_frame(line, project_root, linecache, context_lines)
26
- frame = StacktraceInterface::Frame.new(@project_root, line)
27
- frame.set_context(linecache, context_lines) if context_lines
28
- frame
29
- end
30
-
31
15
  # Not actually an interface, but I want to use the same style
32
16
  class Frame < Interface
33
- attr_accessor :abs_path, :context_line, :function, :in_app,
34
- :lineno, :module, :pre_context, :post_context, :vars
17
+ attr_accessor :abs_path, :context_line, :function, :in_app, :filename,
18
+ :lineno, :module, :pre_context, :post_context, :vars
35
19
 
36
20
  def initialize(project_root, line)
37
21
  @project_root = project_root
38
22
 
39
- @abs_path = line.file if line.file
23
+ @abs_path = line.file
40
24
  @function = line.method if line.method
41
25
  @lineno = line.number
42
26
  @in_app = line.in_app
43
27
  @module = line.module_name if line.module_name
28
+ @filename = compute_filename
44
29
  end
45
30
 
46
- def filename
31
+ def compute_filename
47
32
  return if abs_path.nil?
48
- return @filename if instance_variable_defined?(:@filename)
49
33
 
50
34
  prefix =
51
35
  if under_project_root? && in_app
@@ -56,19 +40,18 @@ module Sentry
56
40
  longest_load_path
57
41
  end
58
42
 
59
- @filename = prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
43
+ prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
60
44
  end
61
45
 
62
46
  def set_context(linecache, context_lines)
63
47
  return unless abs_path
64
48
 
65
- self.pre_context, self.context_line, self.post_context = \
49
+ @pre_context, @context_line, @post_context = \
66
50
  linecache.get_file_context(abs_path, lineno, context_lines)
67
51
  end
68
52
 
69
53
  def to_hash(*args)
70
54
  data = super(*args)
71
- data[:filename] = filename
72
55
  data.delete(:vars) unless vars && !vars.empty?
73
56
  data.delete(:pre_context) unless pre_context && !pre_context.empty?
74
57
  data.delete(:post_context) unless post_context && !post_context.empty?
@@ -0,0 +1,50 @@
1
+ module Sentry
2
+ class StacktraceBuilder
3
+ attr_reader :project_root, :app_dirs_pattern, :linecache, :context_lines, :backtrace_cleanup_callback
4
+
5
+ def initialize(project_root:, app_dirs_pattern:, linecache:, context_lines:, backtrace_cleanup_callback: nil)
6
+ @project_root = project_root
7
+ @app_dirs_pattern = app_dirs_pattern
8
+ @linecache = linecache
9
+ @context_lines = context_lines
10
+ @backtrace_cleanup_callback = backtrace_cleanup_callback
11
+ end
12
+
13
+ # you can pass a block to customize/exclude frames:
14
+ #
15
+ # ```ruby
16
+ # builder.build(backtrace) do |frame|
17
+ # if frame.module.match?(/a_gem/)
18
+ # nil
19
+ # else
20
+ # frame
21
+ # end
22
+ # end
23
+ # ```
24
+ def build(backtrace:, &frame_callback)
25
+ parsed_lines = parse_backtrace_lines(backtrace).select(&:file)
26
+
27
+ frames = parsed_lines.reverse.map do |line|
28
+ frame = convert_parsed_line_into_frame(line)
29
+ frame = frame_callback.call(frame) if frame_callback
30
+ frame
31
+ end.compact
32
+
33
+ StacktraceInterface.new(frames: frames)
34
+ end
35
+
36
+ private
37
+
38
+ def convert_parsed_line_into_frame(line)
39
+ frame = StacktraceInterface::Frame.new(project_root, line)
40
+ frame.set_context(linecache, context_lines) if context_lines
41
+ frame
42
+ end
43
+
44
+ def parse_backtrace_lines(backtrace)
45
+ Backtrace.parse(
46
+ backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback
47
+ ).lines
48
+ end
49
+ end
50
+ end
@@ -1,12 +1,11 @@
1
1
  module Sentry
2
2
  class ThreadsInterface
3
- attr_accessor :stacktrace
4
-
5
- def initialize(crashed: false)
3
+ def initialize(crashed: false, stacktrace: nil)
6
4
  @id = Thread.current.object_id
7
5
  @name = Thread.current.name
8
6
  @current = true
9
7
  @crashed = crashed
8
+ @stacktrace = stacktrace
10
9
  end
11
10
 
12
11
  def to_hash
@@ -22,5 +21,12 @@ module Sentry
22
21
  ]
23
22
  }
24
23
  end
24
+
25
+ # patch this method if you want to change a threads interface's stacktrace frames
26
+ # also see `StacktraceBuilder.build`.
27
+ def self.build(backtrace:, stacktrace_builder:, **options)
28
+ stacktrace = stacktrace_builder.build(backtrace: backtrace) if backtrace
29
+ new(**options, stacktrace: stacktrace)
30
+ end
25
31
  end
26
32
  end
@@ -16,27 +16,24 @@ module Sentry
16
16
  scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
17
17
  scope.set_rack_env(env)
18
18
 
19
- sentry_trace = env["HTTP_SENTRY_TRACE"]
20
- span = Sentry::Transaction.from_sentry_trace(sentry_trace, name: scope.transaction_name, op: transaction_op) if sentry_trace
21
- span ||= Sentry.start_transaction(name: scope.transaction_name, op: transaction_op)
22
-
23
- scope.set_span(span)
19
+ transaction = start_transaction(env, scope)
20
+ scope.set_span(transaction) if transaction
24
21
 
25
22
  begin
26
23
  response = @app.call(env)
27
24
  rescue Sentry::Error
28
- finish_span(span, 500)
25
+ finish_transaction(transaction, 500)
29
26
  raise # Don't capture Sentry errors
30
27
  rescue Exception => e
31
28
  capture_exception(e)
32
- finish_span(span, 500)
29
+ finish_transaction(transaction, 500)
33
30
  raise
34
31
  end
35
32
 
36
33
  exception = collect_exception(env)
37
34
  capture_exception(exception) if exception
38
35
 
39
- finish_span(span, response[0])
36
+ finish_transaction(transaction, response[0])
40
37
 
41
38
  response
42
39
  end
@@ -56,9 +53,19 @@ module Sentry
56
53
  Sentry.capture_exception(exception)
57
54
  end
58
55
 
59
- def finish_span(span, status_code)
60
- span.set_http_status(status_code)
61
- span.finish
56
+ def start_transaction(env, scope)
57
+ sentry_trace = env["HTTP_SENTRY_TRACE"]
58
+ options = { name: scope.transaction_name, op: transaction_op }
59
+ transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, **options) if sentry_trace
60
+ Sentry.start_transaction(transaction: transaction, **options)
61
+ end
62
+
63
+
64
+ def finish_transaction(transaction, status_code)
65
+ return unless transaction
66
+
67
+ transaction.set_http_status(status_code)
68
+ transaction.finish
62
69
  end
63
70
  end
64
71
  end
data/lib/sentry/scope.rb CHANGED
@@ -9,7 +9,8 @@ module Sentry
9
9
 
10
10
  attr_reader(*ATTRIBUTES)
11
11
 
12
- def initialize
12
+ def initialize(max_breadcrumbs: nil)
13
+ @max_breadcrumbs = max_breadcrumbs
13
14
  set_default_value
14
15
  end
15
16
 
@@ -47,7 +48,7 @@ module Sentry
47
48
  end
48
49
 
49
50
  def clear_breadcrumbs
50
- @breadcrumbs = BreadcrumbBuffer.new
51
+ set_new_breadcrumb_buffer
51
52
  end
52
53
 
53
54
  def dup
@@ -171,7 +172,6 @@ module Sentry
171
172
  private
172
173
 
173
174
  def set_default_value
174
- @breadcrumbs = BreadcrumbBuffer.new
175
175
  @contexts = { :os => self.class.os_context, :runtime => self.class.runtime_context }
176
176
  @extra = {}
177
177
  @tags = {}
@@ -182,8 +182,14 @@ module Sentry
182
182
  @event_processors = []
183
183
  @rack_env = {}
184
184
  @span = nil
185
+ set_new_breadcrumb_buffer
185
186
  end
186
187
 
188
+ def set_new_breadcrumb_buffer
189
+ @breadcrumbs = BreadcrumbBuffer.new(@max_breadcrumbs)
190
+ end
191
+
192
+
187
193
  class << self
188
194
  def os_context
189
195
  @os_context ||=
@@ -25,14 +25,20 @@ module Sentry
25
25
  @span_recorder.add(self)
26
26
  end
27
27
 
28
- def self.from_sentry_trace(sentry_trace, **options)
28
+ def self.from_sentry_trace(sentry_trace, configuration: Sentry.configuration, **options)
29
+ return unless configuration.tracing_enabled?
29
30
  return unless sentry_trace
30
31
 
31
32
  match = SENTRY_TRACE_REGEXP.match(sentry_trace)
32
33
  return if match.nil?
33
34
  trace_id, parent_span_id, sampled_flag = match[1..3]
34
35
 
35
- sampled = sampled_flag != "0"
36
+ sampled =
37
+ if sampled_flag.nil?
38
+ nil
39
+ else
40
+ sampled_flag != "0"
41
+ end
36
42
 
37
43
  new(trace_id: trace_id, parent_span_id: parent_span_id, parent_sampled: sampled, sampled: sampled, **options)
38
44
  end
@@ -67,8 +73,8 @@ module Sentry
67
73
  copy
68
74
  end
69
75
 
70
- def set_initial_sample_desicion(sampling_context = {})
71
- unless Sentry.configuration.tracing_enabled?
76
+ def set_initial_sample_decision(sampling_context: {}, configuration: Sentry.configuration)
77
+ unless configuration.tracing_enabled?
72
78
  @sampled = false
73
79
  return
74
80
  end
@@ -77,9 +83,9 @@ module Sentry
77
83
 
78
84
  transaction_description = generate_transaction_description
79
85
 
80
- logger = Sentry.configuration.logger
81
- sample_rate = Sentry.configuration.traces_sample_rate
82
- traces_sampler = Sentry.configuration.traces_sampler
86
+ logger = configuration.logger
87
+ sample_rate = configuration.traces_sample_rate
88
+ traces_sampler = configuration.traces_sampler
83
89
 
84
90
  if traces_sampler.is_a?(Proc)
85
91
  sampling_context = sampling_context.merge(
@@ -31,9 +31,6 @@ module Sentry
31
31
  send_data(encoded_data)
32
32
 
33
33
  event
34
- rescue => e
35
- failed_for_exception(e, event)
36
- nil
37
34
  end
38
35
 
39
36
  def generate_auth_header
@@ -72,15 +69,6 @@ module Sentry
72
69
  configuration.logger.info(LOGGER_PROGNAME) { "Sending #{event_type} #{event_id} to Sentry" }
73
70
  encode(event_hash)
74
71
  end
75
-
76
- def failed_for_exception(e, event)
77
- configuration.logger.warn(LOGGER_PROGNAME) { "Unable to record event with remote Sentry server (#{e.class} - #{e.message}):\n#{e.backtrace[0..10].join("\n")}" }
78
- log_not_sending(event)
79
- end
80
-
81
- def log_not_sending(event)
82
- configuration.logger.warn(LOGGER_PROGNAME) { "Failed to submit event. Unreported Event: #{Event.get_log_message(event.to_hash)}" }
83
- end
84
72
  end
85
73
  end
86
74
 
@@ -1,12 +1,14 @@
1
1
  module Sentry
2
2
  class Transport
3
3
  class Configuration
4
- attr_accessor :timeout, :open_timeout, :proxy, :ssl, :ssl_ca_file, :ssl_verification, :http_adapter, :faraday_builder, :transport_class
4
+ attr_accessor :timeout, :open_timeout, :proxy, :ssl, :ssl_ca_file, :ssl_verification, :http_adapter, :faraday_builder,
5
+ :transport_class, :encoding
5
6
 
6
7
  def initialize
7
8
  @ssl_verification = true
8
9
  @open_timeout = 1
9
10
  @timeout = 2
11
+ @encoding = HTTPTransport::GZIP_ENCODING
10
12
  end
11
13
 
12
14
  def transport_class=(klass)
@@ -1,8 +1,12 @@
1
1
  require 'faraday'
2
+ require 'zlib'
2
3
 
3
4
  module Sentry
4
5
  class HTTPTransport < Transport
5
- CONTENT_TYPE = 'application/json'
6
+ GZIP_ENCODING = "gzip"
7
+ GZIP_THRESHOLD = 1024 * 30
8
+ CONTENT_TYPE = 'application/x-sentry-envelope'
9
+
6
10
  attr_reader :conn, :adapter
7
11
 
8
12
  def initialize(*args)
@@ -13,8 +17,16 @@ module Sentry
13
17
  end
14
18
 
15
19
  def send_data(data)
20
+ encoding = ""
21
+
22
+ if should_compress?(data)
23
+ data = Zlib.gzip(data)
24
+ encoding = GZIP_ENCODING
25
+ end
26
+
16
27
  conn.post @endpoint do |req|
17
28
  req.headers['Content-Type'] = CONTENT_TYPE
29
+ req.headers['Content-Encoding'] = encoding
18
30
  req.headers['X-Sentry-Auth'] = generate_auth_header
19
31
  req.body = data
20
32
  end
@@ -26,11 +38,15 @@ module Sentry
26
38
  error_info += " Error in headers is: #{e.response[:headers]['x-sentry-error']}" if e.response[:headers]['x-sentry-error']
27
39
  end
28
40
 
29
- raise Sentry::Error, error_info
41
+ raise Sentry::ExternalError, error_info
30
42
  end
31
43
 
32
44
  private
33
45
 
46
+ def should_compress?(data)
47
+ @transport_configuration.encoding == GZIP_ENCODING && data.bytesize >= GZIP_THRESHOLD
48
+ end
49
+
34
50
  def set_conn
35
51
  server = @dsn.server
36
52
 
@@ -1,3 +1,3 @@
1
1
  module Sentry
2
- VERSION = "4.2.2"
2
+ VERSION = "4.3.0"
3
3
  end
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: 4.2.2
4
+ version: 4.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-02-18 00:00:00.000000000 Z
11
+ date: 2021-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -71,6 +71,7 @@ files:
71
71
  - lib/sentry/core_ext/object/duplicable.rb
72
72
  - lib/sentry/dsn.rb
73
73
  - lib/sentry/event.rb
74
+ - lib/sentry/exceptions.rb
74
75
  - lib/sentry/hub.rb
75
76
  - lib/sentry/integrable.rb
76
77
  - lib/sentry/interface.rb
@@ -78,6 +79,7 @@ files:
78
79
  - lib/sentry/interfaces/request.rb
79
80
  - lib/sentry/interfaces/single_exception.rb
80
81
  - lib/sentry/interfaces/stacktrace.rb
82
+ - lib/sentry/interfaces/stacktrace_builder.rb
81
83
  - lib/sentry/interfaces/threads.rb
82
84
  - lib/sentry/linecache.rb
83
85
  - lib/sentry/logger.rb