activesupport 6.0.6.1 → 7.1.3.2

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.
Files changed (245) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +865 -438
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/active_support/actionable_error.rb +4 -2
  6. data/lib/active_support/array_inquirer.rb +4 -2
  7. data/lib/active_support/backtrace_cleaner.rb +30 -10
  8. data/lib/active_support/benchmarkable.rb +4 -3
  9. data/lib/active_support/broadcast_logger.rb +250 -0
  10. data/lib/active_support/builder.rb +1 -1
  11. data/lib/active_support/cache/coder.rb +153 -0
  12. data/lib/active_support/cache/entry.rb +134 -0
  13. data/lib/active_support/cache/file_store.rb +53 -20
  14. data/lib/active_support/cache/mem_cache_store.rb +208 -63
  15. data/lib/active_support/cache/memory_store.rb +120 -38
  16. data/lib/active_support/cache/null_store.rb +16 -2
  17. data/lib/active_support/cache/redis_cache_store.rb +201 -208
  18. data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
  19. data/lib/active_support/cache/strategy/local_cache.rb +73 -66
  20. data/lib/active_support/cache.rb +539 -261
  21. data/lib/active_support/callbacks.rb +273 -142
  22. data/lib/active_support/code_generator.rb +65 -0
  23. data/lib/active_support/concern.rb +53 -7
  24. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +44 -7
  25. data/lib/active_support/concurrency/null_lock.rb +13 -0
  26. data/lib/active_support/concurrency/share_lock.rb +2 -2
  27. data/lib/active_support/configurable.rb +19 -6
  28. data/lib/active_support/configuration_file.rb +51 -0
  29. data/lib/active_support/core_ext/array/access.rb +1 -5
  30. data/lib/active_support/core_ext/array/conversions.rb +15 -13
  31. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  32. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  33. data/lib/active_support/core_ext/benchmark.rb +2 -2
  34. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  35. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  36. data/lib/active_support/core_ext/class/subclasses.rb +19 -29
  37. data/lib/active_support/core_ext/date/blank.rb +1 -1
  38. data/lib/active_support/core_ext/date/calculations.rb +24 -9
  39. data/lib/active_support/core_ext/date/conversions.rb +18 -16
  40. data/lib/active_support/core_ext/date_and_time/calculations.rb +27 -4
  41. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  42. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  43. data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
  44. data/lib/active_support/core_ext/date_time/conversions.rb +19 -15
  45. data/lib/active_support/core_ext/digest/uuid.rb +30 -13
  46. data/lib/active_support/core_ext/enumerable.rb +146 -72
  47. data/lib/active_support/core_ext/erb/util.rb +196 -0
  48. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  49. data/lib/active_support/core_ext/hash/conversions.rb +3 -4
  50. data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
  51. data/lib/active_support/core_ext/hash/deep_transform_values.rb +4 -4
  52. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  53. data/lib/active_support/core_ext/hash/keys.rb +5 -5
  54. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  55. data/lib/active_support/core_ext/integer/inflections.rb +12 -12
  56. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  57. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  58. data/lib/active_support/core_ext/load_error.rb +1 -1
  59. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  60. data/lib/active_support/core_ext/module/attribute_accessors.rb +31 -29
  61. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +51 -20
  62. data/lib/active_support/core_ext/module/concerning.rb +14 -8
  63. data/lib/active_support/core_ext/module/delegation.rb +75 -42
  64. data/lib/active_support/core_ext/module/deprecation.rb +15 -12
  65. data/lib/active_support/core_ext/module/introspection.rb +1 -26
  66. data/lib/active_support/core_ext/name_error.rb +23 -2
  67. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  68. data/lib/active_support/core_ext/numeric/conversions.rb +82 -73
  69. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  70. data/lib/active_support/core_ext/object/blank.rb +2 -2
  71. data/lib/active_support/core_ext/object/deep_dup.rb +17 -1
  72. data/lib/active_support/core_ext/object/duplicable.rb +15 -4
  73. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  74. data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
  75. data/lib/active_support/core_ext/object/json.rb +52 -28
  76. data/lib/active_support/core_ext/object/to_query.rb +2 -4
  77. data/lib/active_support/core_ext/object/try.rb +20 -20
  78. data/lib/active_support/core_ext/object/with.rb +44 -0
  79. data/lib/active_support/core_ext/object/with_options.rb +25 -6
  80. data/lib/active_support/core_ext/object.rb +1 -0
  81. data/lib/active_support/core_ext/pathname/blank.rb +16 -0
  82. data/lib/active_support/core_ext/pathname/existence.rb +23 -0
  83. data/lib/active_support/core_ext/pathname.rb +4 -0
  84. data/lib/active_support/core_ext/range/compare_range.rb +6 -25
  85. data/lib/active_support/core_ext/range/conversions.rb +34 -13
  86. data/lib/active_support/core_ext/range/each.rb +1 -1
  87. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  88. data/lib/active_support/core_ext/range.rb +1 -2
  89. data/lib/active_support/core_ext/regexp.rb +8 -1
  90. data/lib/active_support/core_ext/securerandom.rb +25 -13
  91. data/lib/active_support/core_ext/string/access.rb +5 -24
  92. data/lib/active_support/core_ext/string/conversions.rb +3 -2
  93. data/lib/active_support/core_ext/string/filters.rb +21 -15
  94. data/lib/active_support/core_ext/string/indent.rb +1 -1
  95. data/lib/active_support/core_ext/string/inflections.rb +51 -10
  96. data/lib/active_support/core_ext/string/inquiry.rb +2 -1
  97. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  98. data/lib/active_support/core_ext/string/output_safety.rb +85 -194
  99. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  100. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  101. data/lib/active_support/core_ext/symbol.rb +3 -0
  102. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  103. data/lib/active_support/core_ext/time/calculations.rb +46 -8
  104. data/lib/active_support/core_ext/time/conversions.rb +16 -13
  105. data/lib/active_support/core_ext/time/zones.rb +12 -28
  106. data/lib/active_support/core_ext.rb +2 -1
  107. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  108. data/lib/active_support/current_attributes.rb +54 -22
  109. data/lib/active_support/deep_mergeable.rb +53 -0
  110. data/lib/active_support/dependencies/autoload.rb +17 -12
  111. data/lib/active_support/dependencies/interlock.rb +10 -18
  112. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  113. data/lib/active_support/dependencies.rb +58 -769
  114. data/lib/active_support/deprecation/behaviors.rb +77 -38
  115. data/lib/active_support/deprecation/constant_accessor.rb +5 -4
  116. data/lib/active_support/deprecation/deprecators.rb +104 -0
  117. data/lib/active_support/deprecation/disallowed.rb +54 -0
  118. data/lib/active_support/deprecation/instance_delegator.rb +31 -5
  119. data/lib/active_support/deprecation/method_wrappers.rb +12 -28
  120. data/lib/active_support/deprecation/proxy_wrappers.rb +40 -25
  121. data/lib/active_support/deprecation/reporting.rb +76 -16
  122. data/lib/active_support/deprecation.rb +36 -4
  123. data/lib/active_support/deprecator.rb +7 -0
  124. data/lib/active_support/descendants_tracker.rb +150 -68
  125. data/lib/active_support/digest.rb +5 -3
  126. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  127. data/lib/active_support/duration/iso8601_serializer.rb +24 -12
  128. data/lib/active_support/duration.rb +136 -56
  129. data/lib/active_support/encrypted_configuration.rb +72 -9
  130. data/lib/active_support/encrypted_file.rb +46 -13
  131. data/lib/active_support/environment_inquirer.rb +40 -0
  132. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  133. data/lib/active_support/error_reporter.rb +203 -0
  134. data/lib/active_support/evented_file_update_checker.rb +86 -137
  135. data/lib/active_support/execution_context/test_helper.rb +13 -0
  136. data/lib/active_support/execution_context.rb +53 -0
  137. data/lib/active_support/execution_wrapper.rb +31 -12
  138. data/lib/active_support/executor/test_helper.rb +7 -0
  139. data/lib/active_support/file_update_checker.rb +4 -2
  140. data/lib/active_support/fork_tracker.rb +79 -0
  141. data/lib/active_support/gem_version.rb +5 -5
  142. data/lib/active_support/gzip.rb +2 -0
  143. data/lib/active_support/hash_with_indifferent_access.rb +86 -42
  144. data/lib/active_support/html_safe_translation.rb +53 -0
  145. data/lib/active_support/i18n.rb +2 -1
  146. data/lib/active_support/i18n_railtie.rb +29 -27
  147. data/lib/active_support/inflector/inflections.rb +26 -9
  148. data/lib/active_support/inflector/methods.rb +54 -64
  149. data/lib/active_support/inflector/transliterate.rb +7 -5
  150. data/lib/active_support/isolated_execution_state.rb +76 -0
  151. data/lib/active_support/json/decoding.rb +6 -5
  152. data/lib/active_support/json/encoding.rb +31 -45
  153. data/lib/active_support/key_generator.rb +32 -7
  154. data/lib/active_support/lazy_load_hooks.rb +33 -7
  155. data/lib/active_support/locale/en.yml +10 -4
  156. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  157. data/lib/active_support/log_subscriber.rb +101 -32
  158. data/lib/active_support/logger.rb +9 -60
  159. data/lib/active_support/logger_silence.rb +2 -26
  160. data/lib/active_support/logger_thread_safe_level.rb +24 -25
  161. data/lib/active_support/message_encryptor.rb +205 -58
  162. data/lib/active_support/message_encryptors.rb +141 -0
  163. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  164. data/lib/active_support/message_pack/extensions.rb +292 -0
  165. data/lib/active_support/message_pack/serializer.rb +63 -0
  166. data/lib/active_support/message_pack.rb +50 -0
  167. data/lib/active_support/message_verifier.rb +237 -86
  168. data/lib/active_support/message_verifiers.rb +135 -0
  169. data/lib/active_support/messages/codec.rb +65 -0
  170. data/lib/active_support/messages/metadata.rb +112 -46
  171. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  172. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  173. data/lib/active_support/messages/rotator.rb +35 -32
  174. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  175. data/lib/active_support/multibyte/chars.rb +15 -52
  176. data/lib/active_support/multibyte/unicode.rb +8 -122
  177. data/lib/active_support/multibyte.rb +1 -1
  178. data/lib/active_support/notifications/fanout.rb +310 -105
  179. data/lib/active_support/notifications/instrumenter.rb +113 -48
  180. data/lib/active_support/notifications.rb +56 -29
  181. data/lib/active_support/number_helper/number_converter.rb +15 -8
  182. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  183. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  184. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  185. data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -5
  186. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
  187. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  188. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  189. data/lib/active_support/number_helper.rb +379 -304
  190. data/lib/active_support/option_merger.rb +11 -18
  191. data/lib/active_support/ordered_hash.rb +4 -4
  192. data/lib/active_support/ordered_options.rb +23 -3
  193. data/lib/active_support/parameter_filter.rb +104 -75
  194. data/lib/active_support/proxy_object.rb +2 -0
  195. data/lib/active_support/rails.rb +1 -4
  196. data/lib/active_support/railtie.rb +90 -6
  197. data/lib/active_support/reloader.rb +12 -4
  198. data/lib/active_support/rescuable.rb +18 -16
  199. data/lib/active_support/ruby_features.rb +7 -0
  200. data/lib/active_support/secure_compare_rotator.rb +58 -0
  201. data/lib/active_support/security_utils.rb +19 -12
  202. data/lib/active_support/string_inquirer.rb +5 -3
  203. data/lib/active_support/subscriber.rb +23 -47
  204. data/lib/active_support/syntax_error_proxy.rb +70 -0
  205. data/lib/active_support/tagged_logging.rb +84 -23
  206. data/lib/active_support/test_case.rb +166 -27
  207. data/lib/active_support/testing/assertions.rb +73 -20
  208. data/lib/active_support/testing/autorun.rb +0 -2
  209. data/lib/active_support/testing/constant_stubbing.rb +32 -0
  210. data/lib/active_support/testing/deprecation.rb +53 -2
  211. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  212. data/lib/active_support/testing/isolation.rb +30 -29
  213. data/lib/active_support/testing/method_call_assertions.rb +24 -11
  214. data/lib/active_support/testing/parallelization/server.rb +82 -0
  215. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  216. data/lib/active_support/testing/parallelization.rb +16 -95
  217. data/lib/active_support/testing/parallelize_executor.rb +81 -0
  218. data/lib/active_support/testing/stream.rb +4 -6
  219. data/lib/active_support/testing/strict_warnings.rb +39 -0
  220. data/lib/active_support/testing/tagged_logging.rb +1 -1
  221. data/lib/active_support/testing/time_helpers.rb +89 -19
  222. data/lib/active_support/time_with_zone.rb +105 -70
  223. data/lib/active_support/values/time_zone.rb +59 -26
  224. data/lib/active_support/version.rb +1 -1
  225. data/lib/active_support/xml_mini/jdom.rb +4 -11
  226. data/lib/active_support/xml_mini/libxml.rb +5 -5
  227. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  228. data/lib/active_support/xml_mini/nokogiri.rb +5 -5
  229. data/lib/active_support/xml_mini/nokogirisax.rb +2 -2
  230. data/lib/active_support/xml_mini/rexml.rb +9 -2
  231. data/lib/active_support/xml_mini.rb +7 -6
  232. data/lib/active_support.rb +40 -1
  233. metadata +127 -40
  234. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  235. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  236. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  237. data/lib/active_support/core_ext/marshal.rb +0 -24
  238. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  239. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  240. data/lib/active_support/core_ext/range/include_range.rb +0 -9
  241. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -23
  242. data/lib/active_support/core_ext/range/overlaps.rb +0 -10
  243. data/lib/active_support/core_ext/uri.rb +0 -25
  244. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
  245. data/lib/active_support/per_thread_registry.rb +0 -60
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/security_utils"
4
+ require "active_support/messages/rotator"
5
+
6
+ module ActiveSupport
7
+ # = Secure Compare Rotator
8
+ #
9
+ # The ActiveSupport::SecureCompareRotator is a wrapper around ActiveSupport::SecurityUtils.secure_compare
10
+ # and allows you to rotate a previously defined value to a new one.
11
+ #
12
+ # It can be used as follow:
13
+ #
14
+ # rotator = ActiveSupport::SecureCompareRotator.new('new_production_value')
15
+ # rotator.rotate('previous_production_value')
16
+ # rotator.secure_compare!('previous_production_value')
17
+ #
18
+ # One real use case example would be to rotate a basic auth credentials:
19
+ #
20
+ # class MyController < ApplicationController
21
+ # def authenticate_request
22
+ # rotator = ActiveSupport::SecureCompareRotator.new('new_password')
23
+ # rotator.rotate('old_password')
24
+ #
25
+ # authenticate_or_request_with_http_basic do |username, password|
26
+ # rotator.secure_compare!(password)
27
+ # rescue ActiveSupport::SecureCompareRotator::InvalidMatch
28
+ # false
29
+ # end
30
+ # end
31
+ # end
32
+ class SecureCompareRotator
33
+ include SecurityUtils
34
+
35
+ InvalidMatch = Class.new(StandardError)
36
+
37
+ def initialize(value, on_rotation: nil)
38
+ @value = value
39
+ @rotate_values = []
40
+ @on_rotation = on_rotation
41
+ end
42
+
43
+ def rotate(previous_value)
44
+ @rotate_values << previous_value
45
+ end
46
+
47
+ def secure_compare!(other_value, on_rotation: @on_rotation)
48
+ if secure_compare(@value, other_value)
49
+ true
50
+ elsif @rotate_values.any? { |value| secure_compare(value, other_value) }
51
+ on_rotation&.call
52
+ true
53
+ else
54
+ raise InvalidMatch
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,30 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest/sha2"
4
-
5
3
  module ActiveSupport
6
4
  module SecurityUtils
7
5
  # Constant time string comparison, for fixed length strings.
8
6
  #
9
7
  # The values compared should be of fixed length, such as strings
10
8
  # that have already been processed by HMAC. Raises in case of length mismatch.
11
- def fixed_length_secure_compare(a, b)
12
- raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
13
9
 
14
- l = a.unpack "C#{a.bytesize}"
10
+ if defined?(OpenSSL.fixed_length_secure_compare)
11
+ def fixed_length_secure_compare(a, b)
12
+ OpenSSL.fixed_length_secure_compare(a, b)
13
+ end
14
+ else
15
+ def fixed_length_secure_compare(a, b)
16
+ raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize
17
+
18
+ l = a.unpack "C#{a.bytesize}"
15
19
 
16
- res = 0
17
- b.each_byte { |byte| res |= byte ^ l.shift }
18
- res == 0
20
+ res = 0
21
+ b.each_byte { |byte| res |= byte ^ l.shift }
22
+ res == 0
23
+ end
19
24
  end
20
25
  module_function :fixed_length_secure_compare
21
26
 
22
- # Constant time string comparison, for variable length strings.
27
+ # Secure string comparison for strings of variable length.
23
28
  #
24
- # The values are first processed by SHA256, so that we don't leak length info
25
- # via timing attacks.
29
+ # While a timing attack would not be able to discern the content of
30
+ # a secret compared via secure_compare, it is possible to determine
31
+ # the secret length. This should be considered when using secure_compare
32
+ # to compare weak, short secrets to user input.
26
33
  def secure_compare(a, b)
