greenhat 0.4.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.
@@ -74,7 +74,7 @@ module GreenHat
74
74
  when Hash then render_table(entry, flags)
75
75
  when Float, Integer, Array
76
76
  format_table_entry(flags, entry, key)
77
- # Ignore Special Formatting for Strings / Usually already formatted
77
+ # Ignore Special Formatting for Strings / Usually already formatted
78
78
  when String
79
79
  entry
80
80
  else
@@ -90,13 +90,12 @@ module GreenHat
90
90
  when Float, Integer || entry.numeric?
91
91
  flags.key?(:round) ? entry.to_f.round(flags.round).ai : entry.ai
92
92
 
93
- # General Inspecting
93
+ # General Inspecting
94
94
  when Hash then entry.ai(ruby19_syntax: true)
95
95
 
96
96
  # Arrays often contain Hashes. Dangerous Recursive?
97
97
  when Array
98
98
  entry.map { |x| format_table_entry(flags, x) }.join("\n")
99
-
100
99
  when Time
101
100
  entry.to_s.pastel(:bright_white)
102
101
 
@@ -120,7 +119,7 @@ module GreenHat
120
119
 
121
120
  # Print the Table in a Nice way
122
121
  def self.render_table(entry, flags)
123
- entry = entry.map { |k, v| [k, format_table_entry(flags, v, k)] }.to_h
122
+ entry = entry.to_h { |k, v| [k, format_table_entry(flags, v, k)] }
124
123
  # Pre-format Entry
125
124
 
126
125
  table_style = flags[:table_style]&.to_sym || :unicode
@@ -168,201 +167,7 @@ module GreenHat
168
167
  # Default to everything
169
168
  files = Thing.all.map(&:name) if files.empty?
170
169
 
