sentry-ruby 4.1.2 → 4.1.3

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