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.
@@ -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["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
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,15 +25,22 @@ 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)
33
+ return if match.nil?
32
34
  trace_id, parent_span_id, sampled_flag = match[1..3]
33
35
 
34
- sampled = sampled_flag != "0"
36
+ sampled =
37
+ if sampled_flag.nil?
38
+ nil
39
+ else
40
+ sampled_flag != "0"
41
+ end
35
42
 
36
- new(trace_id: trace_id, parent_span_id: parent_span_id, parent_sampled: sampled, **options)
43
+ new(trace_id: trace_id, parent_span_id: parent_span_id, parent_sampled: sampled, sampled: sampled, **options)
37
44
  end
38
45
 
39
46
  def to_hash
@@ -66,8 +73,8 @@ module Sentry
66
73
  copy
67
74
  end
68
75
 
69
- def set_initial_sample_desicion(sampling_context = {})
70
- unless Sentry.configuration.tracing_enabled?
76
+ def set_initial_sample_decision(sampling_context: {}, configuration: Sentry.configuration)
77
+ unless configuration.tracing_enabled?
71
78
  @sampled = false
72
79
  return
73
80
  end
@@ -76,9 +83,9 @@ module Sentry
76
83
 
77
84
  transaction_description = generate_transaction_description
78
85
 
79
- logger = Sentry.configuration.logger
80
- sample_rate = Sentry.configuration.traces_sample_rate
81
- traces_sampler = Sentry.configuration.traces_sampler
86
+ logger = configuration.logger
87
+ sample_rate = configuration.traces_sample_rate
88
+ traces_sampler = configuration.traces_sampler
82
89
 
83
90
  if traces_sampler.is_a?(Proc)
84
91
  sampling_context = sampling_context.merge(
@@ -89,7 +96,7 @@ module Sentry
89
96
  sample_rate = traces_sampler.call(sampling_context)
90
97
  end
91
98
 
92
- unless [true, false].include?(sample_rate) || (sample_rate.is_a?(Float) && sample_rate >= 0.0 && sample_rate <= 1.0)
99
+ unless [true, false].include?(sample_rate) || (sample_rate.is_a?(Numeric) && sample_rate >= 0.0 && sample_rate <= 1.0)
93
100
  @sampled = false
94
101
  logger.warn("#{MESSAGE_PREFIX} Discarding #{transaction_description} because of invalid sample_rate: #{sample_rate}")
95
102
  return
@@ -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
 
@@ -13,8 +13,8 @@ module Sentry
13
13
  "fc00::/7", # private IPv6 range fc00::/7
14
14
  "10.0.0.0/8", # private IPv4 range 10.x.x.x
15
15
  "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
16
- "192.168.0.0/16" # private IPv4 range 192.168.x.x
17
- ].map { |proxy| IPAddr.new(proxy) }
16
+ "192.168.0.0/16", # private IPv4 range 192.168.x.x
17
+ ]
18
18
 
19
19
  attr_reader :ip
20
20
 
@@ -22,12 +22,14 @@ module Sentry
22
22
  remote_addr: nil,
23
23
  client_ip: nil,
24
24
  real_ip: nil,
25
- forwarded_for: nil
25
+ forwarded_for: nil,
26
+ trusted_proxies: []
26
27
  )
27
28
  @remote_addr = remote_addr
28
29
  @client_ip = client_ip
29
30
  @real_ip = real_ip
30
31
  @forwarded_for = forwarded_for
32
+ @trusted_proxies = (LOCAL_ADDRESSES + Array(trusted_proxies)).map { |proxy| IPAddr.new(proxy.to_s) }.uniq
31
33
  end
32
34
 
33
35
  def calculate_ip
@@ -37,12 +39,16 @@ module Sentry
37
39
  # Could be a CSV list and/or repeated headers that were concatenated.
38
40
  client_ips = ips_from(@client_ip)
39
41
  real_ips = ips_from(@real_ip)
40
- forwarded_ips = ips_from(@forwarded_for)
42
+
43
+ # The first address in this list is the original client, followed by
44
+ # the IPs of successive proxies. We want to search starting from the end
45
+ # until we find the first proxy that we do not trust.
46
+ forwarded_ips = ips_from(@forwarded_for).reverse
41
47
 
42
48
  ips = [client_ips, real_ips, forwarded_ips, remote_addr].flatten.compact
43
49
 
44
50
  # If every single IP option is in the trusted list, just return REMOTE_ADDR
45
- @ip = filter_local_addresses(ips).first || remote_addr
51
+ @ip = filter_trusted_proxy_addresses(ips).first || remote_addr
46
52
  end
47
53
 
48
54
  protected
@@ -62,8 +68,8 @@ module Sentry
62
68
  end
63
69
  end
64
70
 
65
- def filter_local_addresses(ips)
66
- ips.reject { |ip| LOCAL_ADDRESSES.any? { |proxy| proxy === ip } }
71
+ def filter_trusted_proxy_addresses(ips)
72
+ ips.reject { |ip| @trusted_proxies.any? { |proxy| proxy === ip } }
67
73
  end
68
74
  end
69
75
  end
@@ -1,3 +1,3 @@
1
1
  module Sentry
2
- VERSION = "4.1.5"
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.1.5
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-01-27 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
@@ -53,6 +53,7 @@ files:
53
53
  - CODE_OF_CONDUCT.md
54
54
  - Gemfile
55
55
  - LICENSE.txt
56
+ - Makefile
56
57
  - README.md
57
58
  - Rakefile
58
59
  - bin/console
@@ -70,6 +71,7 @@ files:
70
71
  - lib/sentry/core_ext/object/duplicable.rb
71
72
  - lib/sentry/dsn.rb
72
73
  - lib/sentry/event.rb
74
+ - lib/sentry/exceptions.rb
73
75
  - lib/sentry/hub.rb
74
76
  - lib/sentry/integrable.rb
75
77
  - lib/sentry/interface.rb
@@ -77,6 +79,8 @@ files:
77
79
  - lib/sentry/interfaces/request.rb
78
80
  - lib/sentry/interfaces/single_exception.rb
79
81
  - lib/sentry/interfaces/stacktrace.rb
82
+ - lib/sentry/interfaces/stacktrace_builder.rb
83
+ - lib/sentry/interfaces/threads.rb
80
84
  - lib/sentry/linecache.rb
81
85
  - lib/sentry/logger.rb
82
86
  - lib/sentry/rack.rb