sentry-ruby-core 4.1.5 → 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.
@@ -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
@@ -38,10 +39,19 @@ module Sentry
38
39
  #
39
40
  attr_accessor :backtrace_cleanup_callback
40
41
 
42
+ # Optional Proc, called before adding the breadcrumb to the current scope
43
+ # E.g.: lambda { |breadcrumb, hint| breadcrumb }
44
+ # E.g.: lambda { |breadcrumb, hint| nil }
45
+ # E.g.: lambda { |breadcrumb, hint|
46
+ # breadcrumb.message = 'a'
47
+ # breadcrumb
48
+ # }
49
+ attr_reader :before_breadcrumb
50
+
41
51
  # Optional Proc, called before sending an event to the server/
42
- # E.g.: lambda { |event| event }
43
- # E.g.: lambda { |event| nil }
44
- # E.g.: lambda { |event|
52
+ # E.g.: lambda { |event, hint| event }
53
+ # E.g.: lambda { |event, hint| nil }
54
+ # E.g.: lambda { |event, hint|
45
55
  # event[:message] = 'a'
46
56
  # event
47
57
  # }
@@ -52,6 +62,9 @@ module Sentry
52
62
  # - :active_support_logger
53
63
  attr_reader :breadcrumbs_logger
54
64
 
65
+ # Max number of breadcrumbs a breadcrumb buffer can hold
66
+ attr_accessor :max_breadcrumbs
67
+
55
68
  # Number of lines of code context to capture, or nil for none
56
69
  attr_accessor :context_lines
57
70
 
@@ -109,6 +122,9 @@ module Sentry
109
122
  # will not be sent to Sentry.
110
123
  attr_accessor :send_default_pii
111
124
 
125
+ # IP ranges for trusted proxies that will be skipped when calculating IP address.
126
+ attr_accessor :trusted_proxies
127
+
112
128
  attr_accessor :server_name
113
129
 
114
130
  # Return a Transport::Configuration object for transport-related configurations.
@@ -153,23 +169,29 @@ module Sentry
153
169
 
154
170
  AVAILABLE_BREADCRUMBS_LOGGERS = [:sentry_logger, :active_support_logger].freeze
155
171
 
172
+ # Post initialization callbacks are called at the end of initialization process
173
+ # allowing extending the configuration of sentry-ruby by multiple extensions
174
+ @@post_initialization_callbacks = []
175
+
156
176
  def initialize
157
177
  self.background_worker_threads = Concurrent.processor_count
178
+ self.max_breadcrumbs = BreadcrumbBuffer::DEFAULT_SIZE
158
179
  self.breadcrumbs_logger = []
159
180
  self.context_lines = 3
160
181
  self.environment = environment_from_env
161
182
  self.enabled_environments = []
162
183
  self.exclude_loggers = []
163
184
  self.excluded_exceptions = IGNORE_DEFAULT.dup
164
- self.inspect_exception_causes_for_exclusion = false
185
+ self.inspect_exception_causes_for_exclusion = true
165
186
  self.linecache = ::Sentry::LineCache.new
166
187
  self.logger = ::Sentry::Logger.new(STDOUT)
167
- self.project_root = detect_project_root
188
+ self.project_root = Dir.pwd
168
189
 
169
190
  self.release = detect_release
170
191
  self.sample_rate = 1.0
171
192
  self.send_modules = true
172
193
  self.send_default_pii = false
194
+ self.trusted_proxies = []
173
195
  self.dsn = ENV['SENTRY_DSN']
174
196
  self.server_name = server_name_from_env
175
197
 
@@ -178,7 +200,8 @@ module Sentry
178
200
 
179
201
  @transport = Transport::Configuration.new
180
202
  @gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
181
- post_initialization_callback
203
+
204
+ run_post_initialization_callbacks
182
205
  end
183
206
 
184
207
  def dsn=(value)
@@ -223,6 +246,14 @@ module Sentry
223
246
  @before_send = value
224
247
  end
225
248
 
249
+ def before_breadcrumb=(value)
250
+ unless value.nil? || value.respond_to?(:call)
251
+ raise ArgumentError, "before_breadcrumb must be callable (or nil to disable)"
252
+ end
253
+
254
+ @before_breadcrumb = value
255
+ end
256
+
226
257
  def environment=(environment)
227
258
  @environment = environment.to_s
228
259
  end
@@ -265,16 +296,18 @@ module Sentry
265
296
  !!((@traces_sample_rate && @traces_sample_rate > 0.0) || @traces_sampler)
266
297
  end
267
298
 
268
- private
269
-
270
- def detect_project_root
271
- if defined? Rails.root # we are in a Rails application
272
- Rails.root.to_s
273
- else
274
- Dir.pwd
275
- end
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
+ )
276
307
  end
277
308
 
309
+ private
310
+
278
311
  def detect_release
279
312
  detect_release_from_env ||
280
313
  detect_release_from_git ||
