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.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +4 -4
- data/Gemfile.lock +1 -1
- data/Rakefile +5 -4
- data/deprecation_collector.gemspec +1 -1
- data/gemfiles/rails_6.gemfile.lock +1 -1
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_none.gemfile.lock +1 -1
- data/lib/deprecation_collector/deprecation.rb +2 -1
- data/lib/deprecation_collector/storage.rb +212 -0
- data/lib/deprecation_collector/version.rb +1 -1
- data/lib/deprecation_collector/web/application.rb +27 -19
- data/lib/deprecation_collector/web/helpers.rb +31 -16
- data/lib/deprecation_collector/web/router.rb +69 -52
- data/lib/deprecation_collector/web/utils.rb +10 -7
- data/lib/deprecation_collector/web/views/index.html.template.rb +18 -7
- data/lib/deprecation_collector/web/views/show.html.template.rb +72 -3
- data/lib/deprecation_collector/web.rb +3 -1
- data/lib/deprecation_collector.rb +85 -118
- metadata +7 -6
@@ -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 :
|
55
|
+
attr_accessor :raise_on_deprecation, :save_full_backtrace,
|
56
56
|
:exclude_realms,
|
57
|
-
:
|
58
|
-
:app_revision, :app_root,
|
57
|
+
:app_name, :app_revision, :app_root,
|
59
58
|
:print_to_stderr, :print_recurring
|
60
|
-
|
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
|
-
|
75
|
-
|
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
|
-
|
82
|
-
|
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 ||
|
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
|
-
|
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
|
-
|
175
|
+
storage.fetch_known_digests
|
162
176
|
end
|
163
177
|
|
164
178
|
def flush_redis(enable: false)
|
165
|
-
|
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
|
-
|
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
|
-
|
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
|
210
|
+
raise "need digests" unless digests.none?(&:nil?)
|
195
211
|
|
196
|
-
dump_hash = dump.map { |dep| [dep.delete(
|
212
|
+
dump_hash = dump.map { |dep| [dep.delete("digest"), dep] }.to_h
|
197
213
|
|
198
|
-
|
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
|
-
|
205
|
-
|
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
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
296
|
-
|
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
|
+
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-
|
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.
|
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: []
|