27
- fixed_length_secure_compare(::Digest::SHA256.digest(a), ::Digest::SHA256.digest(b)) && a == b
34
+ a.bytesize == b.bytesize && fixed_length_secure_compare(a, b)
28
35
  end
29
36
  module_function :secure_compare
30
37
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
+ # = \String Inquirer
5
+ #
4
6
  # Wrapping a string in this class gives you a prettier way to test
5
7
  # for equality. The value returned by <tt>Rails.env</tt> is wrapped
6
8
  # in a StringInquirer object, so instead of calling this:
@@ -11,7 +13,7 @@ module ActiveSupport
11
13
  #
12
14
  # Rails.env.production?
13
15
  #
14
- # == Instantiating a new StringInquirer
16
+ # == Instantiating a new \StringInquirer
15
17
  #
16
18
  # vehicle = ActiveSupport::StringInquirer.new('car')
17
19
  # vehicle.car? # => true
@@ -19,11 +21,11 @@ module ActiveSupport
19
21
  class StringInquirer < String
20
22
  private
21
23
  def respond_to_missing?(method_name, include_private = false)
22
- (method_name[-1] == "?") || super
24
+ method_name.end_with?("?") || super
23
25
  end
24
26
 
25
27
  def method_missing(method_name, *arguments)
26
- if method_name[-1] == "?"
28
+ if method_name.end_with?("?")
27
29
  self == method_name[0..-2]
