sentry-ruby 4.1.2 → 4.1.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbe0af63148c410c71208f7bac743d24e224b5265102d645831a9538262961dc
4
- data.tar.gz: 9933997d430fc94d400f97b9e2319ab14ccfe1bd2ff42635eda5b13bd4e11621
3
+ metadata.gz: 823d9f50e4335e2c08fa6a6afedac78e2c6243ea94a104db560870f0021c51c7
4
+ data.tar.gz: 385143cfb2e2218afb9fca2bebe07a26bca02565cd525b32a1abdde38bcd53e0
5
5
  SHA512:
6
- metadata.gz: a813b4fad61840a850b6dd82451d689272800b5d4e405ab50f80f99cfe69a371f770c1a8151491ccd2f10400822e0e4a98bc8c2ca1265b562139bd7ffc1cdd02
7
- data.tar.gz: 1331331801ea39234c96e50e9fc469713efd48ee316e5620ae2c200f97a4a4d88810f942d707fb77dab7b458284ee11cad4b6153632cde4aa6014e2df5a89820
6
+ metadata.gz: 143855b2a274c65d17b72a60adbe8ff72b2d71ad316796d7899803d3bf28f4645cba7afa1518aabae592b0093ba51f5f3c41d755ce9674cd65b3da3363a5bb72
7
+ data.tar.gz: a4b8fc9795553867da3b5d6c0eebd6f2d4198805c21f19a936149653ddac882b4db5cba46678f016982f3529314a95d5a23e9770173f53dbeb33ff7ba6a4b6c7
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.1.3
4
+
5
+ - rm reference to old constant (from Rails v2.2) [#1184](https://github.com/getsentry/sentry-ruby/pull/1184)
6
+ - Use copied env in events [#1186](https://github.com/getsentry/sentry-ruby/pull/1186)
7
+ - Fixes [#1183](https://github.com/getsentry/sentry-ruby/issues/1183)
8
+ - Refactor RequestInterface [#1187](https://github.com/getsentry/sentry-ruby/pull/1187)
9
+ - Supply event hint to async callback when possible [#1189](https://github.com/getsentry/sentry-ruby/pull/1189)
10
+ - Fixes [#1188](https://github.com/getsentry/sentry-ruby/issues/1188)
11
+ - Refactor stacktrace parsing and increase test coverage [#1190](https://github.com/getsentry/sentry-ruby/pull/1190)
12
+ - Sentry.send_event should also take a hint [#1192](https://github.com/getsentry/sentry-ruby/pull/1192)
13
+
3
14
  ## 4.1.2
4
15
 
5
16
  - before_send callback shouldn't be applied to transaction events [#1167](https://github.com/getsentry/sentry-ruby/pull/1167)
data/README.md CHANGED
@@ -94,7 +94,7 @@ Sentry.init do |config|
94
94
  end
95
95
  ```
96
96
 
97
- To lean more about performance monitoring, please visit the [official documentation](https://docs.sentry.io/platforms/ruby/performance).
97
+ To learn more about performance monitoring, please visit the [official documentation](https://docs.sentry.io/platforms/ruby/performance).
98
98
 
99
99
  ### Usage
100
100
 
@@ -144,8 +144,8 @@ You're all set - but there's a few more settings you may want to know about too!
144
144
  **Before version 4.1.0**, `sentry-ruby` sends every event immediately. But it can be configured to send asynchronously:
145
145
 
146
146
  ```ruby
147
- config.async = lambda { |event|
148
- Thread.new { Sentry.send_event(event) }
147
+ config.async = lambda { |event, hint|
148
+ Thread.new { Sentry.send_event(event, hint) }
149
149
  }
150
150
  ```
151
151
 
@@ -154,13 +154,13 @@ Using a thread to send events will be adequate for truly parallel Ruby platforms
154
154
  Note that the naive example implementation has a major drawback - it can create an infinite number of threads. We recommend creating a background job, using your background job processor, that will send Sentry notifications in the background.
155
155
 
156
156
  ```ruby
157
- config.async = lambda { |event| SentryJob.perform_later(event) }
157
+ config.async = lambda { |event, hint| SentryJob.perform_later(event, hint) }
158
158
 
159
159
  class SentryJob < ActiveJob::Base
160
160
  queue_as :default
161
161
 
162
- def perform(event)
163
- Sentry.send_event(event)
162
+ def perform(event, hint)
163
+ Sentry.send_event(event, hint)
164
164
  end
165
165
  end
166
166
  ```
@@ -54,6 +54,7 @@ module Sentry
54
54
  class << self
55
55
  extend Forwardable
56
56
 
57
+ def_delegators :get_current_client, :configuration, :send_event
57
58
  def_delegators :get_current_scope, :set_tags, :set_extras, :set_user
58
59
 
59
60
  attr_accessor :background_worker
@@ -85,10 +86,6 @@ module Sentry
85
86
  get_current_scope.breadcrumbs.record(breadcrumb, &block)
86
87
  end
87
88
 
88
- def configuration
89
- get_current_client.configuration
90
- end
91
-
92
89
  def get_current_client
93
90
  get_current_hub&.current_client
94
91
  end
@@ -118,10 +115,6 @@ module Sentry
118
115
  get_current_hub&.configure_scope(&block)
119
116
  end
120
117
 
121
- def send_event(event)
122
- get_current_client.send_event(event)
123
- end
124
-
125
118
  def capture_event(event)
126
119
  get_current_hub&.capture_event(event)
127
120
  end
@@ -10,7 +10,7 @@ module Sentry
10
10
  @number_of_threads = configuration.background_worker_threads
11
11
 
12
12
  @executor =
13
- if configuration.async?
13
+ if configuration.async
14
14
  configuration.logger.debug(LOGGER_PROGNAME) { "config.async is set, BackgroundWorker is disabled" }
15
15
  Concurrent::ImmediateExecutor.new
16
16
  elsif @number_of_threads == 0
@@ -82,16 +82,14 @@ module Sentry
82
82
 
83
83
  # holder for an Array of Backtrace::Line instances
84
84
  attr_reader :lines
85
- attr_reader :configuration
86
85
 
87
- def self.parse(backtrace, configuration:)
86
+ def self.parse(backtrace, project_root, app_dirs_pattern, &backtrace_cleanup_callback)
88
87
  ruby_lines = backtrace.is_a?(Array) ? backtrace : backtrace.split(/\n\s*/)
89
88
 
90
- ruby_lines = configuration.backtrace_cleanup_callback.call(ruby_lines) if configuration&.backtrace_cleanup_callback
89
+ ruby_lines = backtrace_cleanup_callback.call(ruby_lines) if backtrace_cleanup_callback
91
90
 
92
91
  in_app_pattern ||= begin
93
- project_root = configuration.project_root&.to_s
94
- Regexp.new("^(#{project_root}/)?#{configuration.app_dirs_pattern || APP_DIRS_PATTERN}")
92
+ Regexp.new("^(#{project_root}/)?#{app_dirs_pattern || APP_DIRS_PATTERN}")
95
93
  end
96
94
 
97
95
  lines = ruby_lines.to_a.map do |unparsed_line|
@@ -25,11 +25,17 @@ module Sentry
25
25
 
26
26
  scope.apply_to_event(event, hint)
27
27
 
28
- if configuration.async?
28
+ if async_block = configuration.async
29
29
  begin
30
30
  # We have to convert to a JSON-like hash, because background job
31
31
  # processors (esp ActiveJob) may not like weird types in the event hash
32
- configuration.async.call(event.to_json_compatible)
32
+ event_hash = event.to_json_compatible
33
+
34
+ if async_block.arity == 2
35
+ async_block.call(event_hash, hint)
36
+ else
37
+ async_block.call(event_hash)
38
+ end
33
39
  rescue => e
34
40
  configuration.logger.error(LOGGER_PROGNAME) { "async event sending failed: #{e.message}" }
35
41
  send_event(event, hint)
@@ -15,7 +15,6 @@ module Sentry
15
15
  # Provide an object that responds to `call` to send events asynchronously.
16
16
  # E.g.: lambda { |event| Thread.new { Sentry.send_event(event) } }
17
17
  attr_reader :async
18
- alias async? async
19
18
 
20
19
  # to send events in a non-blocking way, sentry-ruby has its own background worker
21
20
  # by default, the worker holds a thread pool that has [the number of processors] threads
@@ -134,7 +133,6 @@ module Sentry
134
133
  # Most of these errors generate 4XX responses. In general, Sentry clients
135
134
  # only automatically report 5xx responses.
136
135
  IGNORE_DEFAULT = [
137
- 'CGI::Session::CookieStore::TamperedWithCookie',
138
136
  'Mongoid::Errors::DocumentNotFound',
139
137
  'Rack::QueryParser::InvalidParameterError',
140
138
  'Rack::QueryParser::ParameterTypeError',
@@ -156,7 +154,6 @@ module Sentry
156
154
  AVAILABLE_BREADCRUMBS_LOGGERS = [:sentry_logger, :active_support_logger].freeze
157
155
 
158
156
  def initialize
159
- self.async = false
160
157
  self.background_worker_threads = Concurrent.processor_count
161
158
  self.breadcrumbs_logger = []
162
159
  self.context_lines = 3
@@ -194,8 +191,8 @@ module Sentry
194
191
 
195
192
 
196
193
  def async=(value)
197
- unless value == false || value.respond_to?(:call)
198
- raise(ArgumentError, "async must be callable (or false to disable)")
194
+ if value && !value.respond_to?(:call)
195
+ raise(ArgumentError, "async must be callable")
199
196
  end
200
197
 
201
198
  @async = value
@@ -76,13 +76,14 @@ module Sentry
76
76
 
77
77
  def rack_env=(env)
78
78
  unless request || env.empty?
79
- @request = Sentry::RequestInterface.new.tap do |int|
80
- int.from_rack(env)
81
- end
79
+ env = env.dup
80
+
81
+ add_request_interface(env)
82
82
 
83
- if configuration.send_default_pii && ip = calculate_real_ip_from_rack(env.dup)
84
- user[:ip_address] = ip
83
+ if configuration.send_default_pii
84
+ user[:ip_address] = calculate_real_ip_from_rack(env)
85
85
  end
86
+
86
87
  if request_id = Utils::RequestId.read_from(env)
87
88
  tags[:request_id] = request_id
88
89
  end
@@ -107,6 +108,10 @@ module Sentry
107
108
  JSON.parse(JSON.generate(to_hash))
108
109
  end
109
110
 
111
+ def add_request_interface(env)
112
+ @request = Sentry::RequestInterface.from_rack(env)
113
+ end
114
+
110
115
  def add_exception_interface(exc)
111
116
  if exc.respond_to?(:sentry_context)
112
117
  @extra.merge!(exc.sentry_context)
@@ -124,33 +129,22 @@ module Sentry
124
129
  int.stacktrace =
125
130
  if e.backtrace && !backtraces.include?(e.backtrace.object_id)
126
131
  backtraces << e.backtrace.object_id
127
- StacktraceInterface.new.tap do |stacktrace|
128
- stacktrace.frames = stacktrace_interface_from(e.backtrace)
129
- end
132
+ initialize_stacktrace_interface(e.backtrace)
130
133
  end
131
134
  end
132
135
  end
133
136
  end
134
137
  end
135
138
 
136
- def stacktrace_interface_from(backtrace)
137
- project_root = configuration.project_root.to_s
138
-
139
- Backtrace.parse(backtrace, configuration: configuration).lines.reverse.each_with_object([]) do |line, memo|
140
- frame = StacktraceInterface::Frame.new(project_root)
141
- frame.abs_path = line.file if line.file
142
- frame.function = line.method if line.method
143
- frame.lineno = line.number
144
- frame.in_app = line.in_app
145
- frame.module = line.module_name if line.module_name
146
-
147
- if configuration.context_lines && frame.abs_path
148
- frame.pre_context, frame.context_line, frame.post_context = \
149
- configuration.linecache.get_file_context(frame.abs_path, frame.lineno, configuration.context_lines)
150
- end
151
-
152
- memo << frame if frame.filename
153
- end
139
+ def initialize_stacktrace_interface(backtrace)
140
+ StacktraceInterface.new(
141
+ backtrace: backtrace,
142
+ project_root: configuration.project_root.to_s,
143
+ app_dirs_pattern: configuration.app_dirs_pattern,
144
+ linecache: configuration.linecache,
145
+ context_lines: configuration.context_lines,
146
+ backtrace_cleanup_callback: configuration.backtrace_cleanup_callback
147
+ )
154
148
  end
155
149
 
156
150
  private
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sentry
2
4
  class RequestInterface < Interface
3
5
  REQUEST_ID_HEADERS = %w(action_dispatch.request_id HTTP_X_REQUEST_ID).freeze
6
+ CONTENT_HEADERS = %w(CONTENT_TYPE CONTENT_LENGTH).freeze
4
7
  IP_HEADERS = [
5
8
  "REMOTE_ADDR",
6
9
  "HTTP_CLIENT_IP",
@@ -8,23 +11,52 @@ module Sentry
8
11
  "HTTP_X_FORWARDED_FOR"
9
12
  ].freeze
10
13
 
14
+ # See Sentry server default limits at
15
+ # https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py
16
+ MAX_BODY_LIMIT = 4096 * 4
17
+
11
18
  attr_accessor :url, :method, :data, :query_string, :cookies, :headers, :env
12
19
 
13
- def initialize
14
- self.headers = {}
15
- self.env = {}
16
- self.cookies = nil
20
+ def self.from_rack(env)
21
+ env = clean_env(env)
22
+ req = ::Rack::Request.new(env)
23
+ self.new(req)
24
+ end
25
+
26
+ def self.clean_env(env)
27
+ unless Sentry.configuration.send_default_pii
28
+ # need to completely wipe out ip addresses
29
+ RequestInterface::IP_HEADERS.each do |header|
30
+ env.delete(header)
31
+ end
32
+ end
33
+
34
+ env
35
+ end
36
+
37
+ def initialize(req)
38
+ env = req.env
39
+
40
+ if Sentry.configuration.send_default_pii
41
+ self.data = read_data_from(req)
42
+ self.cookies = req.cookies
43
+ end
44
+
45
+ self.url = req.scheme && req.url.split('?').first
46
+ self.method = req.request_method
47
+ self.query_string = req.query_string
48
+
49
+ self.headers = filter_and_format_headers(env)
50
+ self.env = filter_and_format_env(env)
17
51
  end
18
52
 
19
53
  private
20
54
 
21
- # See Sentry server default limits at
22
- # https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py
23
55
  def read_data_from(request)
24
56
  if request.form_data?
25
57
  request.POST
26
58
  elsif request.body # JSON requests, etc
27
- data = request.body.read(4096 * 4) # Sentry server limit
59
+ data = request.body.read(MAX_BODY_LIMIT)
28
60
  request.body.rewind
29
61
  data
30
62
  end
@@ -32,22 +64,14 @@ module Sentry
32
64
  e.message
33
65
  end
34
66
 
35
- def format_headers_for_sentry(env_hash)
36
- env_hash.each_with_object({}) do |(key, value), memo|
67
+ def filter_and_format_headers(env)
68
+ env.each_with_object({}) do |(key, value), memo|
37
69
  begin
38
70
  key = key.to_s # rack env can contain symbols
39
71
  value = value.to_s
40
- next memo['X-Request-Id'] ||= Utils::RequestId.read_from(env_hash) if Utils::RequestId::REQUEST_ID_HEADERS.include?(key)
41
- next unless key.upcase == key # Non-upper case stuff isn't either
42
-
43
- # Rack adds in an incorrect HTTP_VERSION key, which causes downstream
44
- # to think this is a Version header. Instead, this is mapped to
45
- # env['SERVER_PROTOCOL']. But we don't want to ignore a valid header
46
- # if the request has legitimately sent a Version header themselves.
47
- # See: https://github.com/rack/rack/blob/028438f/lib/rack/handler/cgi.rb#L29
48
- next if key == 'HTTP_VERSION' && value == env_hash['SERVER_PROTOCOL']
49
- next if key == 'HTTP_COOKIE' # Cookies don't go here, they go somewhere else
50
- next unless key.start_with?('HTTP_') || %w(CONTENT_TYPE CONTENT_LENGTH).include?(key)
72
+ next memo['X-Request-Id'] ||= Utils::RequestId.read_from(env) if Utils::RequestId::REQUEST_ID_HEADERS.include?(key)
73
+ next if is_server_protocol?(key, value, env["SERVER_PROTOCOL"])
74
+ next if is_skippable_header?(key)
51
75
 
52
76
  # Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
53
77
  key = key.sub(/^HTTP_/, "")
@@ -63,10 +87,26 @@ module Sentry
63
87
  end
64
88
  end
65
89
 
66
- def format_env_for_sentry(env_hash)
67
- return env_hash if Sentry.configuration.rack_env_whitelist.empty?
90
+ def is_skippable_header?(key)
91
+ key.upcase != key || # lower-case envs aren't real http headers
92
+ key == "HTTP_COOKIE" || # Cookies don't go here, they go somewhere else
93
+ !(key.start_with?('HTTP_') || CONTENT_HEADERS.include?(key))
94
+ end
95
+
96
+ # Rack adds in an incorrect HTTP_VERSION key, which causes downstream
97
+ # to think this is a Version header. Instead, this is mapped to
98
+ # env['SERVER_PROTOCOL']. But we don't want to ignore a valid header
99
+ # if the request has legitimately sent a Version header themselves.
100
+ # See: https://github.com/rack/rack/blob/028438f/lib/rack/handler/cgi.rb#L29
101
+ # NOTE: This will be removed in version 3.0+
102
+ def is_server_protocol?(key, value, protocol_version)
103
+ key == 'HTTP_VERSION' && value == protocol_version
104
+ end
105
+
106
+ def filter_and_format_env(env)
107
+ return env if Sentry.configuration.rack_env_whitelist.empty?
68
108
 
69
- env_hash.select do |k, _v|
109
+ env.select do |k, _v|
70
110
  Sentry.configuration.rack_env_whitelist.include? k.to_s
71
111
  end
72
112
  end
@@ -1,11 +1,31 @@
1
1
  module Sentry
2
- class StacktraceInterface < Interface
3
- attr_accessor :frames
2
+ class StacktraceInterface
3
+ attr_reader :frames
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
17
+ end
4
18
 
5
19
  def to_hash
6
- data = super
7
- data[:frames] = data[:frames].map(&:to_hash)
8
- data
20
+ { frames: @frames.map(&:to_hash) }
21
+ end
22
+
23
+ private
24
+
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
9
29
  end
10
30
 
11
31
  # Not actually an interface, but I want to use the same style
@@ -13,8 +33,14 @@ module Sentry
13
33
  attr_accessor :abs_path, :context_line, :function, :in_app,
14
34
  :lineno, :module, :pre_context, :post_context, :vars
15
35
 
16
- def initialize(project_root)
36
+ def initialize(project_root, line)
17
37
  @project_root = project_root
38
+
39
+ @abs_path = line.file if line.file
40
+ @function = line.method if line.method
41
+ @lineno = line.number
42
+ @in_app = line.in_app
43
+ @module = line.module_name if line.module_name
18
44
  end
19
45
 
20
46
  def filename
@@ -33,6 +59,13 @@ module Sentry
33
59
  @filename = prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
34
60
  end
35
61
 
62
+ def set_context(linecache, context_lines)
63
+ return unless abs_path
64
+
65
+ self.pre_context, self.context_line, self.post_context = \
66
+ linecache.get_file_context(abs_path, lineno, context_lines)
67
+ end
68
+
36
69
  def to_hash(*args)
37
70
  data = super(*args)
38
71
  data[:filename] = filename
@@ -1,5 +1,4 @@
1
1
  require 'rack'
2
2
 
3
3
  require 'sentry/rack/capture_exceptions'
4
- require 'sentry/rack/interface'
5
4
  require 'sentry/rack/deprecations'
@@ -4,9 +4,9 @@ module Sentry
4
4
  REQUEST_ID_HEADERS = %w(action_dispatch.request_id HTTP_X_REQUEST_ID).freeze
5
5
 
6
6
  # Request ID based on ActionDispatch::RequestId
7
- def self.read_from(env_hash)
7
+ def self.read_from(env)
8
8
  REQUEST_ID_HEADERS.each do |key|
9
- request_id = env_hash[key]
9
+ request_id = env[key]
10
10
  return request_id if request_id
11
11
  end
12
12
  nil
@@ -1,3 +1,3 @@
1
1
  module Sentry
2
- VERSION = "4.1.2"
2
+ VERSION = "4.1.3"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentry-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.2
4
+ version: 4.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-12-30 00:00:00.000000000 Z
11
+ date: 2021-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -89,7 +89,6 @@ files:
89
89
  - lib/sentry/rack.rb
90
90
  - lib/sentry/rack/capture_exceptions.rb
91
91
  - lib/sentry/rack/deprecations.rb
92
- - lib/sentry/rack/interface.rb
93
92
  - lib/sentry/rake.rb
94
93
  - lib/sentry/scope.rb
95
94
  - lib/sentry/span.rb
@@ -1,22 +0,0 @@
1
- module Sentry
2
- class RequestInterface
3
- def from_rack(env_hash)
4
- req = ::Rack::Request.new(env_hash)
5
-
6
- if Sentry.configuration.send_default_pii
7
- self.data = read_data_from(req)
8
- self.cookies = req.cookies
9
- else
10
- # need to completely wipe out ip addresses
11
- IP_HEADERS.each { |h| env_hash.delete(h) }
12
- end
13
-
14
- self.url = req.scheme && req.url.split('?').first
15
- self.method = req.request_method
16
- self.query_string = req.query_string
17
-
18
- self.headers = format_headers_for_sentry(env_hash)
19
- self.env = format_env_for_sentry(env_hash)
20
- end
21
- end
22
- end