deprecation_collector 0.3.0 → 0.5.0

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.
@@ -1,13 +1,82 @@
1
1
  _buf = ''; _buf << ("<header class=\"mb-3\"><h1>Deprecation</h1><a class=\"btn btn-secondary\"".freeze);
2
2
  ;
3
3
  ;
4
- ; _slim_codeattributes1 = deprecations_path; if _slim_codeattributes1; if _slim_codeattributes1 == true; _buf << (" href=\"\"".freeze); else; _buf << (" href=\"".freeze); _buf << ((::Temple::Utils.escape_html((_slim_codeattributes1))).to_s); _buf << ("\"".freeze); end; end; _buf << (">Back</a><a class=\"btn btn-danger\" data-confirm=\"Delete?\" data-method=\"delete\"".freeze);
5
- ; _slim_codeattributes2 = deprecation_path(@deprecation[:digest]); if _slim_codeattributes2; if _slim_codeattributes2 == true; _buf << (" href=\"\"".freeze); else; _buf << (" href=\"".freeze); _buf << ((::Temple::Utils.escape_html((_slim_codeattributes2))).to_s); _buf << ("\"".freeze); end; end; _buf << (" rel=\"nofollow\" title=\"Delete\"><span class=\"glyphicon glyphicon-trash insalesicon-trash\"></span>Delete</a></header><main><div class=\"card text-dark bg-light p-3\"><pre><code>".freeze);
4
+ ; _slim_codeattributes1 = deprecations_path; if _slim_codeattributes1; if _slim_codeattributes1 == true; _buf << (" href=\"\"".freeze); else; _buf << (" href=\"".freeze); _buf << ((::Temple::Utils.escape_html((_slim_codeattributes1))).to_s); _buf << ("\"".freeze); end; end; _buf << (">Back</a> <a class=\"btn btn-danger\" data-confirm=\"Delete?\" data-method=\"delete\"".freeze);
5
+ ; _slim_codeattributes2 = deprecation_path(@deprecation[:digest]); if _slim_codeattributes2; if _slim_codeattributes2 == true; _buf << (" href=\"\"".freeze); else; _buf << (" href=\"".freeze); _buf << ((::Temple::Utils.escape_html((_slim_codeattributes2))).to_s); _buf << ("\"".freeze); end; end; _buf << (" rel=\"nofollow\" title=\"Delete\"><span class=\"glyphicon glyphicon-trash insalesicon-trash\"></span>Delete</a> </header><main><dl class=\"row\"><dt>Message</dt><dd><code>".freeze);
6
6
  ;
7
7
  ;
8
8
  ;
9
9
  ;
10
10
  ;
11
11
  ;