28
30
  else
29
31
  super
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/per_thread_registry"
4
3
  require "active_support/notifications"
5
4
 
6
5
  module ActiveSupport
7
- # ActiveSupport::Subscriber is an object set to consume
6
+ # = Active Support \Subscriber
7
+ #
8
+ # +ActiveSupport::Subscriber+ is an object set to consume
8
9
  # ActiveSupport::Notifications. The subscriber dispatches notifications to
9
10
  # a registered object based on its given namespace.
10
11
  #
@@ -21,9 +22,9 @@ module ActiveSupport
21
22
  # end
22
23
  # end
23
24
  #
24
- # After configured, whenever a "sql.active_record" notification is published,
25
- # it will properly dispatch the event (ActiveSupport::Notifications::Event) to
26
- # the +sql+ method.
25
+ # After configured, whenever a <tt>"sql.active_record"</tt> notification is
26
+ # published, it will properly dispatch the event
27
+ # (ActiveSupport::Notifications::Event) to the +sql+ method.
27
28
  #
28
29
  # We can detach a subscriber as well:
29
30
  #
@@ -31,15 +32,16 @@ module ActiveSupport
31
32
  class Subscriber
32
33
  class << self
33
34
  # Attach the subscriber to a namespace.
34
- def attach_to(namespace, subscriber = new, notifier = ActiveSupport::Notifications)
35
+ def attach_to(namespace, subscriber = new, notifier = ActiveSupport::Notifications, inherit_all: false)
35
36
  @namespace = namespace
