activesupport 7.0.6 → 7.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +847 -267
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/lib/active_support/actionable_error.rb +3 -1
  6. data/lib/active_support/array_inquirer.rb +2 -0
  7. data/lib/active_support/backtrace_cleaner.rb +25 -5
  8. data/lib/active_support/benchmarkable.rb +1 -0
  9. data/lib/active_support/broadcast_logger.rb +242 -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 +128 -0
  13. data/lib/active_support/cache/file_store.rb +36 -9
  14. data/lib/active_support/cache/mem_cache_store.rb +84 -68
  15. data/lib/active_support/cache/memory_store.rb +75 -21
  16. data/lib/active_support/cache/null_store.rb +6 -0
  17. data/lib/active_support/cache/redis_cache_store.rb +136 -131
  18. data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
  19. data/lib/active_support/cache/strategy/local_cache.rb +21 -9
  20. data/lib/active_support/cache.rb +298 -246
  21. data/lib/active_support/callbacks.rb +44 -21
  22. data/lib/active_support/concern.rb +4 -2
  23. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +42 -3
  24. data/lib/active_support/concurrency/null_lock.rb +13 -0
  25. data/lib/active_support/configurable.rb +10 -0
  26. data/lib/active_support/core_ext/array/conversions.rb +2 -1
  27. data/lib/active_support/core_ext/array.rb +0 -1
  28. data/lib/active_support/core_ext/class/subclasses.rb +13 -10
  29. data/lib/active_support/core_ext/date/conversions.rb +1 -0
  30. data/lib/active_support/core_ext/date.rb +0 -1
  31. data/lib/active_support/core_ext/date_and_time/calculations.rb +10 -0
  32. data/lib/active_support/core_ext/date_time/conversions.rb +6 -2
  33. data/lib/active_support/core_ext/date_time.rb +0 -1
  34. data/lib/active_support/core_ext/digest/uuid.rb +1 -10
  35. data/lib/active_support/core_ext/enumerable.rb +3 -70
  36. data/lib/active_support/core_ext/erb/util.rb +196 -0
  37. data/lib/active_support/core_ext/hash/conversions.rb +1 -1
  38. data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
  39. data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
  40. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +34 -16
  41. data/lib/active_support/core_ext/module/delegation.rb +40 -11
  42. data/lib/active_support/core_ext/module/deprecation.rb +15 -12
  43. data/lib/active_support/core_ext/module/introspection.rb +0 -1
  44. data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
  45. data/lib/active_support/core_ext/numeric/conversions.rb +2 -0
  46. data/lib/active_support/core_ext/numeric.rb +0 -1
  47. data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
  48. data/lib/active_support/core_ext/object/inclusion.rb +13 -5
  49. data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
  50. data/lib/active_support/core_ext/object/json.rb +11 -3
  51. data/lib/active_support/core_ext/object/with.rb +44 -0
  52. data/lib/active_support/core_ext/object/with_options.rb +3 -3
  53. data/lib/active_support/core_ext/object.rb +1 -0
  54. data/lib/active_support/core_ext/pathname/blank.rb +16 -0
  55. data/lib/active_support/core_ext/pathname/existence.rb +2 -0
  56. data/lib/active_support/core_ext/pathname.rb +1 -0
  57. data/lib/active_support/core_ext/range/conversions.rb +28 -7
  58. data/lib/active_support/core_ext/range/overlap.rb +40 -0
  59. data/lib/active_support/core_ext/range.rb +1 -2
  60. data/lib/active_support/core_ext/securerandom.rb +24 -12
  61. data/lib/active_support/core_ext/string/filters.rb +20 -14
  62. data/lib/active_support/core_ext/string/inflections.rb +16 -5
  63. data/lib/active_support/core_ext/string/output_safety.rb +38 -174
  64. data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
  65. data/lib/active_support/core_ext/time/calculations.rb +18 -2
  66. data/lib/active_support/core_ext/time/conversions.rb +2 -2
  67. data/lib/active_support/core_ext/time/zones.rb +4 -4
  68. data/lib/active_support/core_ext/time.rb +0 -1
  69. data/lib/active_support/current_attributes.rb +15 -6
  70. data/lib/active_support/deep_mergeable.rb +53 -0
  71. data/lib/active_support/dependencies/autoload.rb +17 -12
  72. data/lib/active_support/deprecation/behaviors.rb +55 -34
  73. data/lib/active_support/deprecation/constant_accessor.rb +5 -4
  74. data/lib/active_support/deprecation/deprecators.rb +104 -0
  75. data/lib/active_support/deprecation/disallowed.rb +3 -5
  76. data/lib/active_support/deprecation/instance_delegator.rb +31 -4
  77. data/lib/active_support/deprecation/method_wrappers.rb +6 -23
  78. data/lib/active_support/deprecation/proxy_wrappers.rb +37 -22
  79. data/lib/active_support/deprecation/reporting.rb +35 -21
  80. data/lib/active_support/deprecation.rb +32 -5
  81. data/lib/active_support/deprecator.rb +7 -0
  82. data/lib/active_support/descendants_tracker.rb +104 -132
  83. data/lib/active_support/duration/iso8601_serializer.rb +0 -2
  84. data/lib/active_support/duration.rb +2 -1
  85. data/lib/active_support/encrypted_configuration.rb +30 -9
  86. data/lib/active_support/encrypted_file.rb +16 -12
  87. data/lib/active_support/environment_inquirer.rb +22 -2
  88. data/lib/active_support/error_reporter/test_helper.rb +15 -0
  89. data/lib/active_support/error_reporter.rb +121 -35
  90. data/lib/active_support/evented_file_update_checker.rb +3 -1
  91. data/lib/active_support/execution_wrapper.rb +4 -4
  92. data/lib/active_support/file_update_checker.rb +4 -2
  93. data/lib/active_support/fork_tracker.rb +10 -2
  94. data/lib/active_support/gem_version.rb +3 -3
  95. data/lib/active_support/gzip.rb +2 -0
  96. data/lib/active_support/hash_with_indifferent_access.rb +35 -17
  97. data/lib/active_support/i18n.rb +1 -1
  98. data/lib/active_support/i18n_railtie.rb +20 -13
  99. data/lib/active_support/inflector/inflections.rb +2 -0
  100. data/lib/active_support/inflector/methods.rb +23 -11
  101. data/lib/active_support/inflector/transliterate.rb +3 -1
  102. data/lib/active_support/isolated_execution_state.rb +26 -22
  103. data/lib/active_support/json/decoding.rb +2 -1
  104. data/lib/active_support/json/encoding.rb +25 -43
  105. data/lib/active_support/key_generator.rb +9 -1
  106. data/lib/active_support/lazy_load_hooks.rb +6 -4
  107. data/lib/active_support/locale/en.yml +2 -0
  108. data/lib/active_support/log_subscriber.rb +78 -33
  109. data/lib/active_support/logger.rb +9 -60
  110. data/lib/active_support/logger_thread_safe_level.rb +10 -24
  111. data/lib/active_support/message_encryptor.rb +197 -53
  112. data/lib/active_support/message_encryptors.rb +141 -0
  113. data/lib/active_support/message_pack/cache_serializer.rb +23 -0
  114. data/lib/active_support/message_pack/extensions.rb +292 -0
  115. data/lib/active_support/message_pack/serializer.rb +63 -0
  116. data/lib/active_support/message_pack.rb +50 -0
  117. data/lib/active_support/message_verifier.rb +212 -93
  118. data/lib/active_support/message_verifiers.rb +135 -0
  119. data/lib/active_support/messages/codec.rb +65 -0
  120. data/lib/active_support/messages/metadata.rb +111 -45
  121. data/lib/active_support/messages/rotation_coordinator.rb +93 -0
  122. data/lib/active_support/messages/rotator.rb +34 -32
  123. data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
  124. data/lib/active_support/multibyte/chars.rb +2 -0
  125. data/lib/active_support/multibyte/unicode.rb +9 -37
  126. data/lib/active_support/notifications/fanout.rb +239 -81
  127. data/lib/active_support/notifications/instrumenter.rb +77 -20
  128. data/lib/active_support/notifications.rb +1 -1
  129. data/lib/active_support/number_helper/number_converter.rb +14 -5
  130. data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -6
  131. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  132. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
  133. data/lib/active_support/ordered_hash.rb +3 -3
  134. data/lib/active_support/ordered_options.rb +14 -0
  135. data/lib/active_support/parameter_filter.rb +84 -69
  136. data/lib/active_support/proxy_object.rb +2 -0
  137. data/lib/active_support/railtie.rb +33 -21
  138. data/lib/active_support/reloader.rb +12 -4
  139. data/lib/active_support/rescuable.rb +2 -0
  140. data/lib/active_support/secure_compare_rotator.rb +16 -9
  141. data/lib/active_support/string_inquirer.rb +3 -1
  142. data/lib/active_support/subscriber.rb +9 -27
  143. data/lib/active_support/syntax_error_proxy.rb +49 -0
  144. data/lib/active_support/tagged_logging.rb +60 -24
  145. data/lib/active_support/test_case.rb +153 -6
  146. data/lib/active_support/testing/assertions.rb +25 -9
  147. data/lib/active_support/testing/autorun.rb +0 -2
  148. data/lib/active_support/testing/constant_stubbing.rb +32 -0
  149. data/lib/active_support/testing/deprecation.rb +25 -25
  150. data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
  151. data/lib/active_support/testing/isolation.rb +1 -1
  152. data/lib/active_support/testing/method_call_assertions.rb +21 -8
  153. data/lib/active_support/testing/parallelize_executor.rb +8 -3
  154. data/lib/active_support/testing/stream.rb +1 -1
  155. data/lib/active_support/testing/strict_warnings.rb +38 -0
  156. data/lib/active_support/testing/time_helpers.rb +32 -14
  157. data/lib/active_support/time_with_zone.rb +6 -35
  158. data/lib/active_support/values/time_zone.rb +9 -7
  159. data/lib/active_support/version.rb +1 -1
  160. data/lib/active_support/xml_mini/jdom.rb +3 -10
  161. data/lib/active_support/xml_mini/nokogiri.rb +1 -1
  162. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  163. data/lib/active_support/xml_mini/rexml.rb +1 -1
  164. data/lib/active_support/xml_mini.rb +2 -2
  165. data/lib/active_support.rb +14 -3
  166. metadata +106 -19
  167. data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
  168. data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -26
  169. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -22
  170. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
  171. data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -26
  172. data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -5
  173. data/lib/active_support/core_ext/range/overlaps.rb +0 -10
  174. data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -22
  175. data/lib/active_support/core_ext/uri.rb +0 -5
  176. data/lib/active_support/per_thread_registry.rb +0 -65