12
- ; _buf << ((::Temple::Utils.escape_html((@deprecation.pretty_inspect))).to_s);
13
- ; _buf << ("</code></pre></div></main>".freeze); _buf
12
+ ;
13
+ ; _buf << ((::Temple::Utils.escape_html((@deprecation[:message]))).to_s);
14
+ ; _buf << ("</code></dd><dt>First time</dt><dd>".freeze);
15
+ ;
16
+ ;
17
+ ; _buf << ((::Temple::Utils.escape_html((@deprecation[:first_timestamp]&.yield_self { |time| Time.at(time) }))).to_s);
18
+ ;
19
+ ; _buf << ("</dd>".freeze); if @deprecation[:count] > 1;
20
+ ; _buf << ("<dt>Count</dt><dd>".freeze);
21
+ ; _buf << ((::Temple::Utils.escape_html((@deprecation[:count]))).to_s);
22
+ ;
23
+ ; _buf << ("</dd>".freeze); end; _buf << ("<dt>Realm</dt><dd>".freeze);
24
+ ; _buf << ((::Temple::Utils.escape_html((@deprecation[:realm]))).to_s);
25
+ ;
26
+ ; _buf << ("</dd>".freeze); if @deprecation[:ruby_version];
27
+ ; _buf << ("<dt>Ruby</dt><dd>".freeze);
28
+ ; _buf << ((::Temple::Utils.escape_html((@deprecation[:ruby_version]))).to_s);
29
+ ; _buf << ("</dd>".freeze); end; if @deprecation[:rails_version];
30
+ ; _buf << ("<dt>Rails</dt><dd>".freeze);
31
+ ; _buf << ((::Temple::Utils.escape_html((@deprecation[:rails_version]))).to_s);
32
+ ;
33
+ ; _buf << ("</dd>".freeze); end; if @deprecation[:revision];
34
+ ; _buf << ("<dt>Revision</dt><dd>".freeze);
35
+ ; _buf << ((::Temple::Utils.escape_html((@deprecation[:revision]))).to_s);
36
+ ; _buf << ("</dd>".freeze); end; if @deprecation[:hostname];
37
+ ; _buf << ("<dt>Hostname</dt><dd>".freeze);
38
+ ; _buf << ((::Temple::Utils.escape_html((@deprecation[:hostname]))).to_s);
39
+ ;
40
+ ; _buf << ("</dd>".freeze); end; if @deprecation[:context];
41
+ ; _buf << ("<dt>Context</dt><dd><div class=\"card p-3\"><pre><code>".freeze);
42
+ ;
43
+ ;
44
+ ;
45
+ ; _buf << ((::Temple::Utils.escape_html((JSON.pretty_generate(@deprecation[:context])))).to_s);
46
+ ;
47
+ ; _buf << ("</code></pre></div></dd>".freeze); end; if @deprecation[:app_traceline];
48
+ ; _buf << ("<dt>App traceline</dt><dd>".freeze);
49
+ ;
50
+ ; location, function = @deprecation[:app_traceline].split(':in `', 2);
51
+ ; _buf << ("<code class=\"code_location\">".freeze); _buf << ((::Temple::Utils.escape_html((location))).to_s);
52
+ ; _buf << ("</code> <i>".freeze); _buf << ((::Temple::Utils.escape_html((function.delete_suffix("'")))).to_s);
53
+ ;
54
+ ; _buf << ("</i></dd>".freeze); end; if @deprecation[:gem_traceline];
55
+ ; _buf << ("<dt>Gem traceline</dt><dd>".freeze);
56
+ ;
57
+ ; location, function = @deprecation[:gem_traceline].split(':in `', 2);
58
+ ; full_gemname = location.delete_prefix('/gems/').gsub(%r{/.*}, '');
59
+ ; location_in_gem = location.delete_prefix("/gems/#{full_gemname}/");
60
+ ; _buf << ("<i>".freeze); _buf << ((::Temple::Utils.escape_html((full_gemname))).to_s);
61
+ ; _buf << ("</i> <code class=\"code_location\"".freeze); _slim_codeattributes3 = location_in_gem; if _slim_codeattributes3; if _slim_codeattributes3 == true; _buf << (" data-copy-value=\"\"".freeze); else; _buf << (" data-copy-value=\"".freeze); _buf << ((::Temple::Utils.escape_html((_slim_codeattributes3))).to_s); _buf << ("\"".freeze); end; end; _buf << (">".freeze); _buf << ((::Temple::Utils.escape_html((location_in_gem.delete_prefix('lib/')))).to_s);
62
+ ; _buf << ("</code> <i>".freeze); _buf << ((::Temple::Utils.escape_html((function.delete_suffix("'")))).to_s);
63
+ ;
64
+ ; _buf << ("</i></dd>".freeze); end; if @deprecation[:full_backtrace];
65
+ ; _buf << ("<dt>Backtrace</dt><dd>".freeze);
66
+ ;
67
+ ; @deprecation[:full_backtrace].each do |trace_line|;
68
+ ; _buf << ("<div class=\"trace-line\">".freeze);
69
+ ; location, function = trace_line.split(':in `', 2);
70
+ ; _buf << ("<code class=\"code_location\">".freeze); _buf << ((::Temple::Utils.escape_html((location))).to_s);
71
+ ; _buf << ("</code> <i>".freeze); _buf << ((::Temple::Utils.escape_html((function.delete_suffix("'")))).to_s);
72
+ ;
73
+ ; _buf << ("</i></div>".freeze); end; _buf << ("</dd>".freeze); end; fields_to_reject = %i[message first_timestamp count realm app_traceline gem_traceline full_backtrace ruby_version rails_version context revision hostname digest_base digest].to_set;
74
+ ; additional_data = @deprecation.reject { |key,_val| fields_to_reject.include?(key) };
75
+ ; if additional_data.size > 0;
76
+ ; _buf << ("<dt>Additional data <div class=\"card p-3\"><pre><code>".freeze);
77
+ ;
78
+ ;
79
+ ;
80
+ ; _buf << ((::Temple::Utils.escape_html((JSON.pretty_generate additional_data))).to_s);
81
+ ; _buf << ("</code></pre></div></dt>".freeze); end; _buf << ("</dl><a".freeze); _slim_codeattributes4 = deprecation_path(params[:id], format: 'json'); if _slim_codeattributes4; if _slim_codeattributes4 == true; _buf << (" href=\"\"".freeze); else; _buf << (" href=\"".freeze); _buf << ((::Temple::Utils.escape_html((_slim_codeattributes4))).to_s); _buf << ("\"".freeze); end; end; _buf << (">Raw json</a></main>".freeze);
82
+ ; _buf
@@ -4,10 +4,17 @@ require "erb"
4
4
  require "rack/content_length"