171
- ShellHelper.filter_start(files, flags, args)
172
- end
173
-
174
- # Main Entry Point for Filtering
175
- def self.filter_start(files, flags = {}, args = {})
176
- # Convert to Things
177
- logs = ShellHelper.find_things(files, flags).select(&:processed?)
178
-
179
- # Ignore Archive/Host Dividers
180
- if flags[:combine]
181
- results = logs.reject(&:blank?).map(&:data).flatten.compact
182
- ShellHelper.filter(results, flags, args)
183
- else
184
- # Iterate and Preserve Archive/Host Index
185
- logs.each_with_object({}) do |log, obj|
186
- # Ignore Empty Results / No Thing
187
- next if log&.blank?
188
-
189
- # Include Total Count in Name
190
- results = ShellHelper.filter(log.data, flags, args)
191
- duration = calculate_duration(results)
192
-
193
- title = [
194
- log.friendly_name,
195
- " #{results.count}".pastel(:bright_black)
196
-
197
- ]
198
-
199
- # Append Duration
200
- title.push(" #{duration.pastel(:cyan, :dim)}") unless duration.blank?
201
-
202
- # Save unless empty
203
- obj[title.join] = results unless results.count.zero?
204
-
205
- obj
206
- end
207
- end
208
- end
209
-
210
- def self.calculate_duration(results)
211
- # Skip for Pluck
212
- only_with_time = results.select { |x| x.instance_of?(Hash) && x.key?(:time) }
213
-
214
- # If slice is used ignore
215
- return nil if only_with_time.empty?
216
-
217
- sorted = only_with_time.map(&:time).sort
218
- humanize_time(sorted.first, sorted.last)
219
- end
220
-
221
- # Replace TimeDifference with https://stackoverflow.com/a/4136485/1678507
222
- def self.humanize_time(time_start, time_end, increments = 2)
223
- miliseconds = (time_end - time_start) * 1000
224
-
225
- list = [[1000, :ms], [60, :s], [60, :m], [24, :h]].map do |count, name|
226
- next unless miliseconds.positive?
227
-
228
- miliseconds, n = miliseconds.divmod(count)
229
-
230
- "#{n.to_i}#{name}" unless n.to_i.zero?
231
- end
232
-
233
- list.compact.reverse[0..increments - 1].join(' ')
234
- end
235
-
236
- # Filter Logic
237
- # TODO: Simplify
238
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
239
- def self.filter(data, flags = {}, args = {})
240
- # results = data.clone.flatten.compact
241
-
242
- # Experimenting with deep clone
243
- results = Marshal.load(Marshal.dump(data))
244
- results.select! do |row|
245
- args.send(flags.logic || :all?) do |arg|
246
- filter_row_key(row, arg, flags)
247
- end
248
- end
249
-
250
- # Ensure presecense of a specific field
251
- results = filter_exists(results, flags[:exists]) if flags.key?(:exists)
252
-
253
- # Time Zone
254
- results = filter_modify_timezone(results, flags[:time_zone]) if flags.key?(:time_zone)
255
-
256
- # Time Filtering
257
- results = filter_time(results, flags) if flags.key?(:start) || flags.key?(:end)
258
-
259
- # Strip Results if Slice is defined
260
- results = filter_slice(results, flags[:slice]) if flags.key?(:slice)
261
-
262
- # Strip Results if Except is defined
263
- results = filter_except(results, flags[:except]) if flags.key?(:except)
264
-
265
- # Remove Blank from either slice or except
266
- results.reject!(&:empty?)
267
-
268
- # Sort
269
- results.sort_by! { |x| x.slice(*flags[:sort]).values } if flags.key?(:sort)
270
-
271
- # JSON Formatting
272
- results = results.map { |x| Oj.dump(x) } if flags.key?(:json)
273
-
274
- # Show Unique Only
275
- results = filter_uniq(results, flags[:uniq]) if flags.key?(:uniq)
276
-
277
- # Reverse
278
- results.reverse! if flags[:reverse]
279
-
280
- # Count occurrences / Skip Results
281
- return filter_stats(results, flags) if flags.key?(:stats)
282
-
283
- # Limit before Pluck / Flattening
284
- results = filter_limit(results, flags[:limit]) if flags.key?(:limit)
285
-
286
- # Pluck
287
- results = filter_pluck(results, flags[:pluck]) if flags.key?(:pluck)
288
-
289
- results
290
- end
291
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
292
-
293
- # Limit / Ensure Exists and Valid Number
294
- def self.filter_limit(results, limit)
295
- return results unless limit.integer? && limit.positive?
296
-
297
- results.shift limit
298
- end
299
-
300
- def self.filter_modify_timezone(results, time_zone)
301
- results.each do |x|
302
- next unless x.key? :time
303
-
304
- x[:time] = x[:time].in_time_zone time_zone
305
- end
306
-
307
- results
308
- end
309
-
310
- # Filter Start and End Times
311
- # TODO: This is a bit icky, simplify/dry
312
- def self.filter_time(results, flags)
313
- if flags.key?(:start)
314
- begin
315
- time_start = Time.parse(flags[:start])
316
-
317
- results.select! do |x|
318
- if x.time
319
- time_start < x.time
320
- else
321
- true
322
- end
323
- end
324
- rescue StandardError
325
- puts 'Unable to Process Start Time Filter'.pastel(:red)
326
- end
327
- end
328
-
329
- if flags.key?(:end)
330
- begin
331
- time_start = Time.parse(flags[:end])
332
-
333
- results.select! do |x|
334
- if x.time
335
- time_start > x.time
336
- else
337
- true
338
- end
339
- end
340
- rescue StandardError
341
- puts 'Unable to Process End Time Filter'.pastel(:red)
342
- end
343
- end
344
-
345
- results
346
- end
347
-
348
- def self.filter_except(results, except)
349
- # Avoid Empty Results
350
- if except.empty?
351
- filter_empty_arg('except')
352
- return results
353
- end
354
-
355
- results.map { |row| row.except(*except) }
356
- end
357
-
358
- def self.filter_exists(results, exists)
359
- # Avoid Empty Results
360
- if exists.empty?
361
- filter_empty_arg('exists')
362
- return results
363
- end
364
-
365
- results.select { |row| (exists - row.keys).empty? }
170
+ Query.start(files, flags, args)
366
171
  end