36
37
  @subscriber = subscriber
37
38
  @notifier = notifier
39
+ @inherit_all = inherit_all
38
40
 
39
41
  subscribers << subscriber
40
42
 
41
43
  # Add event subscribers for all existing methods on the class.
42
- subscriber.public_methods(false).each do |event|
44
+ fetch_public_methods(subscriber, inherit_all).each do |event|
43
45
  add_event_subscriber(event)
44
46
  end
45
47
  end
@@ -55,7 +57,7 @@ module ActiveSupport
55
57
  subscribers.delete(subscriber)
56
58
 
57
59
  # Remove event subscribers of all existing methods on the class.
58
- subscriber.public_methods(false).each do |event|
60
+ fetch_public_methods(subscriber, true).each do |event|
59
61
  remove_event_subscriber(event)
60
62
  end
61
63
 
@@ -81,18 +83,18 @@ module ActiveSupport
81
83
  attr_reader :subscriber, :notifier, :namespace
82
84
 
83
85
  def add_event_subscriber(event) # :doc:
84
- return if invalid_event?(event.to_s)
86
+ return if invalid_event?(event)
85
87
 
86
88
  pattern = prepare_pattern(event)
87
89
 
88
- # Don't add multiple subscribers (eg. if methods are redefined).
90
+ # Don't add multiple subscribers (e.g. if methods are redefined).
89
91
  return if pattern_subscribed?(pattern)