5
5
  require "rack/builder"
6
6
 
7
- require_relative 'web/application'
7
+ require_relative "web/application"
8
8
 
9
9
  class DeprecationCollector
10
+ # rack app with a html interface to deprecation collector with a persistent storage like redis
10
11
  class Web
12
+ attr_accessor :import_enabled
13
+
14
+ def initialize(import_enabled: nil)
15
+ @import_enabled = import_enabled
16
+ end
17
+
11
18
  def self.call(env)
12
19
  @app ||= new
13
20
  @app.call(env)
@@ -22,11 +29,13 @@ class DeprecationCollector
22
29
  end
23
30
 
24
31
  private
32
+
25
33
  def build
34
+ web = self
26
35
  ::Rack::Builder.new do
27
36
  # use Rack::Static etc goes here
28
37
 
29
- run Web::Application.new
38
+ run Web::Application.new(web)
30
39
  end
31
40
  end
32
41
  end
@@ -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,68 @@ 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
83
81
  @context_saver = nil
84
82
  end
85
83
 
84
+ def redis=(val)
85
+ raise ArgumentError, "redis should not be nil" unless val
86
+
87
+ @storage ||= DeprecationCollector::Storage::Redis.new(val, mutex: @instance_mutex, count: count, # rubocop:disable Naming/MemoizedInstanceVariableName
88
+ write_interval: write_interval,
89
+ write_interval_jitter: write_interval_jitter,
90
+ key_prefix: key_prefix)
91
+ end
92
+
93
+ def count=(val)
94
+ storage.count = val if storage.respond_to?(:count)
95
+ @count = val
96
+ end
97
+
98
+ def write_interval=(val)
99
+ storage.write_interval = val if storage.respond_to?(:write_interval)
100
+ @write_interval = val
101
+ end
102
+
103
+ def write_interval_jitter=(val)
104
+ storage.write_interval_jitter = val if storage.respond_to?(:write_interval_jitter)
105
+ @write_interval_jitter = val
106
+ end
107
+
108
+ def key_prefix=(val)
109
+ storage.key_prefix = val
110
+ @key_prefix = val
111
+ end
112
+
113
+ def storage
114
+ @storage ||= DeprecationCollector::Storage::StdErr.new
115
+ end
116
+
86
117
  def ignored_messages=(val)
87
118
  @ignore_message_regexp = (val && Regexp.union(val)) || nil
88
119
  end
@@ -127,123 +158,81 @@ class DeprecationCollector
127
158
  @count
128
159
  end
129
160
 
130
- def redis
131
- raise "DeprecationCollector#redis is not set" unless @redis
132
-
133
- @redis
134
- end
135
-
136
161
  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
162
+ return unless force || @enabled
147
163
 
148
- write_count_to_redis(deprecations_to_flush) if count?
149
-
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))
164
+ storage.flush(force: force)
157
165
  end