@@ -62,10 +62,12 @@ module ActiveSupport
62
62
  # Transliteration is restricted to UTF-8, US-ASCII, and GB18030 strings.
63
63
  # Other encodings will raise an ArgumentError.
64
64
  def transliterate(string, replacement = "?", locale: nil)
65
- string = string.dup if string.frozen?
66
65
  raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String)
67
66
  raise ArgumentError, "Cannot transliterate strings with #{string.encoding} encoding" unless ALLOWED_ENCODINGS_FOR_TRANSLITERATE.include?(string.encoding)
68
67
 
68
+ return string.dup if string.ascii_only?
69
+ string = string.dup if string.frozen?
70
+
69
71
  input_encoding = string.encoding
70
72
 
71
73
  # US-ASCII is a subset of UTF-8 so we'll force encoding as UTF-8 if
@@ -4,25 +4,30 @@ require "fiber"
4
4
 
5
5
  module ActiveSupport
6
6
  module IsolatedExecutionState # :nodoc:
7
- @isolation_level = :thread
7
+ @isolation_level = nil
8
8
 
9
9
  Thread.attr_accessor :active_support_execution_state
10
10
  Fiber.attr_accessor :active_support_execution_state
11
11
 
12
12
  class << self
13
- attr_reader :isolation_level
13
+ attr_reader :isolation_level, :scope
14
14
 