367
172
 
368
173
  def self.entry_truncate(entry, truncate)
@@ -376,162 +181,6 @@ module GreenHat
376
181
  "#{entry.to_s[0..truncate]} #{'...'.pastel(:bright_blue)}"
377
182
  end
378
183
 
379
- def self.filter_slice(results, slice)
380
- # Avoid Empty Results
381
- if slice.empty?
382
- filter_empty_arg('slice')
383
- return results
384
- end
385
-
386
- results.map { |row| row.slice(*slice) }
387
- end
388
-
389
- def self.filter_pluck(results, pluck)
390
- # Avoid Empty Results
391
- if pluck.empty?
392
- filter_empty_arg('pluck')
393
- return results
394
- end
395
-
396
- results.map { |x| x.slice(*pluck).values }.flatten
397
- end
398
-
399
- def self.filter_uniq(results, unique)
400
- # Avoid Empty Results
401
- if unique.empty?
402
- filter_empty_arg('uniq')
403
- return results
404
- end
405
-
406
- unique.map do |field|
407
- results.uniq { |x| x[field] }
408
- end.inject(:&)
409
- end
410
-
411
- def self.filter_stats(results, flags)
412
- stats = flags[:stats]
413
-
414
- # Avoid Empty Results
415
- if stats.empty?
416
- filter_empty_arg('stats')
417
- return results
418
- end
419
-
420
- # Loop through Stats, Separate Hash/Tables
421
- stats.map do |field|
422
- occurrences = filter_count_occurrences(results, field, flags)
423
-
424
- # Use Truncate For Long Keys
425
- occurrences.transform_keys! { |key| key.to_s[0..flags[:truncate]] } if flags[:truncate]
426
-
427
- # Total Occurences
428
- total = occurrences.values.sum
429
-
430
- # Percs
431
- occurrences.transform_values! do |count|
432
- [
433
- count,
434
- " #{percent(count, total)}%".pastel(:bright_black)
435
- ]
436
- end
437
-
438
- # Sort by total occurances / New Variable for Total
439
- output = occurrences.sort_by(&:last).to_h.transform_values!(&:join).to_a
440
-
441
- # Append Header / Total with field name
442
- output.unshift([field.to_s.pastel(:bright_black), total])
443
-
444
- # Format
445
- output.to_h
446
- end
447
- end
448
-
449
- # Percent Helper
450
- def self.percent(value, total)
451
- ((value / total.to_f) * 100).round
452
- end
453
-
454
- # Helper to Count occurrences
455
- def self.filter_count_occurrences(results, field, flags = {})
456
- results.each_with_object(Hash.new(0)) do |entry, counts|
457
- if entry.key? field
458
- # Rounding in pagination breaks stats
459
- key = if flags.key?(:round) && entry[field].numeric?
460
- entry[field].to_f.round(flags.round)
461
- else
462
- entry[field]
463
- end
464
-
465
- counts[key] += 1
466
- else
467
- counts['None'.pastel(:bright_black)] += 1
468
- end
469
-
470
- counts
471
- end
472
- end
473
-
474
- def self.filter_empty_arg(arg)
475
- puts [
476
- 'Ignoring'.pastel(:bright_yellow),
477
- "--#{arg}".pastel(:cyan),
478
- 'it requires an argument'.pastel(:red)
479
- ].join(' ')
480
- end
481
-
482
- # Break out filter row logic into separate method
483
- def self.filter_row_key(row, arg, flags)
484
- # Ignore Other Logic if Field isn't even included / Full Text Searching
485
- return false unless row.key?(arg[:field]) || arg[:field] == :text
486
-
487
- # Sensitivity Check / Check for Match / Full Text Searching
488
- search_data = arg[:field] == :text ? row : row[arg.field]
489
- match = filter_row_entry(search_data.to_s, arg, flags)
490
-
491
- # Pivot of off include vs exclude
492
- if arg.bang
493
- !match
494
- else
495
- match
496
- end
497
- end
498
-
499
- # Field Partial / Case / Exact search
500
- def self.filter_row_entry(entry, arg, flags)
501
- # Exact Matching / Unless doing full text search
502
- return entry == arg.value.to_s if flags.key?(:exact) && arg.field != :text
503
-
504
- # Cast to String/Integer Helper
505
- entry, value = filter_entry_cast(entry, arg, flags)
506
-
507
- entry.send(arg.logic, value)
508
- end
509
-
510
- # Handle casting to strings or integers
511
- def self.filter_entry_cast(entry, arg, flags)
512
- # Cast to String
513
- value = arg.value.to_s
514
-
515
- case arg.logic
516
- when :include?
517
-
518
- # Exact Case argument
519
- unless flags.key?(:case)
520
- entry.downcase!
521
- value.downcase!
522
- end
523
- when :>=, :<=
524
- entry = entry.to_i if entry.numeric?
525
- value = value.to_i if value&.numeric?
526
- end
527
-
528
- [entry, value]
529
- end
530
-
531
- def self.filter_entry_logic(entry, arg)
532
- entry.send(arg.logic, arg.value)
533
- end
534
-
535
184
  # Total Count Helper