90
92
 
91
93
  subscriber.patterns[pattern] = notifier.subscribe(pattern, subscriber)
92
94
  end
93
95
 
94
96
  def remove_event_subscriber(event) # :doc:
95
- return if invalid_event?(event.to_s)
97
+ return if invalid_event?(event)
96
98
 
97
99
  pattern = prepare_pattern(event)
98
100
 
@@ -107,7 +109,7 @@ module ActiveSupport
107
109
  end
108
110
 
109
111
  def invalid_event?(event)
110
- %w{ start finish }.include?(event.to_s)
112
+ %i{ start finish }.include?(event.to_sym)
111
113
  end
112
114
 
113
115
  def prepare_pattern(event)
@@ -117,53 +119,27 @@ module ActiveSupport
117
119
  def pattern_subscribed?(pattern)
118
120
  subscriber.patterns.key?(pattern)
119
121
  end
122
+
123
+ def fetch_public_methods(subscriber, inherit_all)
124
+ subscriber.public_methods(inherit_all) - Subscriber.public_instance_methods(true)
125
+ end
120
126
  end
121
127
 
122
128
  attr_reader :patterns # :nodoc:
123
129
 
124
130
  def initialize
125
- @queue_key = [self.class.name, object_id].join "-"
126
131
  @patterns = {}
127
132
  super
128
133
  end
129
134
 
130
- def start(name, id, payload)
131
- event = ActiveSupport::Notifications::Event.new(name, nil, nil, id, payload)
132
- event.start!
133
- parent = event_stack.last
134
- parent << event if parent
135
-
136
- event_stack.push event
137
- end
138
-
139
- def finish(name, id, payload)
140
- event = event_stack.pop
141
- event.finish!
142
- event.payload.merge!(payload)
143
-
144
- method = name.split(".").first
135
+ def call(event)
136
+ method = event.name[0, event.name.index(".")]
145
137
  send(method, event)
146
138
  end
147
139
 
148
- private
149
- def event_stack
150
- SubscriberQueueRegistry.instance.get_queue(@queue_key)
151
- end
152
- end
153
-
154
- # This is a registry for all the event stacks kept for subscribers.
155
- #
156
- # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
157
- # for further details.
158
- class SubscriberQueueRegistry # :nodoc:
159
- extend PerThreadRegistry
160
-
161
- def initialize
162
- @registry = {}
163
- end
164
-
165
- def get_queue(queue_key)
166
- @registry[queue_key] ||= []
140
+ def publish_event(event) # :nodoc:
141
+ method = event.name[0, event.name.index(".")]
142
+ send(method, event)
167
143
  end
168
144
  end
169
145
  end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