15
15
  def isolation_level=(level)
16
+ return if level == @isolation_level
17
+
16
18
  unless %i(thread fiber).include?(level)
17
19
  raise ArgumentError, "isolation_level must be `:thread` or `:fiber`, got: `#{level.inspect}`"
18
20
  end
19
21
 
20
- if level != isolation_level
21
- clear
22
- singleton_class.alias_method(:current, "current_#{level}")
23
- singleton_class.send(:private, :current)
24
- @isolation_level = level
25
- end
22
+ clear if @isolation_level
23
+
24
+ @scope =
25
+ case level
26
+ when :thread; Thread
27
+ when :fiber; Fiber
28
+ end
29
+
30
+ @isolation_level = level
26
31
  end
27
32
 
28
33
  def unique_id
@@ -30,43 +35,42 @@ module ActiveSupport
30
35
  end
31
36
 
32
37
  def [](key)
33
- current[key]
38
+ state[key]
34
39
  end
35
40
 
36
41
  def []=(key, value)
37
- current[key] = value
42
+ state[key] = value
38
43
  end
39
44
 
40
45
  def key?(key)
41
- current.key?(key)
46
+ state.key?(key)
42
47
  end
43
48
 
44
49
  def delete(key)
45
- current.delete(key)
50
+ state.delete(key)
46
51
  end
