sentry-ruby-core 4.1.6 → 4.3.1

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.
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 < Error
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?
@@ -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
@@ -0,0 +1,32 @@
1
+ module Sentry
2
+ class ThreadsInterface
3
+ def initialize(crashed: false, stacktrace: nil)
4
+ @id = Thread.current.object_id
5
+ @name = Thread.current.name
6
+ @current = true
7
+ @crashed = crashed
8
+ @stacktrace = stacktrace
9
+ end
10
+
11
+ def to_hash
12
+ {
13
+ values: [
14
+ {
15
+ id: @id,
16
+ name: @name,
17
+ crashed: @crashed,
18
+ current: @current,
19
+ stacktrace: @stacktrace&.to_hash
20
+ }
21
+ ]
22
+ }
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
31
+ end
32
+ end
@@ -16,30 +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
- span =
20
- if sentry_trace = env["HTTP_SENTRY_TRACE"]
21
- Sentry::Transaction.from_sentry_trace(sentry_trace, name: scope.transaction_name, op: transaction_op)
22
- else
23
- Sentry.start_transaction(name: scope.transaction_name, op: transaction_op)
24
- end
25
-
26
- scope.set_span(span)
19
+ transaction = start_transaction(env, scope)
20
+ scope.set_span(transaction) if transaction
27
21
 
28
22
  begin
29
23
  response = @app.call(env)
30
24
  rescue Sentry::Error
31
- finish_span(span, 500)
25
+ finish_transaction(transaction, 500)
32
26
  raise # Don't capture Sentry errors
33
27
  rescue Exception => e
34
28
  capture_exception(e)
35
- finish_span(span, 500)
29
+ finish_transaction(transaction, 500)
36
30
  raise
37
31
  end
38
32
 
39
33
  exception = collect_exception(env)
40
34
  capture_exception(exception) if exception
41
35
 
42
- finish_span(span, response[0])
36
+ finish_transaction(transaction, response[0])
43
37
 
44
38
  response
45
39
  end
@@ -59,9 +53,19 @@ module Sentry
59
53
  Sentry.capture_exception(exception)
60
54
  end
61
55
 
62
- def finish_span(span, status_code)
63
- span.set_http_status(status_code)
64
- 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
65
69
  end
66
70
  end
67
71
  end