sentry-ruby-core 5.0.1 → 5.1.1

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: 848bd1089194abb9be8a48b6c1c4c218dfba27a3555d8ae79a5ffe7d52a05569
4
- data.tar.gz: c52711ba4c4275d35cc4ed54a0dc0936422a63e199de91c08e3e6964fb80d6d6
3
+ metadata.gz: bfc28e6f57d57de669447d555889196617f72ce09624b343cd284375021f60f5
4
+ data.tar.gz: 5a6af91521df30a18ac1d021b2e59cd9e871653f1325cf6d65346e6c1dc91d7e
5
5
  SHA512:
6
- metadata.gz: fb55681acc83a4c970fbf8dd7c6715fbef7b6d26ad0a4ca674bc463de13881887d33dd8673772e60e25c2c66b6cb1f111a6c23936b1683d74384296924bb2b33
7
- data.tar.gz: 598c1571d00ca441b3a351dfe12c56967d3dae8e4f05b4a8f21b3e8fe16d0c94558e8dec5d211ff72aced12d5b433fb0f7f5f1b6ed522206d15847087dafd959
6
+ metadata.gz: fbe80e4c07f972d3babbf8c26c31cd97da84e91d77e86628a6931f35d7e66c83fb48461838d1a3e402361ee78dd9a3b6dfe849833f08a4573328d0eb4b5e66e1
7
+ data.tar.gz: 0f081f57f89207ece5279bd77fa89d311c3d516231421ccf60f4b9c232065a606d80eacc53ab460f12e087aa7b2fadfc4cdd4636863cb58261cc4e58a605ad2c
data/Gemfile CHANGED
@@ -9,6 +9,7 @@ gem "rake", "~> 12.0"
9
9
  gem "rspec", "~> 3.0"
10
10
  gem "rspec-retry"
11
11
  gem "webmock"
12
+ gem "fakeredis"
12
13
  gem "timecop"
13
14
  gem 'simplecov'
14
15
  gem "simplecov-cobertura", "~> 1.4"
data/lib/sentry/client.rb CHANGED
@@ -108,6 +108,7 @@ module Sentry
108
108
  event.contexts.merge!(trace: transaction.get_trace_context)
109
109
  event.timestamp = transaction.timestamp
110
110
  event.start_timestamp = transaction.start_timestamp
111
+ event.tags = transaction.tags
111
112
 
112
113
  finished_spans = transaction.span_recorder.spans.select { |span| span.timestamp && span != transaction }
113
114
  event.spans = finished_spans.map(&:to_hash)
@@ -75,6 +75,7 @@ module Sentry
75
75
  # An array of breadcrumbs loggers to be used. Available options are:
76
76
  # - :sentry_logger
77
77
  # - :http_logger
78
+ # - :redis_logger
78
79
  #
79
80
  # And if you also use sentry-rails:
80
81
  # - :active_support_logger
@@ -3,24 +3,47 @@
3
3
  module Sentry
4
4
  # @api private
5
5
  class Envelope
6
- def initialize(headers)
6
+ class Item
7
+ attr_accessor :headers, :payload
8
+
9
+ def initialize(headers, payload)
10
+ @headers = headers
11
+ @payload = payload
12
+ end
13
+
14
+ def type
15
+ @headers[:type] || 'event'
16
+ end
17
+
18
+ def to_s
19
+ <<~ITEM
20
+ #{JSON.generate(@headers)}
21
+ #{JSON.generate(@payload)}
22
+ ITEM
23
+ end
24
+ end
25
+
26
+ attr_accessor :headers, :items
27
+
28
+ def initialize(headers = {})
7
29
  @headers = headers
8
30
  @items = []
9
31
  end
10
32
 
11
33
  def add_item(headers, payload)
12
- @items << [headers, payload]
34
+ @items << Item.new(headers, payload)
13
35
  end
14
36
 
15
37
  def to_s
16
- payload = @items.map do |item_headers, item_payload|
17
- <<~ENVELOPE
18
- #{JSON.generate(item_headers)}
19
- #{JSON.generate(item_payload)}
20
- ENVELOPE
21
- end.join("\n")
22
-
23
- "#{JSON.generate(@headers)}\n#{payload}"
38
+ [JSON.generate(@headers), *@items.map(&:to_s)].join("\n")
39
+ end
40
+
41
+ def item_types
42
+ @items.map(&:type)
43
+ end
44
+
45
+ def event_id
46
+ @headers[:event_id]
24
47
  end