47
52
 
48
53
  def clear
49
- current.clear
54
+ state.clear
55
+ end
56
+
57
+ def context
58
+ scope.current
50
59
  end
51
60
 
52
61
  def share_with(other)
53
62
  # Action Controller streaming spawns a new thread and copy thread locals.
54
63
  # We do the same here for backward compatibility, but this is very much a hack
55
64
  # and streaming should be rethought.
56
- context = @isolation_level == :thread ? Thread.current : Fiber.current
57
65
  context.active_support_execution_state = other.active_support_execution_state.dup
58
66
  end
59
67
 
60
68
  private
61
- def current_thread
62
- Thread.current.active_support_execution_state ||= {}
63
- end
64
-
65
- def current_fiber
66
- Fiber.current.active_support_execution_state ||= {}
69
+ def state
70
+ context.active_support_execution_state ||= {}
67
71
  end
68
-
69
- alias_method :current, :current_thread
70
72
  end
73
+
74
+ self.isolation_level = :thread
71
75
  end
72
76
  end
@@ -5,7 +5,7 @@ require "active_support/core_ext/module/delegation"
5
5
  require "json"
6
6
 
7
7
  module ActiveSupport
8
- # Look for and parse json strings that look like ISO 8601 times.
8
+ # Look for and parse JSON strings that look like ISO 8601 times.
9
9
  mattr_accessor :parse_json_times
10
10
 
11
11
  module JSON
@@ -28,6 +28,7 @@ module ActiveSupport
28
28
  data
29
29
  end
30
30
  end
31
+ alias_method :load, :decode
31
32
 
32
33
  # Returns the class of the error that will be raised when there is an
33
34
  # error in decoding JSON. Using this method means you won't directly
@@ -18,8 +18,11 @@ module ActiveSupport
18
18
  #
19
19
  # ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
20
20
  # # => "{\"team\":\"rails\",\"players\":\"36\"}"
21
- def self.encode(value, options = nil)
22
- Encoding.json_encoder.new(options).encode(value)
21
+ class << self
22
+ def encode(value, options = nil)
23
+ Encoding.json_encoder.new(options).encode(value)
24
+ end
25
+ alias_method :dump, :encode
23
26
  end
24
27
 
25
28
  module Encoding # :nodoc:
@@ -32,49 +35,27 @@ module ActiveSupport
32
35
 
33
36
  # Encode the given object into a JSON string
34
37
  def encode(value)
35
- stringify jsonify value.as_json(options.dup)
36
- end
38
+ unless options.empty?
39
+ value = value.as_json(options.dup)
40
+ end
41
+ json = stringify(jsonify(value))
37
42
 
38
- private
39
43
  # Rails does more escaping than the JSON gem natively does (we
40
44
  # escape \u2028 and \u2029 and optionally >, <, & to work around
41
45
  # certain browser problems).
42
- ESCAPED_CHARS = {
43
- "\u2028" => '\u2028',
44
- "\u2029" => '\u2029',
45
- ">" => '\u003e',
46
- "<" => '\u003c',
47
- "&" => '\u0026',
48
- }
49
-
50
- ESCAPE_REGEX_WITH_HTML_ENTITIES = /[\u2028\u2029><&]/u
51
- ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = /[\u2028\u2029]/u
52
-
53
- # This class wraps all the strings we see and does the extra escaping
54
- class EscapedString < String # :nodoc:
55
- def to_json(*)
56
- if Encoding.escape_html_entities_in_json
57
- s = super
58
- s.gsub! ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS
59
- s
60
- else
61
- s = super
62
- s.gsub! ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS
63
- s
64
- end
65
- end
66
-
67
- def to_s
68
- self
69
- end
46
+ if Encoding.escape_html_entities_in_json
47
+ json.gsub!(">", '\u003e')
48
+ json.gsub!("<", '\u003c')
49
+ json.gsub!("&", '\u0026')
70
50
  end
51
+ json.gsub!("\u2028", '\u2028')
52
+ json.gsub!("\u2029", '\u2029')
53
+ json
54
+ end
71
55
 
