deprecation_collector 0.4.0 → 0.5.1

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.
@@ -3,8 +3,8 @@
3
3
  require_relative "deprecation_collector/version"
4
4
  require_relative "deprecation_collector/deprecation"
5
5
  require_relative "deprecation_collector/collectors"
6
+ require_relative "deprecation_collector/storage"
6
7
  require "time"
7
- require "redis"
8
8
  require "json"
9
9
  require "set"
10
10
 
@@ -52,37 +52,74 @@ class DeprecationCollector
52
52
  end
53
53
 
54
54
  # NB: count is expensive in production env (but possible if needed) - produces a lot of redis writes
55
- attr_accessor :count, :raise_on_deprecation, :save_full_backtrace,
55
+ attr_accessor :raise_on_deprecation, :save_full_backtrace,
56
56
  :exclude_realms,
57
- :write_interval, :write_interval_jitter,
58
- :app_revision, :app_root,
57
+ :app_name, :app_revision, :app_root,
59
58
  :print_to_stderr, :print_recurring
60
- attr_writer :redis, :context_saver, :fingerprinter
59
+ attr_reader :count, :write_interval, :write_interval_jitter, :key_prefix
60
+ attr_writer :context_saver, :fingerprinter
61
61
 
62
62
  def initialize(mutex: nil)
63
- # on cruby hash itself is threadsafe, but we need to prevent races
64
- @deprecations_mutex = mutex || Mutex.new
65
- @deprecations = {}
66
- @known_digests = Set.new
67
- @last_write_time = current_time
68
63
  @enabled = true
64
+ @instance_mutex = mutex
69
65
 
70
66
  load_default_config
71
67
  end
72
68
 
73
69
  def load_default_config
74
- @redis = defined?($redis) && $redis # rubocop:disable Style/GlobalVars
75
- @count = false
70
+ if (redis = defined?($redis) && $redis) # rubocop:disable Style/GlobalVars
71
+ self.redis = redis
72
+ end
76
73
  @raise_on_deprecation = false
77
74
  @exclude_realms = []
78
75
  @ignore_message_regexp = nil
79
76
  @app_root = (defined?(Rails) && Rails.root.present? && Rails.root) || Dir.pwd
80
77
  # NB: in production with hugreds of workers may easily overload redis with writes, so more delay needed:
81
- @write_interval = 900 # 15.minutes
82
- @write_interval_jitter = 60
78
+ self.count = false
79
+ self.write_interval = 900 # 15.minutes
80
+ self.write_interval_jitter = 60
81
+ self.key_prefix = "deprecations"
83
82
  @context_saver = nil
84
83
  end
85
84
 
85
+ def redis=(val)
86
+ raise ArgumentError, "redis should not be nil" unless val
87
+ self.storage = DeprecationCollector::Storage::Redis unless storage.respond_to?(:redis=)
88
+ storage.redis = val
89
+ end
90
+
91
+ def count=(val)
92
+ storage.count = val if storage.respond_to?(:count=)
93
+ @count = val
94
+ end
95
+
96
+ def write_interval=(val)
97
+ storage.write_interval = val if storage.respond_to?(:write_interval=)
98
+ @write_interval = val
99
+ end
100
+
101
+ def write_interval_jitter=(val)
102
+ storage.write_interval_jitter = val if storage.respond_to?(:write_interval_jitter=)
103
+ @write_interval_jitter = val
104
+ end
105
+
106
+ def key_prefix=(val)
107
+ storage.key_prefix = val if storage.respond_to?(:key_prefix=)
108
+ @key_prefix = val
109
+ end
110
+
111
+ def storage
112
+ @storage ||= DeprecationCollector::Storage::StdErr.new
113
+ end
114
+
115
+ def storage=(val)
116
+ return @storage = val unless val.is_a?(Class)
117
+
118
+ @storage = val.new(mutex: @instance_mutex, count: count,
119
+ write_interval: write_interval, write_interval_jitter: write_interval_jitter,
120
+ key_prefix: key_prefix)
121
+ end
122
+
86
123
  def ignored_messages=(val)
87
124
  @ignore_message_regexp = (val && Regexp.union(val)) || nil
88
125
  end
@@ -127,59 +164,38 @@ class DeprecationCollector
127
164
  @count
128
165
  end
129
166
 
130
- def redis
131
- raise "DeprecationCollector#redis is not set" unless @redis
132
-
133
- @redis
134
- end
135
-
136
167
  def write_to_redis(force: false)
