sentry-ruby-core 4.1.6 → 4.3.1

Sign up to get free protection for your applications and to get access to all the features.
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