158
166
 
159
167
  # prevent fresh process from wiring frequent already known messages
160
168
  def fetch_known_digests
161
- @known_digests.merge(@redis.hkeys("deprecations:data"))
169
+ storage.fetch_known_digests
162
170
  end
163
171
 
164
172
  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
173
+ storage.clear(enable: enable)
169
174
  end
170
175
 
171
176
  def enabled_in_redis?
172
- @redis.get("deprecations:enabled") != "false"
177
+ storage.enabled?
178
+ end
179
+
180
+ def enabled?
181
+ @enabled
173
182
  end
174
183
 
175
184
  def enable
185
+ storage.enable
176
186
  @enabled = true
177
- @redis.set("deprecations:enabled", "true")
178
187
  end
179
188
 
180
189
  def disable
181
190
  @enabled = false
182
- @redis.set("deprecations:enabled", "false")
191
+ storage.disable
183
192
  end
184
193
 
185
194
  def dump
186
- read_each.to_a.to_json
195
+ read_each.to_a.compact.to_json
196
+ end
197
+
198
+ def import_dump(json)
199
+ dump = JSON.parse(json)
200
+ # TODO: some checks
201
+
202
+ digests = dump.map { |dep| dep["digest"] }
203
+ raise "need digests" unless digests.none?(&:nil?)
204
+
205
+ dump_hash = dump.map { |dep| [dep.delete("digest"), dep] }.to_h
206
+
207
+ storage.import(dump_hash)
187
208
  end
188
209
 
189
210
  def read_each
190
211
  return to_enum(:read_each) unless block_given?
191
212
 
192
- cursor = 0
193
- loop do
194
- cursor, data_pairs = @redis.hscan("deprecations:data", cursor)
195
-
196
- if data_pairs.any?
197
- data_pairs.zip(
198
- @redis.hmget("deprecations:counter", data_pairs.map(&:first)),
199
- @redis.hmget("deprecations:notes", data_pairs.map(&:first))
200
- ).each do |(digest, data), count, notes|
201
- yield decode_deprecation(digest, data, count, notes)
202
- end
203
- end
204
- break if cursor == "0"
213
+ storage.read_each do |digest, data, count, notes|
214
+ yield decode_deprecation(digest, data, count, notes)
205
215
  end
206
216
  end
207
217
 
208
218
  def read_one(digest)
209
- decode_deprecation(
210
- digest,
211
- *@redis.pipelined do |pipe|
212
- pipe.hget("deprecations:data", digest)
213
- pipe.hget("deprecations:counter", digest)
214
- pipe.hget("deprecations:notes", digest)
215
- end
216
- )
219
+ decode_deprecation(*storage.read_one(digest))
217
220
  end
218
221
 
219
222
  def delete_deprecations(remove_digests)
220
- return 0 unless remove_digests.any?
221
-
222
- @redis.pipelined do |pipe|
223
- pipe.hdel("deprecations:data", *remove_digests)
224
- pipe.hdel("deprecations:notes", *remove_digests)
225
- pipe.hdel("deprecations:counter", *remove_digests) if @count
226
- end.first
227
- end
228
-
229
- def cleanup
230
- cursor = 0
231
- removed = total = 0
232
- loop do
233
- cursor, data_pairs = @redis.hscan("deprecations:data", cursor) # NB: some pages may be empty
234
- total += data_pairs.size
235
- removed += delete_deprecations(
236
- data_pairs.to_h.select { |_digest, data| !block_given? || yield(JSON.parse(data, symbolize_names: true)) }.keys
237
- )
238
- break if cursor == "0"
239
- end
240
- "#{removed} removed, #{total - removed} left"
223
+ storage.delete(remove_digests)
224
+ end
225
+
226
+ def cleanup(&block)
227
+ raise ArgumentError, "provide a block to filter deprecations" unless block
228
+
229
+ storage.cleanup(&block)
241
230
  end
242
231
 
243
232
  protected
244
233
 
245
234
  def unsent_deprecations
246
- @deprecations
235
+ storage.unsent_deprecations
247
236
  end