25
48
  end
26
49
  end
data/lib/sentry/hub.rb CHANGED
@@ -94,6 +94,8 @@ module Sentry
94
94
  def capture_exception(exception, **options, &block)
95
95
  check_argument_type!(exception, ::Exception)
96
96
 
97
+ return if Sentry.exception_captured?(exception)
98
+
97
99
  return unless current_client
98
100
 
99
101
  options[:hint] ||= {}
@@ -102,7 +104,10 @@ module Sentry
102
104
 
103
105
  return unless event
104
106
 
105
- capture_event(event, **options, &block)
107
+ capture_event(event, **options, &block).tap do
108
+ # mark the exception as captured so we can use this information to avoid duplicated capturing
109
+ exception.instance_variable_set(Sentry::CAPTURED_SIGNATURE, true)
110
+ end
106
111
  end
107
112
 
108
113
  def capture_message(message, **options, &block)
@@ -62,7 +62,7 @@ module Sentry
62
62
  self.url = request.scheme && request.url.split('?').first
63
63
  self.method = request.request_method
64
64
 
65
- self.headers = filter_and_format_headers(env)
65
+ self.headers = filter_and_format_headers(env, send_default_pii)
66
66
  self.env = filter_and_format_env(env, rack_env_whitelist)
67
67
  end
68
68
 
@@ -81,13 +81,14 @@ module Sentry
81
81
  e.message
82
82
  end
83
83
 
84
- def filter_and_format_headers(env)
84
+ def filter_and_format_headers(env, send_default_pii)
85
85
  env.each_with_object({}) do |(key, value), memo|
86
86
  begin
87
87
  key = key.to_s # rack env can contain symbols
88
88
  next memo['X-Request-Id'] ||= Utils::RequestId.read_from(env) if Utils::RequestId::REQUEST_ID_HEADERS.include?(key)
89
89
  next if is_server_protocol?(key, value, env["SERVER_PROTOCOL"])
90
90
  next if is_skippable_header?(key)
91
+ next if key == "HTTP_AUTHORIZATION" && !send_default_pii
91
92
 
92
93
  # Rack stores headers as HTTP_WHAT_EVER, we need What-Ever
93
94
  key = key.sub(/^HTTP_/, "")
@@ -6,7 +6,8 @@ module Sentry
6
6
  # @api private
7
7
  module Net
8
8
  module HTTP
9
- OP_NAME = "net.http"
9
+ OP_NAME = "http.client"
10
+ BREADCRUMB_CATEGORY = "net.http"
10
11
 
11
12
  # To explain how the entire thing works, we need to know how the original Net::HTTP#request works
12
13
  # Here's part of its definition. As you can see, it usually calls itself inside a #start block
@@ -53,7 +54,7 @@ module Sentry
53
54
 
