deprecation_collector 0.4.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []