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.
- 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: []
|