54
55
  crumb = Sentry::Breadcrumb.new(
55
56
  level: :info,
56
- category: OP_NAME,
57
+ category: BREADCRUMB_CATEGORY,
57
58
  type: :info,
58
59
  data: {
59
60
  status: res.code.to_i,
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ # @api private
5
+ class Redis
6
+ OP_NAME ||= "db.redis.command"
7
+ LOGGER_NAME ||= :redis_logger
8
+
9
+ def initialize(commands, host, port, db)
10
+ @commands, @host, @port, @db = commands, host, port, db
11
+ end
12
+
13
+ def instrument
14
+ return yield unless Sentry.initialized?
15
+
16
+ record_span do
17
+ yield.tap do
18
+ record_breadcrumb
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :commands, :host, :port, :db
26
+
27
+ def record_span
28
+ return yield unless (transaction = Sentry.get_current_scope.get_transaction) && transaction.sampled
29
+
30
+ sentry_span = transaction.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
31
+
32
+ yield.tap do
33
+ sentry_span.set_description(commands_description)
34
+ sentry_span.set_data(:server, server_description)
35
+ sentry_span.set_timestamp(Sentry.utc_now.to_f)
36
+ end
37
+ end
38
+
39
+ def record_breadcrumb
40
+ return unless Sentry.configuration.breadcrumbs_logger.include?(LOGGER_NAME)
41
+
42
+ Sentry.add_breadcrumb(
43
+ Sentry::Breadcrumb.new(
44
+ level: :info,
45
+ category: OP_NAME,
46
+ type: :info,
47
+ data: {
48
+ commands: parsed_commands,
49
+ server: server_description
50
+ }
51
+ )
52
+ )
53
+ end
54
+
55
+ def commands_description
56
+ parsed_commands.map do |statement|
57
+ statement.values.join(" ").strip
58
+ end.join(", ")
59
+ end
60
+
61
+ def parsed_commands
62
+ commands.map do |statement|
63
+ command, key, *_values = statement
64
+
65
+ { command: command.to_s.upcase, key: key }
66
+ end
67
+ end
68
+
69
+ def server_description
70
+ "#{host}:#{port}/#{db}"
71
+ end
72
+
73
+ module Client
74
+ def logging(commands, &block)
75
+ Sentry::Redis.new(commands, host, port, db).instrument do
76
+ super
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ if defined?(::Redis::Client)
84
+ Sentry.register_patch do
85
+ patch = Sentry::Redis::Client
86
+ Redis::Client.prepend(patch) unless Redis::Client.ancestors.include?(patch)
87
+ end
88
+ end
data/lib/sentry/scope.rb CHANGED
@@ -173,7 +173,7 @@ module Sentry
173
173
  def set_contexts(contexts_hash)
174
174
  check_argument_type!(contexts_hash, Hash)
175
175
  @contexts.merge!(contexts_hash) do |key, old, new|
176
- new.merge(old)
176
+ old.merge(new)
177
177
  end
178
178
  end
179
179
 
@@ -164,13 +164,12 @@ module Sentry
164
164
  @name = UNLABELD_NAME
165
165
  end
166
166
 
167
- unless @sampled || @parent_sampled
167
+ if @sampled
168
+ event = hub.current_client.event_from_transaction(self)
169
+ hub.capture_event(event)
170
+ else
168
171
  hub.current_client.transport.record_lost_event(:sample_rate, 'transaction')
169
- return
170
172
  end
171
-
172
- event = hub.current_client.event_from_transaction(self)
173
- hub.capture_event(event)
174
173
  end
175
174
 
176
175
  protected
@@ -129,17 +129,14 @@ module Sentry
129
129
  def conn
130
130
  server = URI(@dsn.server)
131
131
 
132
- use_ssl = server.scheme == "https"
133
- port = use_ssl ? 443 : 80
134
-
135
132
  connection =
136
133
  if proxy = @transport_configuration.proxy
137
- ::Net::HTTP.new(server.hostname, port, proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password])
134
+ ::Net::HTTP.new(server.hostname, server.port, proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password])
138
135
  else
139
- ::Net::HTTP.new(server.hostname, port, nil)
136
+ ::Net::HTTP.new(server.hostname, server.port, nil)
140
137
  end
141
138
 
142
- connection.use_ssl = use_ssl
139
+ connection.use_ssl = server.scheme == "https"
143
140
  connection.read_timeout = @transport_configuration.timeout
144
141
  connection.write_timeout = @transport_configuration.timeout if connection.respond_to?(:write_timeout)
145
142
  connection.open_timeout = @transport_configuration.open_timeout
@@ -46,23 +46,19 @@ module Sentry
46
46
  end
47
47
 
48
48
  def send_event(event)
49
- event_hash = event.to_hash
50
- item_type = get_item_type(event_hash)
49
+ envelope = envelope_from_event(event)
50
+ send_envelope(envelope)
51
51
 
52
- if is_rate_limited?(item_type)
53
- log_info("Envelope [#{item_type}] not sent: rate limiting")
54
- record_lost_event(:ratelimit_backoff, item_type)
55
-
56
- return
57
- end
58
-
59
- encoded_data = encode(event)
52
+ event
53
+ end
60
54
 
61
- return nil unless encoded_data
55
+ def send_envelope(envelope)
56
+ reject_rate_limited_items(envelope)
62
57
 
63
- send_data(encoded_data)
58
+ return if envelope.items.empty?
64
59
 
65
- event
60
+ log_info("[Transport] Sending envelope with items [#{envelope.item_types.join(', ')}] #{envelope.event_id} to Sentry")
61
+ send_data(envelope.to_s)
66
62
  end
67
63
 