137
- return unless force || (@enabled && (current_time > @last_write_time + @write_interval))
138
-
139
- deprecations_to_flush = nil
140
- @deprecations_mutex.synchronize do
141
- deprecations_to_flush = @deprecations
142
- @deprecations = {}
143
- @last_write_time = current_time
144
- # checking in this section to prevent multiple parallel check requests
145
- return (@enabled = false) unless enabled_in_redis?
146
- end
147
-
148
- write_count_to_redis(deprecations_to_flush) if count?
168
+ return unless force || @enabled
149
169
 
150
- # make as few writes as possible, other workers may already have reported our warning
151
- fetch_known_digests
152
- deprecations_to_flush.reject! { |digest, _val| @known_digests.include?(digest) }
153
- return unless deprecations_to_flush.any?
154
-
155
- @known_digests.merge(deprecations_to_flush.keys)
156
- @redis.mapped_hmset("deprecations:data", deprecations_to_flush.transform_values(&:to_json))
170
+ storage.flush(force: force)
157
171
  end
158
172
 
159
173
  # prevent fresh process from wiring frequent already known messages
160
174
  def fetch_known_digests
161
- @known_digests.merge(@redis.hkeys("deprecations:data"))
175
+ storage.fetch_known_digests
162
176
  end
163
177
 
164
178
  def flush_redis(enable: false)
165
- @redis.del("deprecations:data", "deprecations:counter", "deprecations:notes")
166
- @redis.del("deprecations:enabled") if enable
167
- @deprecations.clear
168
- @known_digests.clear
179
+ storage.clear(enable: enable)
169
180
  end
170
181
 
182
+ # deprecated, use storage.enabled?
171
183
  def enabled_in_redis?
172
- @redis.get("deprecations:enabled") != "false"
184
+ storage.enabled?
185
+ end
186
+
187
+ def enabled?
188
+ @enabled
173
189
  end
174
190
 
175
191
  def enable
192
+ storage.enable
176
193
  @enabled = true
177
- @redis.set("deprecations:enabled", "true")
178
194
  end
179
195
 
180
196
  def disable
181
197
  @enabled = false
182
- @redis.set("deprecations:enabled", "false")
198
+ storage.disable
183
199
  end
184
200
 
185
201
  def dump
@@ -191,71 +207,39 @@ class DeprecationCollector
191
207
  # TODO: some checks
192
208
 
193
209
  digests = dump.map { |dep| dep["digest"] }
194
- raise 'need digests' unless digests.none?(&:nil?)
210
+ raise "need digests" unless digests.none?(&:nil?)
195
211
 
196
- dump_hash = dump.map { |dep| [dep.delete('digest'), dep] }.to_h
212
+ dump_hash = dump.map { |dep| [dep.delete("digest"), dep] }.to_h
197
213
 
198
- @redis.mapped_hmset("deprecations:data", dump_hash.transform_values(&:to_json))
214
+ storage.import(dump_hash)
199
215
  end
200
216
 
201
217
  def read_each
202
218
  return to_enum(:read_each) unless block_given?
203
219
 
204
- cursor = 0
205
- loop do
206
- cursor, data_pairs = @redis.hscan("deprecations:data", cursor)
207
-
208
- if data_pairs.any?
209
- data_pairs.zip(
210
- @redis.hmget("deprecations:counter", data_pairs.map(&:first)),
211
- @redis.hmget("deprecations:notes", data_pairs.map(&:first))
212
- ).each do |(digest, data), count, notes|
213
- yield decode_deprecation(digest, data, count, notes)
214
- end
215
- end
216
- break if cursor == "0"
220
+ storage.read_each do |digest, data, count, notes|
221
+ yield decode_deprecation(digest, data, count, notes)
217
222
  end
218
223
  end
219
224
 
220
225
  def read_one(digest)
221
- decode_deprecation(
222
- digest,
223
- *@redis.pipelined do |pipe|
224
- pipe.hget("deprecations:data", digest)
225
- pipe.hget("deprecations:counter", digest)
226
- pipe.hget("deprecations:notes", digest)
227
- end
228
- )
226
+ decode_deprecation(*storage.read_one(digest))
229
227
  end
230
228
 
231
229
  def delete_deprecations(remove_digests)
232
- return 0 unless remove_digests.any?
233
-
234
- @redis.pipelined do |pipe|
235
- pipe.hdel("deprecations:data", *remove_digests)
236
- pipe.hdel("deprecations:notes", *remove_digests)
237
- pipe.hdel("deprecations:counter", *remove_digests) if @count
238
- end.first
239
- end
240
-
241
- def cleanup
242
- cursor = 0
243
- removed = total = 0
244
- loop do
245
- cursor, data_pairs = @redis.hscan("deprecations:data", cursor) # NB: some pages may be empty
246
- total += data_pairs.size
247
- removed += delete_deprecations(
248
- data_pairs.to_h.select { |_digest, data| !block_given? || yield(JSON.parse(data, symbolize_names: true)) }.keys
249
- )
250
- break if cursor == "0"
251
- end
252
- "#{removed} removed, #{total - removed} left"
230
+ storage.delete(remove_digests)
231
+ end
232
+
233
+ def cleanup(&block)
234
+ raise ArgumentError, "provide a block to filter deprecations" unless block
235
+
236
+ storage.cleanup(&block)
253
237
  end
