sentry-ruby-core 4.1.5 → 4.3.0

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