68
64
  def is_rate_limited?(item_type)
@@ -106,7 +102,7 @@ module Sentry
106
102
  'Sentry ' + fields.map { |key, value| "#{key}=#{value}" }.join(', ')
107
103
  end
108
104
 
109
- def encode(event)
105
+ def envelope_from_event(event)
110
106
  # Convert to hash
111
107
  event_payload = event.to_hash
112
108
  event_id = event_payload[:event_id] || event_payload["event_id"]
@@ -129,9 +125,8 @@ module Sentry
129
125
  client_report_headers, client_report_payload = fetch_pending_client_report
130
126
  envelope.add_item(client_report_headers, client_report_payload) if client_report_headers
131
127
 
132
- log_info("Sending envelope [#{item_type}] #{event_id} to Sentry")
133
128
 
134
- envelope.to_s
129
+ envelope
135
130
  end
136
131
 
137
132
  def record_lost_event(reason, item_type)
@@ -173,6 +168,19 @@ module Sentry
173
168
 
174
169
  [item_header, item_payload]
175
170
  end
171
+
172
+ def reject_rate_limited_items(envelope)
173
+ envelope.items.reject! do |item|
174
+ if is_rate_limited?(item.type)
175
+ log_info("[Transport] Envelope item [#{item.type}] not sent: rate limiting")
176
+ record_lost_event(:ratelimit_backoff, item.type)
177
+
178
+ true
179
+ else
180
+ false
181
+ end
182
+ end
183
+ end
176
184
  end
177
185
  end
178
186
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sentry
4
- VERSION = "5.0.1"
4
+ VERSION = "5.1.1"
5
5
  end
data/lib/sentry-ruby.rb CHANGED
@@ -31,6 +31,8 @@ end
31
31
  module Sentry
32
32
  META = { "name" => "sentry.ruby", "version" => Sentry::VERSION }.freeze
33
33
 
34
+ CAPTURED_SIGNATURE = :@__sentry_captured
35
+
34
36
  LOGGER_PROGNAME = "sentry".freeze
35
37
 
36
38
  SENTRY_TRACE_HEADER_NAME = "sentry-trace".freeze
@@ -111,9 +113,17 @@ module Sentry
111
113
 
112
114
  # @!method configuration
113
115
  # @!macro configuration
116
+ def configuration
117
+ return unless initialized?
118
+ get_current_client.configuration
119
+ end
120
+
114
121
  # @!method send_event
115
122
  # @!macro send_event
116
- def_delegators :get_current_client, :configuration, :send_event
123
+ def send_event(*args)
124
+ return unless initialized?
125
+ get_current_client.send_event(*args)
126
+ end
117
127
 
118
128
  # @!macro [new] set_extras
119
129
  # Updates the scope's extras attribute by merging with the old value.
@@ -135,13 +145,31 @@ module Sentry
135
145
 
136
146
  # @!method set_tags
137
147
  # @!macro set_tags
148
+ def set_tags(*args)
149
+ return unless initialized?
150
+ get_current_scope.set_tags(*args)
151
+ end
152
+
138
153
  # @!method set_extras
139
154
  # @!macro set_extras
155
+ def set_extras(*args)
156
+ return unless initialized?
157
+ get_current_scope.set_extras(*args)
158
+ end
159
+
140
160
  # @!method set_user
141
161
  # @!macro set_user
162
+ def set_user(*args)
163
+ return unless initialized?
164
+ get_current_scope.set_user(*args)
165
+ end
166
+
142
167
  # @!method set_context
143
168
  # @!macro set_context
144
- def_delegators :get_current_scope, :set_tags, :set_extras, :set_user, :set_context
169
+ def set_context(*args)
170
+ return unless initialized?
171
+ get_current_scope.set_context(*args)
172
+ end
145
173
 
146
174
  ##### Main APIs #####
147
175
 
@@ -201,7 +229,8 @@ module Sentry
201
229
  #
202
230
  # @return [Breadcrumb, nil]
203
231
  def add_breadcrumb(breadcrumb, **options)
204
- get_current_hub&.add_breadcrumb(breadcrumb, **options)
232
+ return unless initialized?
233
+ get_current_hub.add_breadcrumb(breadcrumb, **options)
205
234
  end
206
235
 
207
236
  # Returns the current active hub.
@@ -221,14 +250,16 @@ module Sentry
221
250
  # Returns the current active client.