536
185
  def self.total_count(results, flags)
537
186
  results.map do |k, v|
@@ -541,7 +190,7 @@ module GreenHat
541
190
 
542
191
  output = {}
543
192
  output[:total] = v.count
544
- output[:duration] = calculate_duration(v)
193
+ output[:duration] = Query.calculate_duration(v)
545
194
 
546
195
  # Sort / Get first and Last
547
196
  list = v.select(&:time).map(&:time)
@@ -594,8 +243,8 @@ module GreenHat
594
243
  end
595
244
 
596
245
  # Total Log List Manipulator
597
- def self.prepare_list(log_list, base_list = nil, _flags = {})
598
- base_list ||= GreenHat::ShellHelper::Log.list
246
+ def self.prepare_list(log_list, base_list = nil)
247
+ base_list ||= Thing.list
599
248
 
600
249
  # Assume all
601
250
  log_list.push '*' if log_list.empty?
@@ -606,9 +255,9 @@ module GreenHat
606
255
  log_list
607
256
  end
608
257
 
609
- # Fuzzy match for things
258
+ # Fuzzy match for things / List used for processable (Hash Entries)
610
259
  def self.thing_list
611
- @thing_list ||= Thing.all.map(&:name)
260
+ @thing_list ||= Thing.list.map(&:name)
612
261
 
613
262
  @thing_list
614
263
  end
@@ -620,7 +269,7 @@ module GreenHat
620
269
  return file if file.instance_of?(Thing)
621
270
 
622
271
  if flags.fuzzy_file_match
623
- Thing.all.select { |x| x.name.include?(file) || x.type.include?(file) }
272
+ Thing.list.select { |x| x.name.include?(file) || x.type.include?(file) }
624
273
  else
625
274
  Thing.where name: file
626
275
  end
@@ -666,5 +315,6 @@ module GreenHat
666
315
  puts
667
316
  end
668
317
  end
318
+
669
319
  # rubocop:enable Metrics/ModuleLength
670
320
  end
@@ -7,6 +7,10 @@ module GreenHat
7
7
  # rubocop:enable Lint/Debugger
8
8
  end
9
9
 
10
+ def self.settings
11
+ Settings.configure_settings
12
+ end
13
+
10
14
  def self.web
11
15
  # Load Required Files
12
16
  require 'greenhat/web'
@@ -18,6 +22,10 @@ module GreenHat
18
22
  Disk.df
19
23
  end
20
24
 
25
+ def self.quit(_raw = {})
26
+ exit 0
27
+ end
28
+
21
29
  def self.ps(raw = {})
22
30
  Process.ps raw
23
31
  end