72
- # Mark these as private so we don't leak encoding-specific constructs
73
- private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES,
74
- :ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString
75
-
56
+ private
76
57
  # Convert an object into a "JSON-ready" representation composed of
77
- # primitives like Hash, Array, String, Numeric,
58
+ # primitives like Hash, Array, String, Symbol, Numeric,
78
59
  # and +true+/+false+/+nil+.
79
60
  # Recursively calls #as_json to the object to recursively build a
80
61
  # fully JSON-ready object.
@@ -88,14 +69,15 @@ module ActiveSupport
88
69
  # calls.
89
70
  def jsonify(value)
90
71
  case value
91
- when String
92
- EscapedString.new(value)
93
- when Numeric, NilClass, TrueClass, FalseClass
72
+ when String, Integer, Symbol, nil, true, false
73
+ value
74
+ when Numeric
94
75
  value.as_json
95
76
  when Hash
96
77
  result = {}
97
78
  value.each do |k, v|
98
- result[jsonify(k)] = jsonify(v)
79
+ k = k.to_s unless Symbol === k || String === k
80
+ result[k] = jsonify(v)
99
81
  end
100
82
  result
101
83
  when Array
@@ -124,7 +106,7 @@ module ActiveSupport
124
106
  # Defaults to 3 (equivalent to millisecond precision)
125
107
  attr_accessor :time_precision
126
108
 
127
- # Sets the encoder used by Rails to encode Ruby objects into JSON strings
109
+ # Sets the encoder used by \Rails to encode Ruby objects into JSON strings
128
110
  # in +Object#to_json+ and +ActiveSupport::JSON.encode+.
129
111
  attr_accessor :json_encoder
130
112
  end
@@ -4,9 +4,11 @@ require "concurrent/map"
4
4
  require "openssl"
5
5
 
6
6
  module ActiveSupport
7
+ # = Key Generator
8
+ #
7
9
  # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2.
8
10
  # It can be used to derive a number of keys for various purposes from a given secret.
9
- # This lets Rails applications have a single secure secret, but avoid reusing that
11
+ # This lets \Rails applications have a single secure secret, but avoid reusing that
10
12
  # key in multiple incompatible contexts.
11
13
  class KeyGenerator
12
14
  class << self
@@ -39,8 +41,14 @@ module ActiveSupport
39
41
  def generate_key(salt, key_size = 64)
40
42
  OpenSSL::PKCS5.pbkdf2_hmac(@secret, salt, @iterations, key_size, @hash_digest_class.new)
41
43
  end
44
+
45
+ def inspect # :nodoc:
46
+ "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
47
+ end
42
48
  end
43
49
 
50
+ # = Caching Key Generator
51
+ #
44
52
  # CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid
45
53
  # re-executing the key generation process when it's called using the same +salt+ and
46
54
  # +key_size+.
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
- # LazyLoadHooks allows Rails to lazily load a lot of components and thus
4
+ # = Lazy Load Hooks
5
+ #
6
+ # LazyLoadHooks allows \Rails to lazily load a lot of components and thus
5
7
  # making the app boot faster. Because of this feature now there is no need to
6
- # require <tt>ActiveRecord::Base</tt> at boot time purely to apply
8
+ # require +ActiveRecord::Base+ at boot time purely to apply
7
9
  # configuration. Instead a hook is registered that applies configuration once
8
- # <tt>ActiveRecord::Base</tt> is loaded. Here <tt>ActiveRecord::Base</tt> is
10
+ # +ActiveRecord::Base+ is loaded. Here +ActiveRecord::Base+ is
9
11
  # used as example but this feature can be applied elsewhere too.
10
12
  #
11
13
  # Here is an example where on_load method is called to register a hook.
@@ -47,7 +49,7 @@ module ActiveSupport
47
49
  end
48
50
  end
49
51
 
50
- # Declares a block that will be executed when a Rails component is fully
52
+ # Declares a block that will be executed when a \Rails component is fully
51
53
  # loaded. If the component has already loaded, the block is executed
52
54
  # immediately.
53
55
  #
@@ -57,6 +57,7 @@ en:
57
57
  format:
58
58
  # Where is the currency sign? %u is the currency unit, %n is the number (default: $5.00)
59
59
  format: "%u%n"
60
+ negative_format: "-%u%n"
60
61
  unit: "$"
61
62
  # These six are to override number.format and are optional
62
63
  separator: "."
@@ -112,6 +113,7 @@ en:
112
113
  tb: "TB"