@@ -390,7 +423,21 @@ module Sentry
390
423
  end
391
424
  end
392
425
 
393
- # allow extensions to extend the Configuration class
394
- def post_initialization_callback; end
426
+ def run_post_initialization_callbacks
427
+ self.class.post_initialization_callbacks.each do |hook|
428
+ instance_eval(&hook)
429
+ end
430
+ end
431
+
432
+ # allow extensions to add their hooks to the Configuration class
433
+ def self.add_post_initialization_callback(&block)
434
+ self.post_initialization_callbacks << block
435
+ end
436
+
437
+ protected
438
+
439
+ def self.post_initialization_callbacks
440
+ @@post_initialization_callbacks
441
+ end
395
442
  end
396
443
  end
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
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,9 +106,9 @@ 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
111
+ data[:threads] = threads.to_hash if threads
105
112
 
106
113
  data
107
114
  end
@@ -111,42 +118,23 @@ module Sentry
111
118
  end
112
119
 
113
120
  def add_request_interface(env)
114
- @request = Sentry::RequestInterface.from_rack(env)
121
+ @request = Sentry::RequestInterface.build(env: env)
115
122
  end
116
123
 
117
- def add_exception_interface(exc)
118
- if exc.respond_to?(:sentry_context)
119
- @extra.merge!(exc.sentry_context)
120
- end
124
+ def add_threads_interface(backtrace: nil, **options)
125
+ @threads = ThreadsInterface.build(
126
+ backtrace: backtrace,
127
+ stacktrace_builder: configuration.stacktrace_builder,
128
+ **options
129
+ )
130
+ end
121
131
 
122
- @exception = Sentry::ExceptionInterface.new.tap do |exc_int|
123
- exceptions = Sentry::Utils::ExceptionCauseChain.exception_to_array(exc).reverse
124
- backtraces = Set.new
125
- exc_int.values = exceptions.map do |e|
126
- SingleExceptionInterface.new.tap do |int|
127
- int.type = e.class.to_s
128
- int.value = e.message.byteslice(0..MAX_MESSAGE_SIZE_IN_BYTES)
129
- int.module = e.class.to_s.split('::')[0...-1].join('::')
130
-
131
- int.stacktrace =
132
- if e.backtrace && !backtraces.include?(e.backtrace.object_id)
133
- backtraces << e.backtrace.object_id
134
- initialize_stacktrace_interface(e.backtrace)
135
- end
136
- end
137
- end
132
+ def add_exception_interface(exception)
133
+ if exception.respond_to?(:sentry_context)
134
+ @extra.merge!(exception.sentry_context)
138
135
  end
139
- end
140
136
 
141
- def initialize_stacktrace_interface(backtrace)
142
- StacktraceInterface.new(
143
- backtrace: backtrace,
144
- project_root: configuration.project_root.to_s,
145
- app_dirs_pattern: configuration.app_dirs_pattern,
146
- linecache: configuration.linecache,
147
- context_lines: configuration.context_lines,
148
- backtrace_cleanup_callback: configuration.backtrace_cleanup_callback
149
- )
137
+ @exception = Sentry::ExceptionInterface.build(exception: exception, stacktrace_builder: configuration.stacktrace_builder)
150
138
  end
151
139
 
152
140
  private
@@ -166,7 +154,8 @@ module Sentry
166
154
  :remote_addr => env["REMOTE_ADDR"],
167
155
  :client_ip => env["HTTP_CLIENT_IP"],
168
156
  :real_ip => env["HTTP_X_REAL_IP"],
169
- :forwarded_for => env["HTTP_X_FORWARDED_FOR"]
157
+ :forwarded_for => env["HTTP_X_FORWARDED_FOR"],
158
+ :trusted_proxies => configuration.trusted_proxies
170
159
  ).calculate_ip
171
160
  end
172
161
  end
@@ -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
 
@@ -120,7 +122,13 @@ module Sentry
120
122
  event
121
123
  end
122
124
 
123
- def add_breadcrumb(breadcrumb)
125
+ def add_breadcrumb(breadcrumb, hint: {})
126
+ if before_breadcrumb = current_client.configuration.before_breadcrumb
127
+ breadcrumb = before_breadcrumb.call(breadcrumb, hint)
128
+ end
129
+
130
+ return unless breadcrumb
131
+
124
132
  current_scope.add_breadcrumb(breadcrumb)
125
133
  end
126
134
 
@@ -20,3 +20,4 @@ require "sentry/interfaces/exception"
20
20
  require "sentry/interfaces/request"
21
21
  require "sentry/interfaces/single_exception"
22
22
  require "sentry/interfaces/stacktrace"
23
+ require "sentry/interfaces/threads"
@@ -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,14 +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 :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
7
12
 
8
13
  def to_hash
9
14
  data = super
10
15
  data[:stacktrace] = data[:stacktrace].to_hash if data[:stacktrace]
11
16
  data
12
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
13
25
  end
14
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?