254
238
 
255
239
  protected
256
240
 
257
241
  def unsent_deprecations
258
- @deprecations
242
+ storage.unsent_deprecations
259
243
  end
260
244
 
261
245
  def store_deprecation(deprecation, allow_context: true)
@@ -263,51 +247,34 @@ class DeprecationCollector
263
247
 
264
248
  deprecation.context = context_saver.call if context_saver && allow_context
265
249
  deprecation.custom_fingerprint = fingerprinter.call(deprecation) if fingerprinter && allow_context
250
+ deprecation.app_name = app_name if app_name
266
251
 
267
- fresh = !@deprecations.key?(deprecation.digest)
268
- @deprecations_mutex.synchronize do
269
- (@deprecations[deprecation.digest] ||= deprecation).touch
270
- end
271
-
272
- write_to_redis if current_time - @last_write_time > (@write_interval + rand(@write_interval_jitter))
273
- fresh
252
+ storage.store(deprecation)
274
253
  end
275
254
 
276
255
  def log_deprecation_if_needed(deprecation, fresh)
277
256
  return unless print_to_stderr && !deprecation.ignored?
278
257
  return unless fresh || print_recurring
279
258
 
259
+ log_deprecation(deprecation)
260
+ end
261
+
262
+ def log_deprecation(deprecation)
280
263
  msg = deprecation.message
281
264
  msg = "DEPRECATION: #{msg}" unless msg.start_with?("DEPRECAT")
282
265
  $stderr.puts(msg) # rubocop:disable Style/StderrPuts
283
266
  end
284
267
 
285
- def current_time
286
- return Time.zone.now if Time.respond_to?(:zone) && Time.zone
287
-
288
- Time.now
289
- end
290
-
291
268
  def decode_deprecation(digest, data, count, notes)
292
269
  return nil unless data
293
270
 
294
271
  data = JSON.parse(data, symbolize_names: true)
295
- unless data.is_a?(Hash)
296
- # this should not happen (this means broken Deprecation#to_json or some data curruption)
297
- return nil
298
- end
272
+ # this should not happen (this means broken Deprecation#to_json or some data curruption)
273
+ return nil unless data.is_a?(Hash)
299
274
 
300
275
  data[:digest] = digest
301
276
  data[:notes] = JSON.parse(notes, symbolize_names: true) if notes
302
277
  data[:count] = count.to_i if count
303
278
  data
304
279
  end
305
-
306
- def write_count_to_redis(deprecations_to_flush)
307
- @redis.pipelined do |pipe|
308
- deprecations_to_flush.each_pair do |digest, deprecation|
309
- pipe.hincrby("deprecations:counter", digest, deprecation.occurences)
310
- end
311
- end
312
- end
313
280
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deprecation_collector
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vasily Fedoseyev
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-28 00:00:00.000000000 Z
11
+ date: 2023-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -80,6 +80,7 @@ files:
80
80
  - lib/deprecation_collector.rb
81
81
  - lib/deprecation_collector/collectors.rb
82
82
  - lib/deprecation_collector/deprecation.rb
83
+ - lib/deprecation_collector/storage.rb
83
84
  - lib/deprecation_collector/version.rb
84
85
  - lib/deprecation_collector/web.rb
85
86
  - lib/deprecation_collector/web/application.rb
@@ -98,7 +99,7 @@ metadata:
98
99
  homepage_uri: https://github.com/Vasfed/deprecation_collector
99
100
  source_code_uri: https://github.com/Vasfed/deprecation_collector
100
101
  changelog_uri: https://github.com/Vasfed/deprecation_collector/blob/main/CHANGELOG.md
101
- post_install_message:
102
+ post_install_message:
102
103
  rdoc_options: []
103
104
  require_paths:
104
105
  - lib
@@ -113,8 +114,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
114
  - !ruby/object:Gem::Version
114
115
  version: '0'
115
116
  requirements: []
116
- rubygems_version: 3.1.6
117
- signing_key:
117
+ rubygems_version: 3.4.10
118
+ signing_key:
118
119
  specification_version: 4
119
120
  summary: Collector for ruby/rails deprecations and warnings, suitable for production
120
121
  test_files: []