113
114
  pb: "PB"
114
115
  eb: "EB"
116
+ zb: "ZB"
115
117
  # Used in NumberHelper.number_to_human()
116
118
  decimal_units:
117
119
  format: "%n %u"
@@ -3,9 +3,12 @@
3
3
  require "active_support/core_ext/module/attribute_accessors"
4
4
  require "active_support/core_ext/class/attribute"
5
5
  require "active_support/subscriber"
6
+ require "active_support/deprecation/proxy_wrappers"
6
7
 
7
8
  module ActiveSupport
8
- # <tt>ActiveSupport::LogSubscriber</tt> is an object set to consume
9
+ # = Active Support Log \Subscriber
10
+ #
11
+ # +ActiveSupport::LogSubscriber+ is an object set to consume
9
12
  # ActiveSupport::Notifications with the sole purpose of logging them.
10
13
  # The log subscriber dispatches notifications to a registered object based
11
14
  # on its given namespace.
@@ -15,29 +18,23 @@ module ActiveSupport
15
18
  #
16
19
  # module ActiveRecord
17
20
  # class LogSubscriber < ActiveSupport::LogSubscriber
21
+ # attach_to :active_record
22
+ #
18
23
  # def sql(event)
19
24
  # info "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}"
20
25
  # end
21
26
  # end
22
27
  # end
23
28
  #
24
- # And it's finally registered as:
25
- #
26
- # ActiveRecord::LogSubscriber.attach_to :active_record
29
+ # ActiveRecord::LogSubscriber.logger must be set as well, but it is assigned
30
+ # automatically in a \Rails environment.
27
31
  #
28
- # Since we need to know all instance methods before attaching the log
29
- # subscriber, the line above should be called after your
30
- # <tt>ActiveRecord::LogSubscriber</tt> definition.
31
- #
32
- # A logger also needs to be set with <tt>ActiveRecord::LogSubscriber.logger=</tt>.
33
- # This is assigned automatically in a Rails environment.
34
- #
35
- # After configured, whenever a <tt>"sql.active_record"</tt> notification is published,
36
- # it will properly dispatch the event
37
- # (<tt>ActiveSupport::Notifications::Event</tt>) to the sql method.
32
+ # After configured, whenever a <tt>"sql.active_record"</tt> notification is
33
+ # published, it will properly dispatch the event
34
+ # (ActiveSupport::Notifications::Event) to the +sql+ method.
38
35
  #
39
36
  # Being an ActiveSupport::Notifications consumer,
40
- # <tt>ActiveSupport::LogSubscriber</tt> exposes a simple interface to check if
37
+ # +ActiveSupport::LogSubscriber+ exposes a simple interface to check if
41
38
  # instrumented code raises an exception. It is common to log a different
42
39
  # message in case of an error, and this can be achieved by extending
43
40
  # the previous example:
@@ -59,15 +56,24 @@ module ActiveSupport
59
56
  # end
60
57
  # end
61
58
  #
62
- # Log subscriber also has some helpers to deal with logging and automatically
63
- # flushes all logs when the request finishes
64
- # (via <tt>action_dispatch.callback</tt> notification) in a Rails environment.
59
+ # +ActiveSupport::LogSubscriber+ also has some helpers to deal with
60
+ # logging. For example, ActiveSupport::LogSubscriber.flush_all! will ensure
61
+ # that all logs are flushed, and it is called in Rails::Rack::Logger after a
62
+ # request finishes.
65
63
  class LogSubscriber < Subscriber
66
64
  # Embed in a String to clear all previous ANSI sequences.
67
- CLEAR = "\e[0m"
68
- BOLD = "\e[1m"
69
-
70
- # Colors
65
+ CLEAR = ActiveSupport::Deprecation::DeprecatedObjectProxy.new("\e[0m", "CLEAR is deprecated! Use MODES[:clear] instead.", ActiveSupport.deprecator)
66
+ BOLD = ActiveSupport::Deprecation::DeprecatedObjectProxy.new("\e[1m", "BOLD is deprecated! Use MODES[:bold] instead.", ActiveSupport.deprecator)
67
+
68
+ # ANSI sequence modes
69
+ MODES = {
70
+ clear: 0,
71
+ bold: 1,
72
+ italic: 3,
73
+ underline: 4,
74
+ }
75
+
76
+ # ANSI sequence colors
71
77
  BLACK = "\e[30m"
