sentry-ruby-core 4.2.2 → 4.3.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: 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