deprecation_collector 0.3.0 → 0.5.0

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