72
78
  RED = "\e[31m"
73
79
  GREEN = "\e[32m"
@@ -78,6 +84,7 @@ module ActiveSupport
78
84
  WHITE = "\e[37m"
79
85
 
80
86
  mattr_accessor :colorize_logging, default: true
87
+ class_attribute :log_levels, instance_accessor: false, default: {} # :nodoc:
81
88
 
82
89
  class << self
83
90
  def logger
@@ -86,6 +93,12 @@ module ActiveSupport
86
93
  end
87
94
  end
88
95
 
96
+ def attach_to(...) # :nodoc:
97
+ result = super
98
+ set_event_levels
99
+ result
100
+ end
101
+
89
102
  attr_writer :logger
90
103
 
91
104
  def log_subscribers
@@ -101,20 +114,36 @@ module ActiveSupport
101
114
  def fetch_public_methods(subscriber, inherit_all)
102
115
  subscriber.public_methods(inherit_all) - LogSubscriber.public_instance_methods(true)
103
116
  end
117
+
118
+ def set_event_levels
119
+ if subscriber
120
+ subscriber.event_levels = log_levels.transform_keys { |k| "#{k}.#{namespace}" }
121
+ end
122
+ end
123
+
124
+ def subscribe_log_level(method, level)
125
+ self.log_levels = log_levels.merge(method => ::Logger.const_get(level.upcase))
126
+ set_event_levels
127
+ end
128
+ end
129
+
130
+ def initialize
131
+ super
132
+ @event_levels = {}
104
133
  end
105
134
 
106
135
  def logger
107
136
  LogSubscriber.logger
108
137
  end
109
138
 
110
- def start(name, id, payload)
111
- super if logger
139
+ def silenced?(event)
140
+ logger.nil? || logger.level > @event_levels.fetch(event, Float::INFINITY)
112
141
  end
113
142
 
114
- def finish(name, id, payload)
143
+ def call(event)
115
144
  super if logger
116
145
  rescue => e
117
- log_exception(name, e)
146
+ log_exception(event.name, e)
118
147
  end
119
148
 
120
149
  def publish_event(event)
@@ -123,6 +152,8 @@ module ActiveSupport
123
152
  log_exception(event.name, e)
124
153
  end
125
154
 
155
+ attr_writer :event_levels # :nodoc:
156
+
126
157
  private
127
158
  %w(info debug warn error fatal unknown).each do |level|
128
159
  class_eval <<-METHOD, __FILE__, __LINE__ + 1
@@ -132,15 +163,29 @@ module ActiveSupport
132
163
  METHOD
133
164
  end
134
165
 
135
- # Set color by using a symbol or one of the defined constants. If a third
136
- # option is set to +true+, it also adds bold to the string. This is based
137
- # on the Highline implementation and will automatically append CLEAR to the
138
- # end of the returned String.
139
- def color(text, color, bold = false) # :doc:
166
+ # Set color by using a symbol or one of the defined constants. Set modes
167
+ # by specifying bold, italic, or underline options. Inspired by Highline,
168
+ # this method will automatically clear formatting at the end of the returned String.
169
+ def color(text, color, mode_options = {}) # :doc:
140
170
  return text unless colorize_logging
141
171
  color = self.class.const_get(color.upcase) if color.is_a?(Symbol)
142
- bold = bold ? BOLD : ""
143
- "#{bold}#{color}#{text}#{CLEAR}"
172
+ mode = mode_from(mode_options)
173
+ clear = "\e[#{MODES[:clear]}m"
174
+ "#{mode}#{color}#{text}#{clear}"
175
+ end
176
+
177
+ def mode_from(options)
178
+ if options.is_a?(TrueClass) || options.is_a?(FalseClass)
179
+ ActiveSupport.deprecator.warn(<<~MSG.squish)
180
+ Bolding log text with a positional boolean is deprecated and will be removed
181
+ in Rails 7.2. Use an option hash instead (eg. `color("my text", :red, bold: true)`).
182
+ MSG
183
+ options = { bold: options }
184
+ end
185
+
186
+ modes = MODES.values_at(*options.compact_blank.keys)
187
+
188
+ "\e[#{modes.join(";")}m" if modes.any?
144
189
  end
145
190
 
146
191
  def log_exception(name, e)
@@ -14,72 +14,21 @@ module ActiveSupport
14
14
  # ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT)
15
15
  # # => true
16
16
  def self.logger_outputs_to?(logger, *sources)