5
+ module ActiveSupport
6
+ # This is a class for wrapping syntax errors. The purpose of this class
7
+ # is to enhance the backtraces on SyntaxError exceptions to include the
8
+ # source location of the syntax error. That way we can display the error
9
+ # source on error pages in development.
10
+ class SyntaxErrorProxy < DelegateClass(SyntaxError) # :nodoc:
11
+ def backtrace
12
+ parse_message_for_trace + super
13
+ end
14
+
15
+ class BacktraceLocation < Struct.new(:path, :lineno, :to_s) # :nodoc:
16
+ def spot(_)
17
+ end
18
+
19
+ def label
20
+ end
21
+ end
22
+
23
+ class BacktraceLocationProxy < DelegateClass(Thread::Backtrace::Location) # :nodoc:
24
+ def initialize(loc, ex)
25
+ super(loc)
26
+ @ex = ex
27
+ end
28
+
29
+ def spot(_)
30
+ super(@ex.__getobj__)
31
+ end
32
+ end
33
+
34
+ def backtrace_locations
35
+ return nil if super.nil?
36
+
37
+ parse_message_for_trace.map { |trace|
38
+ file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace
39
+ BacktraceLocation.new(file, line.to_i, trace)
40
+ # We have to wrap these backtrace locations because we need the
41
+ # spot information to come from the originating exception, not the
42
+ # proxy object that's generating these
43
+ } + super.map { |loc| BacktraceLocationProxy.new(loc, self) }
44
+ end
45
+
46
+ private
47
+ def parse_message_for_trace
48
+ if source_location_eval?
49
+ # If the exception is coming from a call to eval, we need to keep
50
+ # the path of the file in which eval was called to ensure we can
51
+ # return the right source fragment to show the location of the
52
+ # error
53
+ location = __getobj__.backtrace_locations[0]
54
+ ["#{location.path}:#{location.lineno}: #{__getobj__}"]
55
+ else
56
+ __getobj__.to_s.split("\n")
57
+ end
58
+ end
59
+
60
+ if SyntaxError.method_defined?(:path) # Ruby 3.3+
61
+ def source_location_eval?
62
+ __getobj__.path.start_with?("(eval")
63
+ end
64
+ else # 3.2 and older versions of Ruby
65
+ def source_location_eval?
66
+ __getobj__.to_s.start_with?("(eval")
67
+ end
68
+ end
69
+ end
70
+ end
@@ -6,12 +6,23 @@ require "logger"
6
6
  require "active_support/logger"
7
7
 
8
8
  module ActiveSupport
9
+ # = Active Support Tagged Logging
10
+ #
9
11
  # Wraps any standard Logger object to provide tagging capabilities.
10
12
  #
13
+ # May be called with a block:
14
+ #
11
15
  # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
12
- # logger.tagged('BCX') { logger.info 'Stuff' } # Logs "[BCX] Stuff"
13
- # logger.tagged('BCX', "Jason") { logger.info 'Stuff' } # Logs "[BCX] [Jason] Stuff"
14
- # logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Logs "[BCX] [Jason] Stuff"
16
+ # logger.tagged('BCX') { logger.info 'Stuff' } # Logs "[BCX] Stuff"
17
+ # logger.tagged('BCX', "Jason") { |tagged_logger| tagged_logger.info 'Stuff' } # Logs "[BCX] [Jason] Stuff"
18
+ # logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Logs "[BCX] [Jason] Stuff"
19
+ #
20
+ # If called without a block, a new logger will be returned with applied tags:
21
+ #
22
+ # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
23
+ # logger.tagged("BCX").info "Stuff" # Logs "[BCX] Stuff"
24
+ # logger.tagged("BCX", "Jason").info "Stuff" # Logs "[BCX] [Jason] Stuff"
25
+ # logger.tagged("BCX").tagged("Jason").info "Stuff" # Logs "[BCX] [Jason] Stuff"
15
26
  #
16
27
  # This is used by the default Rails.logger as configured by Railties to make
17
28
  # it easy to stamp log lines with subdomains, request ids, and anything else
@@ -20,51 +31,94 @@ module ActiveSupport
20
31
  module Formatter # :nodoc:
21
32
  # This method is invoked when a log event occurs.