222
251
  # @return [Client, nil]
223
252
  def get_current_client
224
- get_current_hub&.current_client
253
+ return unless initialized?
254
+ get_current_hub.current_client
225
255
  end
226
256
 
227
257
  # Returns the current active scope.
228
258
  #
229
259
  # @return [Scope, nil]
230
260
  def get_current_scope
231
- get_current_hub&.current_scope
261
+ return unless initialized?
262
+ get_current_hub.current_scope
232
263
  end
233
264
 
234
265
  # Clones the main thread's active hub and stores it to the current thread.
@@ -250,7 +281,8 @@ module Sentry
250
281
  # @yieldparam scope [Scope]
251
282
  # @return [void]
252
283
  def configure_scope(&block)
253
- get_current_hub&.configure_scope(&block)
284
+ return unless initialized?
285
+ get_current_hub.configure_scope(&block)
254
286
  end
255
287
 
256
288
  # Takes a block and yields a temporary scope.
@@ -274,7 +306,8 @@ module Sentry
274
306
  # @yieldparam scope [Scope]
275
307
  # @return [void]
276
308
  def with_scope(&block)
277
- get_current_hub&.with_scope(&block)
309
+ return unless initialized?
310
+ get_current_hub.with_scope(&block)
278
311
  end
279
312
 
280
313
  # Takes an exception and reports it to Sentry via the currently active hub.
@@ -282,7 +315,8 @@ module Sentry
282
315
  # @yieldparam scope [Scope]
283
316
  # @return [Event, nil]
284
317
  def capture_exception(exception, **options, &block)
285
- get_current_hub&.capture_exception(exception, **options, &block)
318
+ return unless initialized?
319
+ get_current_hub.capture_exception(exception, **options, &block)
286
320
  end
287
321
 
288
322
  # Takes a message string and reports it to Sentry via the currently active hub.
@@ -290,30 +324,41 @@ module Sentry
290
324
  # @yieldparam scope [Scope]
291
325
  # @return [Event, nil]
292
326
  def capture_message(message, **options, &block)
293
- get_current_hub&.capture_message(message, **options, &block)
327
+ return unless initialized?
328
+ get_current_hub.capture_message(message, **options, &block)
294
329
  end
295
330
 
296
331
  # Takes an instance of Sentry::Event and dispatches it to the currently active hub.
297
332
  #
298
333
  # @return [Event, nil]
299
334
  def capture_event(event)
300
- get_current_hub&.capture_event(event)
335
+ return unless initialized?
336
+ get_current_hub.capture_event(event)
301
337
  end
302
338
 
303
339
  # Takes or initializes a new Sentry::Transaction and makes a sampling decision for it.
304
340
  #
305
341
  # @return [Transaction, nil]
306
342
  def start_transaction(**options)
307
- get_current_hub&.start_transaction(**options)
343
+ return unless initialized?
344
+ get_current_hub.start_transaction(**options)
308
345
  end
309
346
 
310
347
  # Returns the id of the lastly reported Sentry::Event.
311
348
  #
312
349
  # @return [String, nil]
313
350
  def last_event_id
314
- get_current_hub&.last_event_id
351
+ return unless initialized?
352
+ get_current_hub.last_event_id
315
353
  end
316
354
 
355
+ # Checks if the exception object has been captured by the SDK.
356
+ #
357
+ # @return [Boolean]
358
+ def exception_captured?(exc)
359
+ return false unless initialized?
360
+ !!exc.instance_variable_get(CAPTURED_SIGNATURE)
361
+ end
317
362
 
318
363
  ##### Helpers #####
319
364
 
@@ -344,3 +389,4 @@ end
344
389
 
345
390
  # patches
346
391
  require "sentry/net/http"
392
+ require "sentry/redis"
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: 5.0.1
4
+ version: 5.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sentry Team
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-23 00:00:00.000000000 Z
11
+ date: 2022-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -73,6 +73,7 @@ files:
73
73
  - lib/sentry/rack.rb
74
74
  - lib/sentry/rack/capture_exceptions.rb
75
75
  - lib/sentry/rake.rb
76
+ - lib/sentry/redis.rb
76
77
  - lib/sentry/release_detector.rb
77
78
  - lib/sentry/scope.rb
78
79
  - lib/sentry/span.rb