17
- logdev = logger.instance_variable_get(:@logdev)
18
- logger_source = logdev.dev if logdev.respond_to?(:dev)
19
- sources.any? { |source| source == logger_source }
20
- end
21
-
22
- # Broadcasts logs to multiple loggers.
23
- def self.broadcast(logger) # :nodoc:
24
- Module.new do
25
- define_method(:add) do |*args, &block|
26
- logger.add(*args, &block)
27
- super(*args, &block)
28
- end
29
-
30
- define_method(:<<) do |x|
31
- logger << x
32
- super(x)
33
- end
34
-
35
- define_method(:close) do
36
- logger.close
37
- super()
38
- end
39
-
40
- define_method(:progname=) do |name|
41
- logger.progname = name
42
- super(name)
43
- end
44
-
45
- define_method(:formatter=) do |formatter|
46
- logger.formatter = formatter
47
- super(formatter)
48
- end
49
-
50
- define_method(:level=) do |level|
51
- logger.level = level
52
- super(level)
53
- end
17
+ loggers = if logger.is_a?(BroadcastLogger)
18
+ logger.broadcasts
19
+ else
20
+ [logger]
21
+ end
54
22
 
55
- define_method(:local_level=) do |level|
56
- logger.local_level = level if logger.respond_to?(:local_level=)
57
- super(level) if respond_to?(:local_level=)
58
- end
23
+ logdevs = loggers.map { |logger| logger.instance_variable_get(:@logdev) }
24
+ logger_sources = logdevs.filter_map { |logdev| logdev.dev if logdev.respond_to?(:dev) }
59
25
 
60
- define_method(:silence) do |level = Logger::ERROR, &block|
61
- if logger.respond_to?(:silence)
62
- logger.silence(level) do
63
- if defined?(super)
64
- super(level, &block)
65
- else
66
- block.call(self)
67
- end
68
- end
69
- else
70
- if defined?(super)
71
- super(level, &block)
72
- else
73
- block.call(self)
74
- end
75
- end
76
- end
77
- end
26
+ (sources & logger_sources).any?
78
27
  end
79
28
 
80
29
  def initialize(*args, **kwargs)
81
30
  super
82
- @formatter = SimpleFormatter.new
31
+ @formatter ||= SimpleFormatter.new
83
32
  end
84
33
 
85
34
  # Simple formatter which only displays the message.
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/concern"
4
- require "active_support/core_ext/module/attribute_accessors"
5
- require "concurrent"
6
- require "fiber"
4
+ require "logger"
7
5
 
8
6
  module ActiveSupport
9
7
  module LoggerThreadSafeLevel # :nodoc:
@@ -18,7 +16,7 @@ module ActiveSupport
18
16
  end
19
17
 
20
18
  def local_level
21
- IsolatedExecutionState[:logger_thread_safe_level]
19
+ IsolatedExecutionState[local_level_key]
22
20
  end
23
21
 
24
22
  def local_level=(level)
@@ -30,7 +28,11 @@ module ActiveSupport
30
28
  else
31
29
  raise ArgumentError, "Invalid log level: #{level.inspect}"
32
30
  end
33
- IsolatedExecutionState[:logger_thread_safe_level] = level
31
+ if level.nil?
32
+ IsolatedExecutionState.delete(local_level_key)
33
+ else
34
+ IsolatedExecutionState[local_level_key] = level
35
+ end
34
36
  end
35
37
 
36
38
  def level
@@ -45,25 +47,9 @@ module ActiveSupport
45
47
  self.local_level = old_local_level
46
48
  end
47
49
 
48
- # Redefined to check severity against #level, and thus the thread-local level, rather than +@level+.
49
- # FIXME: Remove when the minimum Ruby version supports overriding Logger#level.
50
- def add(severity, message = nil, progname = nil, &block) # :nodoc:
51
- severity ||= UNKNOWN
52
- progname ||= @progname
53
-
54
- return true if @logdev.nil? || severity < level
55
-
56
- if message.nil?
57
- if block_given?
58
- message = yield
59
- else
60
- message = progname
61
- progname = @progname
62
- end
50
+ private
51
+ def local_level_key
52
+ @local_level_key ||= :"logger_thread_safe_level_#{object_id}"
63
53
  end
64
-
65
- @logdev.write \
66
- format_message(format_severity(severity), Time.now, progname, message)
67
- end
68
54
  end
69
55
  end