22
33
  def call(severity, timestamp, progname, msg)
23
- super(severity, timestamp, progname, "#{tags_text}#{msg}")
34
+ super(severity, timestamp, progname, tag_stack.format_message(msg))
24
35
  end
25
36
 
26
37
  def tagged(*tags)
27
- new_tags = push_tags(*tags)
38
+ pushed_count = tag_stack.push_tags(tags).size
28
39
  yield self
29
40
  ensure
30
- pop_tags(new_tags.size)
41
+ pop_tags(pushed_count)
31
42
  end
32
43
 
33
44
  def push_tags(*tags)
34
- tags.flatten.reject(&:blank?).tap do |new_tags|
35
- current_tags.concat new_tags
36
- end
45
+ tag_stack.push_tags(tags)
37
46
  end
38
47
 
39
- def pop_tags(size = 1)
40
- current_tags.pop size
48
+ def pop_tags(count = 1)
49
+ tag_stack.pop_tags(count)
41
50
  end
42
51
 
43
52
  def clear_tags!
44
- current_tags.clear
53
+ tag_stack.clear
45
54
  end
46
55
 
47
- def current_tags
56
+ def tag_stack
48
57
  # We use our object ID here to avoid conflicting with other instances
49
- thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}"
50
- Thread.current[thread_key] ||= []
58
+ @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}"
59
+ IsolatedExecutionState[@thread_key] ||= TagStack.new
60
+ end
61
+
62
+ def current_tags
63
+ tag_stack.tags
51
64
  end
52
65
 
53
66
  def tags_text
54
- tags = current_tags
55
- if tags.one?
56
- "[#{tags[0]}] "
57
- elsif tags.any?
58
- tags.collect { |tag| "[#{tag}] " }.join
67
+ tag_stack.format_message("")
68
+ end
69
+ end
70
+
71
+ class TagStack # :nodoc:
72
+ attr_reader :tags
73
+
74
+ def initialize
75
+ @tags = []
76
+ @tags_string = nil
77
+ end
78
+
79
+ def push_tags(tags)
80
+ @tags_string = nil
81
+ tags.flatten!
82
+ tags.reject!(&:blank?)
83
+ @tags.concat(tags)
84
+ tags
85
+ end
86
+
87
+ def pop_tags(count)
88
+ @tags_string = nil
89
+ @tags.pop(count)
90
+ end
91
+
92
+ def clear
93
+ @tags_string = nil
94
+ @tags.clear
95
+ end
96
+
97
+ def format_message(message)
98
+ if @tags.empty?
99
+ message
100
+ elsif @tags.size == 1
101
+ "[#{@tags[0]}] #{message}"
102
+ else
103
+ @tags_string ||= "[#{@tags.join("] [")}] "
104
+ "#{@tags_string}#{message}"
59
105
  end
60
106
  end
61
107
  end
62
108
 
109
+ module LocalTagStorage # :nodoc:
110
+ attr_accessor :tag_stack
111
+
112
+ def self.extended(base)
113
+ base.tag_stack = TagStack.new
114
+ end
115
+ end
116
+
63
117
  def self.new(logger)
64
- logger = logger.dup
118
+ logger = logger.clone
65
119
 
66
120
  if logger.formatter
67
- logger.formatter = logger.formatter.dup
121
+ logger.formatter = logger.formatter.clone
68
122
  else
69
123
  # Ensure we set a default formatter so we aren't extending nil!
70
124
  logger.formatter = ActiveSupport::Logger::SimpleFormatter.new
@@ -77,7 +131,14 @@ module ActiveSupport
77
131
  delegate :push_tags, :pop_tags, :clear_tags!, to: :formatter
78
132
 
79
133
  def tagged(*tags)
80
- formatter.tagged(*tags) { yield self }
134
+ if block_given?
135
+ formatter.tagged(*tags) { yield self }
136
+ else
137
+ logger = ActiveSupport::TaggedLogging.new(self)
138
+ logger.formatter.extend LocalTagStorage
139
+ logger.push_tags(*formatter.current_tags, *tags)
140
+ logger
141
+ end
81
142
  end
82
143
 
83
144
  def flush