@@ -60,7 +68,7 @@ module GreenHat
60
68
  end
61
69
 
62
70
  def self.quiet
63
- Cli.quiet!
71
+ Cli.quiet_toggle
64
72
 
65
73
  if Cli.quiet
66
74
  puts "GreenHat Quiet Logging #{'Off'.pastel(:red)}"
@@ -379,6 +379,13 @@ module GreenHat
379
379
  %r{nginx/gitlab_pages_access.log}
380
380
  ]
381
381
  },
382
+ 'nginx/gitlab_pages_error.log' => {
383
+ format: :raw,
384
+ log: true,
385
+ pattern: [
386
+ %r{nginx/gitlab_pages_error.log}
387
+ ]
388
+ },
382
389
  'nginx/gitlab_access.log' => {
383
390
  format: :nginx,
384
391
  log: true,
@@ -11,9 +11,10 @@ module GreenHat
11
11
  result = Oj.load row
12
12
 
13
13
  # Parsing Time
14
- format_json_traverse result
14
+ format_time(result)
15
15
 
16
- flatten_hash(result).sort.to_h
16
+ # flatten_hash(result).sort.to_h
17
+ result
17
18
  rescue StandardError => e
18
19
  # TODO: Background Logger?
19
20
  e.message
@@ -29,6 +30,7 @@ module GreenHat
29
30
  v.is_a?(Hash) ? a.merge(flatten_hash(v, "#{prefix}#{k}.")) : a.merge("#{prefix}#{k}".to_sym => v)
30
31
  end
31
32
  end
33
+
32
34
  # ==========================================================================
33
35
  end
34
36
  end
@@ -25,7 +25,7 @@ module GreenHat
25
25
  result
26
26
  rescue StandardError => e
27
27
  # TODO: Background logger
28
- LogBot.fatal('dmesg', "Unable to Parse, #{row}:#{e.message}")
28
+ LogBot.warn('dmesg', "Unable to Parse, #{row}:#{e.message}")
29
29
  end
30
30
 
31
31
  self.result.compact!
@@ -4,9 +4,9 @@ module GreenHat
4
4
  module Formatters
5
5
  # Formatters for single json blobs in entire file
6
6
  def format_colon_split_strip
7
- self.result = raw.map do |row|
7
+ self.result = raw_full.to_h do |row|
8
8
  row.split(':', 2).map(&:strip)
9
- end.to_h
9
+ end
10
10
  end
11
11
  end
12
12
  end
@@ -4,7 +4,7 @@ module GreenHat
4
4
  module Formatters
5
5
  # Formatters for Dmesg
6
6
  def format_dotenv
7
- self.result = Dotenv::Parser.new(raw.join("\n")).call
7
+ self.result = Dotenv::Parser.new(raw_full.join("\n")).call
8
8
  end
9
9
  end
10
10
  end
@@ -1,12 +1 @@
1
- module GreenHat
2
- # Formatter
3
- module Formatters
4
- def log_format
5
- self.result = send(SuperLog.type?(path)) if log_type
6
- end
7
1
 
8
- def log_type
9
- SuperLog.type?(path)
10
- end
11
- end
12
- end
@@ -5,7 +5,7 @@ module GreenHat
5
5
  # Get Split Memory Table
6
6
  def format_free_m
7
7
  # Headers to Readable Symbol
8
- headers = raw.first.split(' ', 6).map(&:downcase).map do |x|
8
+ headers = raw_full.first.split.map(&:downcase).map do |x|
9
9
  x.gsub(/\s+/, '_').gsub(/[^0-9A-Za-z_]/, '')
10
10
  end.map(&:to_sym)
11
11
 
@@ -15,7 +15,7 @@ module GreenHat
15
15
  final = []
16
16
 
17
17
  # Put fields into a Hash based on Location/Key
18
- raw[1..].map(&:split).each do |row|
18
+ raw_full[1..].map(&:split).each do |row|
19
19
  result = {}
20
20
  row.each_with_index do |detail, i|
21
21
  result[headers[i]] = detail.split(':').first
@@ -14,10 +14,10 @@ module GreenHat
14
14
  { message: row }
15
15
  end
16
16
 
17
- # Parsing Time
18
- format_json_traverse result
17
+ # Parsing Time / Recusrive is really slow
18
+ format_time(result)
19
19
 
20
- result.sort.to_h
20
+ result
21
21
  rescue StandardError => e
22
22
  # TODO: Background Logger?
23
23
  e.message
@@ -27,11 +27,12 @@ module GreenHat
27
27
 
28
28
  :ok
29
29
  end
30
+
30
31
  # ==========================================================================
31
32
 
32
- # Recursively Navigate
33
+ # Recursively Navigate -- Recusrive is really slow
33
34
  def format_json_traverse(result)
34
- format_json_time(result)
35
+ format_time(result)
35
36
 
36
37
  result.each do |_key, value|
37
38
  next unless value.instance_of? Hash
@@ -40,27 +41,50 @@ module GreenHat
40
41
  end
41
42
  end
42
43
 
44
+ def format_time_fields
45
+ %i[time timestamp created_at enqueued_at completed_at]
46
+ end
47
+
48
+ # Loop through potential fields and parse as needed
49
+ def format_time(result)
50
+ format_time_fields.each do |field|
51
+ result[field] = format_time_parse(result[field]) if result.key?(field)
52
+ rescue StandardError => e
53
+ LogBot.warn('JSON Format Time', e.message)
54
+ next
55
+ end
56
+ end
57
+
43
58
  # Check for Common Fields
44
- def format_json_time(result)
45
- result.time = format_time_parse(result.time) if result.key? :time
59
+ # def format_json_time(result)
60
+ # if result.key? :time
61
+ # result.time = format_time_parse(result.time)
62
+ # elsif result.key?(:timestamp)
63
+ # result.time = format_time_parse(result.timestamp)
64
+ # end
46
65
 
47
- # If timestamp exists but time doesn't, add time
48
- result.time = format_time_parse(result.timestamp) if result.key?(:timestamp) && !result.key?(:time)
66
+ # result.created_at = format_time_parse(result.created_at) if result.key? :created_at
67
+ # result.enqueued_at = format_time_parse(result.enqueued_at) if result.key? :enqueued_at
68
+ # rescue StandardError => e
69
+ # LogBot.warn("JSON Time Parse", e.message)
70
+ # true
71
+ # end
49
72
 
50
- result.created_at = format_time_parse(result.created_at) if result.key? :created_at
51
- result.enqueued_at = format_time_parse(result.enqueued_at) if result.key? :enqueued_at
52
- rescue StandardError => e
53
- LogBot.warn('JSON Time Parse', e.message)
54
- true
73
+ def format_time_parse(time)
74
+ Time.parse time
75
+ rescue StandardError
76
+ # Handle Epoch Timestamps as well as string timestamps
77
+ format_time_numeric(time)
55
78
  end
56
79
 
57
- # Handle Epoch Timestamps as well as string timestamps
58
- def format_time_parse(time)
80
+ def format_time_numeric(time)
59
81
  if time.numeric?
60
82
  Time.at time
61
83
  else
62
- Time.parse time
84
+ time
63
85
  end
86
+ rescue StandardError => e
87
+ LogBot.warn('JSON Time Parse', e.message)
64
88
  end
65
89
  # ---
66
90
  end
@@ -14,9 +14,9 @@ module GreenHat
14
14
  end
15
15
 
16
16
  # Parsing Time
17
- format_json_traverse result
17
+ format_time(result)
18
18
 
19
- result.sort.to_h
19
+ result
20
20
  rescue StandardError => e
21
21
  LogBot.warn('JSON Parse', e.message)
22
22
  next
@@ -36,6 +36,7 @@ module GreenHat
36
36
 
37
37
  result
38
38
  end
39
+
39
40
  # ==========================================================================
40
41
  end
41
42
  end