isolator 0.10.0 → 0.11.0

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: 3f7e62d7f280c06f8ad704770d5ba5f14198006bccec0ce69e0e1aa99a8b32c7
4
- data.tar.gz: 4c4aabcfad57d3ba2da54b5e75b7ee057bf25a10d382094d05a04fec2f371c85
3
+ metadata.gz: fb3ecc2b4b93f35241a5f03804bc89fab1ab2a6d9d4fe33753ad827bc8a06ea5
4
+ data.tar.gz: 8385b3b3654b25d1ec2ec178305cac41049523339da8d56c6ba7defb31996640
5
5
  SHA512:
6
- metadata.gz: 565c283a6ddebe9e7d39472094f44f346a1ea1b49afc5ad9fe7aee1ecb98e5910fd5cdc4c0804c581132363338e39465faadab3f51eccbddaea8f731225fd364
7
- data.tar.gz: ab01a1494c0fac2c1920bced02ae86c6b6b26a6e420f3ef6e43fdd1bd300e14e259c3353b9af08b14787b5ebe499d816222998e837d0af4e16006d9c77a9f5d1
6
+ metadata.gz: a18d105a45e116af3120e4f9eefd69f1c533b14f1ce26b8bdd9c284fd99f87ddd8e3650ae3e7a73cc54592329726f549d16b374763f5bae6c71163e36499b55a
7
+ data.tar.gz: cb72be0a0d3aa004dab07392b875e0d70472806517d7d89acdafd133f332a50f59232aed8c919c12ce15cd12399b29f7ef8e33f986e445046b9171765db4ae74
data/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.11.0 (2023-09-27)
6
+
7
+ - Use Rails new `transaction.active_record` event if available to better handle edge cases. ([@palkan][])
8
+
9
+ - Fix logging non-UTF8 strings. ([@palkan][])
10
+
11
+ Fixes [#66](https://github.com/palkan/isolator/issues/66)
12
+
5
13
  ## 0.10.0 (2023-08-15)
6
14
 
7
15
  - Support multiple databases with DatabaseCleaner. ([@palkan][])
@@ -29,14 +29,22 @@ module Isolator
29
29
  def log_exception
30
30
  return unless Isolator.config.logger
31
31
 
32
- msg = "[ISOLATOR EXCEPTION]\n" \
33
- "#{exception.message}"
32
+ separator = " "
34
33
 
35
- filtered_backtrace.each do |offense_line|
36
- msg += "\n #{offense_line}"
37
- end
34
+ begin
35
+ msg = "[ISOLATOR EXCEPTION]\n" \
36
+ "#{exception.message}"
38
37
 
39
- Isolator.config.logger.warn(msg)
38
+ filtered_backtrace.each do |offense_line|
39
+ msg += "\n #{separator}#{offense_line}"
40
+ end
41
+
42
+ Isolator.config.logger.warn(msg)
43
+ rescue Encoding::CompatibilityError
44
+ should_retry = separator != " - "
45
+ separator = " - "
46
+ retry if should_retry
47
+ end
40
48
  end
41
49
 
42
50
  def send_notifications
@@ -2,4 +2,10 @@
2
2
 
3
3
  require_relative "./active_support_subscriber"
4
4
 
5
- Isolator::ActiveSupportSubscriber.subscribe!("sql.active_record")
5
+ # We rely on this feature introduced in 7.1.0.beta1: https://github.com/rails/rails/pull/49192
6
+ if ActiveRecord::VERSION::MAJOR >= 7 && ActiveRecord::VERSION::MINOR >= 1
7
+ require_relative "./active_support_transaction_subscriber"
8
+ Isolator::ActiveSupportTransactionSubscriber.subscribe!
9
+ else
10
+ Isolator::ActiveSupportSubscriber.subscribe!("sql.active_record")
11
+ end
@@ -7,14 +7,46 @@ module Isolator
7
7
  START_PATTERN = %r{(\ABEGIN|\ASAVEPOINT)}xi
8
8
  FINISH_PATTERN = %r{(\ACOMMIT|\AROLLBACK|\ARELEASE|\AEND TRANSACTION)}xi
9
9
 
10
- def self.subscribe!(event)
11
- ::ActiveSupport::Notifications.subscribe(event) do |_name, _start, _finish, _id, query|
12
- connection_id = query[:connection_id] || query[:connection]&.object_id || 0
13
- # Prevents "ArgumentError: invalid byte sequence in UTF-8" by replacing invalid byte sequence with "?"
14
- sanitized_query = query[:sql].encode("UTF-8", "binary", invalid: :replace, undef: :replace, replace: "?")
15
- Isolator.incr_transactions!(connection_id) if START_PATTERN.match?(sanitized_query)
16
- Isolator.decr_transactions!(connection_id) if FINISH_PATTERN.match?(sanitized_query)
10
+ class Subscriber
11
+ def start(event, id, payload)
12
+ return unless start_event?(payload[:sql])
13
+
14
+ connection_id = extract_connection_id(payload)
15
+
16
+ Isolator.incr_transactions!(connection_id)
17
+ end
18
+
19
+ def finish(event, id, payload)
20
+ return unless finish_event?(payload[:sql])
21
+
22
+ connection_id = extract_connection_id(payload)
23
+
24
+ Isolator.decr_transactions!(connection_id)
25
+ end
26
+
27
+ private
28
+
29
+ def start_event?(sql)
30
+ START_PATTERN.match?(sanitize_query(sql))
17
31
  end
32
+
33
+ def finish_event?(sql)
34
+ FINISH_PATTERN.match?(sanitize_query(sql))
35
+ end
36
+
37
+ # Prevents "ArgumentError: invalid byte sequence in UTF-8" by replacing invalid byte sequence with "?"
38
+ def sanitize_query(sql)
39
+ sql.encode("UTF-8", "binary", invalid: :replace, undef: :replace, replace: "?")
40
+ end
41
+
42
+ def extract_connection_id(payload)
43
+ payload[:connection_id] || payload[:connection]&.object_id || 0
44
+ end
45
+ end
46
+
47
+ def self.subscribe!(event)
48
+ subscriber = Subscriber.new
49
+ ::ActiveSupport::Notifications.subscribe(event, subscriber)
18
50
  end
19
51
  end
20
52
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Isolator
4
+ # ActiveSupport notifications subscriber for "transaction.active_record" event (new in Rails 7.1)
5
+ module ActiveSupportTransactionSubscriber
6
+ class Subscriber < ActiveSupportSubscriber::Subscriber
7
+ attr_reader :stacks
8
+
9
+ def initialize
10
+ @stacks = Hash.new { |h, k| h[k] = [] }
11
+ end
12
+
13
+ def start(event, id, payload)
14
+ if event.start_with?("transaction.")
15
+ connection_id = extract_transaction_connection_id(payload)
16
+
17
+ # transaction.active_record can be issued without a query (when we restart the transaction),
18
+ # so we should add a new one on the stack.
19
+ # Example: https://github.com/rails/rails/blob/ce49fa9b31cd4a21d43db39d0cea364bce28b51d/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb#L337
20
+ if stacks[connection_id].last == :raw
21
+ # Update the type of the last transaction event
22
+ stacks[connection_id].pop
23
+ stacks[connection_id] << :transaction
24
+ else
25
+ stacks[connection_id] << :transaction
26
+ Isolator.incr_transactions!(connection_id)
27
+ end
28
+ end
29
+ end
30
+
31
+ def finish(event, id, payload)
32
+ if event.start_with?("sql.")
33
+ if start_event?(payload[:sql])
34
+ connection_id = extract_connection_id(payload)
35
+
36
+ stacks[connection_id] << :raw
37
+
38
+ Isolator.incr_transactions!(connection_id)
39
+ end
40
+
41
+ if finish_event?(payload[:sql])
42
+ connection_id = extract_connection_id(payload)
43
+
44
+ # Decrement only if the transaction was started in the raw mode,
45
+ # otherwise we should wait for the "transaction" event
46
+ if stacks[connection_id].last == :raw
47
+ stacks[connection_id].pop
48
+ Isolator.decr_transactions!(connection_id)
49
+ end
50
+ end
51
+ end
52
+
53
+ if event.start_with?("transaction.")
54
+ connection_id = extract_transaction_connection_id(payload)
55
+ stacks[connection_id].pop
56
+
57
+ Isolator.decr_transactions!(connection_id)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def extract_transaction_connection_id(payload)
64
+ payload[:connection]&.object_id || 0
65
+ end
66
+ end
67
+
68
+ def self.subscribe!(event = "transaction.active_record", sql_event = "sql.active_record")
69
+ subscriber = Subscriber.new
70
+ ::ActiveSupport::Notifications.subscribe(event, subscriber)
71
+ ::ActiveSupport::Notifications.subscribe(sql_event, subscriber)
72
+ end
73
+ end
74
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Isolator
4
- VERSION = "0.10.0"
4
+ VERSION = "0.11.0"
5
5
  end
data/lib/isolator.rb CHANGED
@@ -198,15 +198,23 @@ module Isolator
198
198
 
199
199
  def debug!(msg)
200
200
  return unless debug_enabled
201
- msg = "[ISOLATOR DEBUG] #{msg}"
202
201
 
203
- if backtrace_cleaner && backtrace_length.positive?
204
- source = extract_source_location(caller)
202
+ separator = " ↳ "
205
203
 
206
- msg = "#{msg}\n ↳ #{source.join("\n")}" unless source.empty?
204
+ begin
205
+ msg = "[ISOLATOR DEBUG] #{msg}"
206
+ if backtrace_cleaner && backtrace_length.positive?
207
+ source = extract_source_location(caller)
208
+
209
+ msg = "#{msg}\n #{separator}#{source.join("\n")}" unless source.empty?
210
+ end
211
+
212
+ $stdout.puts(colorize_debug(msg))
213
+ rescue Encoding::CompatibilityError
214
+ should_retry = separator != " - "
215
+ separator = " - "
216
+ retry if should_retry
207
217
  end
208
-
209
- $stdout.puts(colorize_debug(msg))
210
218
  end
211
219
 
212
220
  def extract_source_location(locations)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: isolator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-16 00:00:00.000000000 Z
11
+ date: 2023-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sniffer
@@ -313,6 +313,7 @@ files:
313
313
  - lib/isolator/orm_adapters.rb
314
314
  - lib/isolator/orm_adapters/active_record.rb
315
315
  - lib/isolator/orm_adapters/active_support_subscriber.rb
316
+ - lib/isolator/orm_adapters/active_support_transaction_subscriber.rb
316
317
  - lib/isolator/orm_adapters/rom_active_support.rb
317
318
  - lib/isolator/railtie.rb
318
319
  - lib/isolator/simple_hashie.rb