248
237
 
249
238
  def store_deprecation(deprecation, allow_context: true)
@@ -251,51 +240,34 @@ class DeprecationCollector
251
240
 
252
241
  deprecation.context = context_saver.call if context_saver && allow_context
253
242
  deprecation.custom_fingerprint = fingerprinter.call(deprecation) if fingerprinter && allow_context
243
+ deprecation.app_name = app_name if app_name
254
244
 
255
- fresh = !@deprecations.key?(deprecation.digest)
256
- @deprecations_mutex.synchronize do
257
- (@deprecations[deprecation.digest] ||= deprecation).touch
258
- end
259
-
260
- write_to_redis if current_time - @last_write_time > (@write_interval + rand(@write_interval_jitter))
261
- fresh
245
+ storage.store(deprecation)
262
246
  end
263
247
 
264
248
  def log_deprecation_if_needed(deprecation, fresh)
265
249
  return unless print_to_stderr && !deprecation.ignored?
266
250
  return unless fresh || print_recurring
267
251
 
252
+ log_deprecation(deprecation)
253
+ end
254
+
255
+ def log_deprecation(deprecation)
268
256
  msg = deprecation.message
269
257
  msg = "DEPRECATION: #{msg}" unless msg.start_with?("DEPRECAT")
270
258
  $stderr.puts(msg) # rubocop:disable Style/StderrPuts
271
259
  end
272
260
 
273
- def current_time
274
- return Time.zone.now if Time.respond_to?(:zone) && Time.zone
275
-
276
- Time.now
277
- end
278
-
279
261
  def decode_deprecation(digest, data, count, notes)
280
262
  return nil unless data
281
263
 
282
264
  data = JSON.parse(data, symbolize_names: true)
283
- unless data.is_a?(Hash)
284
- # this should not happen (this means broken Deprecation#to_json or some data curruption)
285
- return nil
286
- end
265
+ # this should not happen (this means broken Deprecation#to_json or some data curruption)
266
+ return nil unless data.is_a?(Hash)
287
267
 
288
268
  data[:digest] = digest
289
269
  data[:notes] = JSON.parse(notes, symbolize_names: true) if notes
290
270
  data[:count] = count.to_i if count
291
271
  data
292
272
  end
293
-
294
- def write_count_to_redis(deprecations_to_flush)
295
- @redis.pipelined do |pipe|
296
- deprecations_to_flush.each_pair do |digest, deprecation|
297
- pipe.hincrby("deprecations:counter", digest, deprecation.occurences)
298
- end
299
- end
300
- end
301
273
  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.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vasily Fedoseyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-26 00:00:00.000000000 Z
11
+ date: 2023-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rack-test
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  description: Collects and aggregates warnings and deprecations. Optimized for production
42
56
  environment.
43
57
  email:
@@ -66,12 +80,14 @@ files:
66
80
  - lib/deprecation_collector.rb
67
81
  - lib/deprecation_collector/collectors.rb
68
82
  - lib/deprecation_collector/deprecation.rb
83
+ - lib/deprecation_collector/storage.rb
69
84
  - lib/deprecation_collector/version.rb
70
85
  - lib/deprecation_collector/web.rb
71
86
  - lib/deprecation_collector/web/application.rb
72
87
  - lib/deprecation_collector/web/helpers.rb
73
88
  - lib/deprecation_collector/web/router.rb
74
89
  - lib/deprecation_collector/web/utils.rb
90
+ - lib/deprecation_collector/web/views/import.html.template.rb
75
91
  - lib/deprecation_collector/web/views/index.html.template.rb
76
92
  - lib/deprecation_collector/web/views/layout.html.template.rb
77
93
  - lib/deprecation_collector/web/views/show.html.template.rb
@@ -98,7 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
114
  - !ruby/object:Gem::Version
99
115
  version: '0'
100
116
  requirements: []
101
- rubygems_version: 3.4.1
117
+ rubygems_version: 3.4.10
102
118
  signing_key:
103
119
  specification_version: 4
104
120
  summary: Collector for ruby/rails deprecations and warnings, suitable for production