log_bench 0.1.4 → 0.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f5ade69636de4054fff54cdb450391a0c238da4349b60be90b3d8faecd80040b
4
- data.tar.gz: 7e28cf786b5ba7a54487da0ce6581a24a713eb1449f12127dfd922a7cf294e4f
3
+ metadata.gz: 7f47f54da37cac6796e02b70422d8fc1ccede25a68cb9397474c366b7495f334
4
+ data.tar.gz: 95716c0f2be707a223760b7bfcf54a92ccb67a9854d067b575c393b44f8d2a25
5
5
  SHA512:
6
- metadata.gz: e2199487aae647d6ea13619a6cc8506e65b2e26d569581b16151cbcde0bad0a674241d463b0d04771c378716e0c55ce3547f4a8d17978e8157925e6ecb0f8850
7
- data.tar.gz: c9d41acd6b3497fcef99e326aed6655f02837c2fc6f27c9b2777aa7a76280a953202086566b29aafe5661011053ab61b05dd41d0c563b88daa399ff17a08d7ed
6
+ metadata.gz: 82ba350746d3738578a7b024bddbe335192fd93e3e190f7131a2017cdde95a300bf2866c9f856f0cbba378e7b56158303f0cb08badb1480c3a73af5718a4f347
7
+ data.tar.gz: 4c99eec64d36aa6728e41b7d19ce3a11542ba81018507072c18cf5ea10b090467f7c774e805adbcd14da8a20e2f6c4cf6cf744f7b10e9f9aa55b6727d228443c
data/README.md CHANGED
@@ -46,6 +46,11 @@ Rails.application.configure do
46
46
  # LogBench: Configure structured logging
47
47
  config.lograge.enabled = true
48
48
  config.lograge.formatter = Lograge::Formatters::Json.new
49
+ config.log_bench.show_init_message = :full # or :min or :none
50
+ config.lograge.custom_options = lambda do |event|
51
+ params = event.payload[:params]&.except("controller", "action")
52
+ { params: params } if params.present?
53
+ end
49
54
  config.logger ||= ActiveSupport::Logger.new(config.default_log_file)
50
55
  config.logger.formatter = LogBench::JsonFormatter.new
51
56
  end
@@ -17,9 +17,10 @@ module LogBench
17
17
  # UI constants
18
18
  DEFAULT_VISIBLE_HEIGHT = 20
19
19
 
20
- def initialize(state, screen)
20
+ def initialize(state, screen, renderer = nil)
21
21
  self.state = state
22
22
  self.screen = screen
23
+ self.renderer = renderer
23
24
  self.mouse_handler = MouseHandler.new(state, screen)
24
25
  end
25
26
 
@@ -27,6 +28,11 @@ module LogBench
27
28
  ch = getch
28
29
  return if ch == -1 || ch.nil?
29
30
 
31
+ # Check if update modal should handle input first
32
+ if renderer&.handle_modal_input(ch)
33
+ return
34
+ end
35
+
30
36
  if ch == KEY_MOUSE
31
37
  mouse_handler.handle_mouse_input
32
38
  elsif filter_mode_active?
@@ -38,7 +44,7 @@ module LogBench
38
44
 
39
45
  private
40
46
 
41
- attr_accessor :state, :screen, :mouse_handler
47
+ attr_accessor :state, :screen, :renderer, :mouse_handler
42
48
 
43
49
  def filter_mode_active?
44
50
  state.filter_mode || state.detail_filter_mode
@@ -11,7 +11,7 @@ module LogBench
11
11
  DEFAULT_LOG_PATHS = %w[log/development.log].freeze
12
12
 
13
13
  # Timing
14
- MAIN_LOOP_SLEEP_INTERVAL = 0.05
14
+ MAIN_LOOP_SLEEP_INTERVAL = 1.0 / 1000 # 1ms
15
15
 
16
16
  # Error messages
17
17
  LOG_FILE_NOT_FOUND = "Error: No log file found at %s!"
@@ -21,12 +21,14 @@ module LogBench
21
21
  self.log_file_path = find_log_file(log_file_path)
22
22
  self.state = State.new
23
23
  validate_log_file!
24
+ validate_configuration!
24
25
  end
25
26
 
26
27
  def run
27
28
  setup_screen
28
29
  setup_components
29
30
  load_initial_data
31
+ check_for_updates
30
32
  initial_draw
31
33
  start_monitoring
32
34
  main_loop
@@ -49,14 +51,21 @@ module LogBench
49
51
  end
50
52
  end
51
53
 
54
+ def validate_configuration!
55
+ ConfigurationValidator.validate_rails_config!
56
+ rescue ConfigurationValidator::ConfigurationError => e
57
+ puts e.message
58
+ exit 1
59
+ end
60
+
52
61
  def setup_screen
53
62
  self.screen = Screen.new
54
63
  screen.setup
55
64
  end
56
65
 
57
66
  def setup_components
58
- self.input_handler = InputHandler.new(state, screen)
59
67
  self.renderer = Renderer::Main.new(screen, state)
68
+ self.input_handler = InputHandler.new(state, screen, renderer)
60
69
  end
61
70
 
62
71
  def load_initial_data
@@ -64,6 +73,11 @@ module LogBench
64
73
  state.requests = log_file.requests
65
74
  end
66
75
 
76
+ def check_for_updates
77
+ latest_version = VersionChecker.check_for_update
78
+ state.set_update_available(latest_version) if latest_version
79
+ end
80
+
67
81
  def initial_draw
68
82
  renderer.draw
69
83
  end
@@ -40,11 +40,9 @@ module LogBench
40
40
  request_index = click_to_request_index(y)
41
41
  return unless request_index
42
42
 
43
- # Update selection
44
43
  max_index = state.filtered_requests.size - 1
45
44
  state.selected = [request_index, max_index].min
46
45
  state.auto_scroll = false
47
- state.adjust_scroll_for_selection(visible_height)
48
46
  elsif click_in_right_pane?(x, y)
49
47
  # Switch to right pane
50
48
  state.switch_to_right_pane unless state.right_pane_focused?
@@ -7,6 +7,7 @@ module LogBench
7
7
  module Renderer
8
8
  class Details
9
9
  include Curses
10
+ EMPTY_LINE = {text: "", color: nil}
10
11
 
11
12
  def initialize(screen, state, scrollbar, ansi_renderer)
12
13
  self.screen = screen
@@ -110,7 +111,7 @@ module LogBench
110
111
  else color_pair(2) | A_BOLD
111
112
  end
112
113
 
113
- lines << {text: "", color: nil} # Empty line
114
+ lines << EMPTY_LINE
114
115
  lines << {
115
116
  text: "Method: #{log[:method]}",
116
117
  color: nil,
@@ -125,6 +126,7 @@ module LogBench
125
126
  add_status_duration_lines(lines, log)
126
127
  add_controller_lines(lines, log)
127
128
  add_request_id_lines(lines, log)
129
+ add_params_lines(lines, log, max_width)
128
130
  add_related_logs_section(lines, log)
129
131
 
130
132
  lines
@@ -138,6 +140,7 @@ module LogBench
138
140
  duration: request.duration,
139
141
  controller: request.controller,
140
142
  action: request.action,
143
+ params: request.params,
141
144
  request_id: request.request_id,
142
145
  related_logs: build_related_logs(request)
143
146
  }
@@ -240,6 +243,80 @@ module LogBench
240
243
  end
241
244
  end
242
245
 
246
+ def add_params_lines(lines, log, max_width)
247
+ return unless log[:params]
248
+
249
+ lines << EMPTY_LINE
250
+ lines << {
251
+ text: "Params:",
252
+ color: nil,
253
+ segments: [
254
+ {text: "Params:", color: color_pair(1) | A_BOLD}
255
+ ]
256
+ }
257
+
258
+ params_text = format_params(log[:params])
259
+ indent = " "
260
+
261
+ # Split the params text into lines that fit within the available width
262
+ line_width = max_width - indent.length
263
+ remaining_text = params_text
264
+
265
+ while remaining_text && remaining_text.length > 0
266
+ line_chunk = remaining_text[0, line_width]
267
+ lines << {text: indent + line_chunk, color: nil}
268
+ remaining_text = remaining_text[line_width..] || ""
269
+ end
270
+ end
271
+
272
+ def format_params(params)
273
+ case params
274
+ when Hash
275
+ # Format as readable key-value pairs
276
+ if params.empty?
277
+ "{}"
278
+ else
279
+ formatted_pairs = params.map do |key, value|
280
+ formatted_value = case value
281
+ when Hash
282
+ format_nested_hash(value)
283
+ when Array
284
+ "[#{value.join(", ")}]"
285
+ else
286
+ value.to_s
287
+ end
288
+ "#{key}: #{formatted_value}"
289
+ end
290
+ "{ #{formatted_pairs.join(", ")} }"
291
+ end
292
+ when String
293
+ params
294
+ else
295
+ params.to_s
296
+ end
297
+ end
298
+
299
+ def format_nested_hash(hash, depth = 1)
300
+ return "{}" if hash.empty?
301
+
302
+ if depth > 2 # Limit nesting depth to avoid overly complex display
303
+ "{...}"
304
+ else
305
+ formatted_pairs = hash.map do |key, value|
306
+ formatted_value = case value
307
+ when Hash
308
+ format_nested_hash(value, depth + 1)
309
+ when Array
310
+ "[#{value.join(", ")}]"
311
+ else
312
+ value.to_s
313
+ end
314
+ "#{key}: #{formatted_value}"
315
+ end
316
+ "{ #{formatted_pairs.join(", ")} }"
317
+ end
318
+ end
319
+
243
320
  def add_request_id_lines(lines, log)
244
321
  if log[:request_id]
245
322
  lines << {
@@ -276,7 +353,7 @@ module LogBench
276
353
  query_stats = calculate_query_stats(related_logs)
277
354
 
278
355
  # Add query summary
279
- lines << {text: "", color: nil} # Empty line
356
+ lines << EMPTY_LINE
280
357
 
281
358
  # Show filter status in summary if filtering is active
282
359
  summary_title = "Query Summary:"
@@ -310,7 +387,7 @@ module LogBench
310
387
  end
311
388
  end
312
389
 
313
- lines << {text: "", color: nil} # Empty line
390
+ lines << EMPTY_LINE
314
391
 
315
392
  # Show filtered logs section
316
393
  if state.detail_filter.present?
@@ -459,7 +536,7 @@ module LogBench
459
536
 
460
537
  # Add extra empty lines after all chunks
461
538
  extra_empty_lines.times do
462
- lines << {text: "", color: nil}
539
+ lines << EMPTY_LINE
463
540
  end
464
541
 
465
542
  text_chunks.length
@@ -7,7 +7,7 @@ module LogBench
7
7
  include Curses
8
8
 
9
9
  # Application info
10
- APP_NAME = "LogBench"
10
+ APP_NAME = "LogBench #{LogBench::VERSION}"
11
11
  APP_SUBTITLE = "Rails Log Viewer"
12
12
  DEFAULT_LOG_FILENAME = "development.log"
13
13
 
@@ -12,18 +12,27 @@ module LogBench
12
12
  self.header = Header.new(screen, state)
13
13
  self.request_list = RequestList.new(screen, state, scrollbar)
14
14
  self.details = Details.new(screen, state, scrollbar, ansi_renderer)
15
+ self.update_modal = UpdateModal.new(screen, state)
15
16
  end
16
17
 
17
18
  def draw
18
- header.draw
19
- request_list.draw
20
- details.draw
21
- screen.refresh_all
19
+ if update_modal.should_show?
20
+ update_modal.draw
21
+ else
22
+ header.draw
23
+ request_list.draw
24
+ details.draw
25
+ screen.refresh_all
26
+ end
27
+ end
28
+
29
+ def handle_modal_input(ch)
30
+ update_modal.handle_input(ch)
22
31
  end
23
32
 
24
33
  private
25
34
 
26
- attr_accessor :screen, :state, :header, :scrollbar, :request_list, :ansi_renderer, :details
35
+ attr_accessor :screen, :state, :header, :scrollbar, :request_list, :ansi_renderer, :details, :update_modal
27
36
  end
28
37
  end
29
38
  end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LogBench
4
+ module App
5
+ module Renderer
6
+ class UpdateModal
7
+ include Curses
8
+
9
+ # Modal dimensions
10
+ MODAL_WIDTH = 40
11
+ MODAL_HEIGHT = 7
12
+ COUNTDOWN_SECONDS = 5
13
+
14
+ # Color constants
15
+ HEADER_CYAN = 1
16
+ SUCCESS_GREEN = 3
17
+ WARNING_YELLOW = 4
18
+
19
+ def initialize(screen, state)
20
+ self.screen = screen
21
+ self.state = state
22
+ self.countdown = COUNTDOWN_SECONDS
23
+ self.modal_win = nil
24
+ self.last_countdown_update = Time.now
25
+ self.dismissed = false
26
+ end
27
+
28
+ def should_show?
29
+ state.update_available? && !dismissed
30
+ end
31
+
32
+ def draw
33
+ return unless should_show?
34
+
35
+ create_modal_window
36
+ update_countdown_timer
37
+ draw_content
38
+ modal_win&.refresh
39
+ end
40
+
41
+ def handle_input(ch)
42
+ return false unless should_show?
43
+
44
+ # Any key dismisses the modal
45
+ if ch != -1
46
+ dismiss_modal
47
+ return true
48
+ end
49
+
50
+ false
51
+ end
52
+
53
+ private
54
+
55
+ attr_accessor :screen, :state, :countdown, :modal_win, :last_countdown_update, :dismissed
56
+
57
+ def create_modal_window
58
+ return if modal_win
59
+
60
+ # Calculate center position
61
+ center_y = (screen.height - MODAL_HEIGHT) / 2
62
+ center_x = (screen.width - MODAL_WIDTH) / 2
63
+
64
+ # Create modal window
65
+ self.modal_win = Window.new(MODAL_HEIGHT, MODAL_WIDTH, center_y, center_x)
66
+ end
67
+
68
+ def draw_content
69
+ return unless modal_win
70
+
71
+ modal_win.erase
72
+ modal_win.box(0, 0)
73
+
74
+ # Header
75
+ modal_win.setpos(1, 2)
76
+ modal_win.attron(color_pair(HEADER_CYAN) | A_BOLD) { modal_win.addstr("🚀 LogBench Update Available!") }
77
+
78
+ # Version info
79
+ modal_win.setpos(3, 2)
80
+ modal_win.addstr("Current: ")
81
+ modal_win.attron(color_pair(SUCCESS_GREEN)) { modal_win.addstr(LogBench::VERSION) }
82
+ modal_win.addstr(" → Latest: ")
83
+ modal_win.attron(color_pair(SUCCESS_GREEN) | A_BOLD) { modal_win.addstr(state.update_version) }
84
+
85
+ # Instructions with countdown
86
+ modal_win.setpos(5, 2)
87
+ modal_win.addstr("Press any key to continue or wait ")
88
+ modal_win.attron(color_pair(WARNING_YELLOW) | A_BOLD) { modal_win.addstr("#{countdown}s") }
89
+ end
90
+
91
+ def update_countdown_timer
92
+ now = Time.now
93
+ if now - last_countdown_update >= 1.0
94
+ self.countdown -= 1
95
+ self.last_countdown_update = now
96
+
97
+ if countdown <= 0
98
+ dismiss_modal
99
+ end
100
+ end
101
+ end
102
+
103
+ def dismiss_modal
104
+ state.dismiss_update_notification
105
+ modal_win&.close
106
+ self.modal_win = nil
107
+ clear
108
+ end
109
+
110
+ def color_pair(n)
111
+ screen.color_pair(n)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -4,7 +4,7 @@ module LogBench
4
4
  module App
5
5
  class State
6
6
  attr_reader :main_filter, :sort, :detail_filter
7
- attr_accessor :requests, :auto_scroll, :scroll_offset, :selected, :detail_scroll_offset, :text_selection_mode
7
+ attr_accessor :requests, :auto_scroll, :scroll_offset, :selected, :detail_scroll_offset, :text_selection_mode, :update_available, :update_version
8
8
 
9
9
  def initialize
10
10
  self.requests = []
@@ -18,6 +18,8 @@ module LogBench
18
18
  self.main_filter = Filter.new
19
19
  self.detail_filter = Filter.new
20
20
  self.sort = Sort.new
21
+ self.update_available = false
22
+ self.update_version = nil
21
23
  end
22
24
 
23
25
  def running?
@@ -40,6 +42,20 @@ module LogBench
40
42
  text_selection_mode
41
43
  end
42
44
 
45
+ def set_update_available(version)
46
+ self.update_available = true
47
+ self.update_version = version
48
+ end
49
+
50
+ def dismiss_update_notification
51
+ self.update_available = false
52
+ self.update_version = nil
53
+ end
54
+
55
+ def update_available?
56
+ update_available
57
+ end
58
+
43
59
  def clear_filter
44
60
  main_filter.clear
45
61
  self.selected = 0
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LogBench
4
+ # Validates that lograge is properly configured for LogBench
5
+ class ConfigurationValidator
6
+ class ConfigurationError < StandardError; end
7
+ ERROR_CONFIGS = {
8
+ enabled: {
9
+ title: "Lograge is not enabled",
10
+ description: "LogBench requires lograge to be enabled in your Rails application.",
11
+ fix: "config.lograge.enabled = true"
12
+ },
13
+ options: {
14
+ title: "Lograge custom_options missing",
15
+ description: "LogBench needs custom_options to include params fields.",
16
+ fix: <<~FIX.strip
17
+ config.lograge.custom_options = lambda do |event|
18
+ params = event.payload[:params]&.except("controller", "action")
19
+ { params: params } if params.present?
20
+ end
21
+ FIX
22
+ },
23
+ formatter: {
24
+ title: "Wrong lograge formatter",
25
+ description: "LogBench requires LogBench::JsonFormatter for proper log parsing.",
26
+ fix: "config.lograge.formatter = LogBench::JsonFormatter.new"
27
+ }
28
+ }.freeze
29
+
30
+ def self.validate_rails_config!
31
+ new.validate_rails_config!
32
+ end
33
+
34
+ def validate_rails_config!
35
+ return true unless defined?(Rails) && Rails.application
36
+
37
+ validate_lograge_enabled!
38
+ validate_custom_options!
39
+ validate_json_formatter!
40
+
41
+ true
42
+ end
43
+
44
+ private
45
+
46
+ def validate_lograge_enabled!
47
+ unless lograge_config&.enabled
48
+ raise ConfigurationError, build_error_message(:enabled)
49
+ end
50
+ end
51
+
52
+ def validate_custom_options!
53
+ unless lograge_config&.custom_options
54
+ raise ConfigurationError, build_error_message(:options)
55
+ end
56
+ end
57
+
58
+ def validate_json_formatter!
59
+ formatter = lograge_config&.formatter
60
+ unless formatter.is_a?(LogBench::JsonFormatter)
61
+ raise ConfigurationError, build_error_message(:formatter)
62
+ end
63
+ end
64
+
65
+ def lograge_config
66
+ return nil unless Rails.application.config.respond_to?(:lograge)
67
+ Rails.application.config.lograge
68
+ end
69
+
70
+ def build_error_message(error_type)
71
+ config = ERROR_CONFIGS[error_type]
72
+
73
+ <<~ERROR
74
+ ❌ #{config[:title]}
75
+
76
+ #{config[:description]}
77
+
78
+ Add this to config/environments/development.rb:
79
+ #{config[:fix]}
80
+
81
+ For complete setup: https://github.com/silva96/log_bench#configuration
82
+ ERROR
83
+ end
84
+ end
85
+ end
@@ -3,7 +3,7 @@
3
3
  module LogBench
4
4
  module Log
5
5
  class Request < Entry
6
- attr_reader :method, :path, :status, :duration, :controller, :action
6
+ attr_reader :method, :path, :status, :duration, :controller, :action, :params
7
7
  attr_accessor :related_logs
8
8
 
9
9
  def initialize(raw_line)
@@ -65,13 +65,14 @@ module LogBench
65
65
  duration: duration,
66
66
  controller: controller,
67
67
  action: action,
68
+ params: params,
68
69
  related_logs: related_logs.map(&:to_h)
69
70
  )
70
71
  end
71
72
 
72
73
  private
73
74
 
74
- attr_writer :method, :path, :status, :duration, :controller, :action
75
+ attr_writer :method, :path, :status, :duration, :controller, :action, :params
75
76
 
76
77
  def extract_from_json(data)
77
78
  return false unless super
@@ -83,8 +84,22 @@ module LogBench
83
84
  self.controller = data["controller"]
84
85
  self.action = data["action"]
85
86
  self.request_id = data["request_id"]
87
+ self.params = parse_params(data["params"])
86
88
  true
87
89
  end
90
+
91
+ def parse_params(params_data)
92
+ return nil unless params_data
93
+
94
+ case params_data
95
+ when String
96
+ JSON.parse(params_data)
97
+ when Hash
98
+ params_data
99
+ end
100
+ rescue JSON::ParserError
101
+ params_data.to_s
102
+ end
88
103
  end
89
104
  end
90
105
  end
@@ -2,8 +2,12 @@ require "rails/railtie"
2
2
 
3
3
  module LogBench
4
4
  class Railtie < Rails::Railtie
5
+ LINE = "=" * 70
6
+
5
7
  railtie_name :log_bench
6
8
 
9
+ config.log_bench = ActiveSupport::OrderedOptions.new
10
+
7
11
  # LogBench uses manual configuration (see README.md)
8
12
 
9
13
  # Provide helpful rake tasks
@@ -11,33 +15,67 @@ module LogBench
11
15
  load "tasks/log_bench.rake"
12
16
  end
13
17
 
18
+ initializer "log_bench.configure" do |app|
19
+ LogBench.setup do |config|
20
+ config.show_init_message = app.config.log_bench.show_init_message
21
+ config.show_init_message = :full if config.show_init_message.nil?
22
+ end
23
+ end
24
+
14
25
  # Show installation instructions when Rails starts in development
15
26
  initializer "log_bench.show_instructions", after: :load_config_initializers do
16
- if Rails.env.development?
17
- # Check if lograge is properly configured
18
- puts "\n" + "=" * 70
19
- puts "\n" + "=" * 70
20
- if Rails.application.config.respond_to?(:lograge) &&
21
- Rails.application.config.lograge.enabled
22
- puts "✅ LogBench is ready to use!"
23
- puts "=" * 70
24
- puts "View your logs: log_bench log/development.log"
25
- else
26
-
27
- puts "🚀 LogBench is ready to configure!"
28
- puts "=" * 70
29
- puts "To start using LogBench:"
30
- puts " 1. See README.md for configuration instructions"
31
- puts " 2. Configure lograge in config/environments/development.rb"
32
- puts " 3. Restart your Rails server"
33
- puts " 4. Make some requests to generate logs"
34
- puts " 5. View logs: log_bench log/development.log"
35
- puts
27
+ return unless Rails.env.development?
28
+
29
+ # Use configuration validator to check lograge setup
30
+ begin
31
+ ConfigurationValidator.validate_rails_config!
32
+ # Lograge is properly configured
33
+ if LogBench.configuration.show_init_message.eql? :full
34
+ print_full_init_message
35
+ print_help_instructions
36
+ puts LINE
37
+ puts LINE
38
+ elsif LogBench.configuration.show_init_message.eql? :min
39
+ print_min_init_message
36
40
  end
37
- puts "For help: log_bench --help"
38
- puts "=" * 70 + "\n"
39
- puts "=" * 70 + "\n"
41
+ rescue ConfigurationValidator::ConfigurationError => e
42
+ # Lograge needs configuration
43
+ print_configuration_instructions
44
+ puts "⚠️ Configuration issue: #{e.message}"
45
+ print_help_instructions
46
+ puts LINE
47
+ puts LINE
40
48
  end
41
49
  end
50
+
51
+ private
52
+
53
+ def print_configuration_instructions
54
+ puts "🚀 LogBench is ready to configure!"
55
+ puts LINE
56
+ puts "To start using LogBench:"
57
+ puts " 1. See README.md for configuration instructions"
58
+ puts " 2. Configure lograge in config/environments/development.rb"
59
+ puts " 3. Restart your Rails server"
60
+ puts " 4. Make some requests to generate logs"
61
+ puts " 5. View logs: log_bench log/development.log"
62
+ puts ""
63
+ end
64
+
65
+ def print_min_init_message
66
+ puts "✅ LogBench is ready to use!"
67
+ end
68
+
69
+ def print_full_init_message
70
+ puts "\n" + LINE
71
+ puts "\n" + LINE
72
+ puts "✅ LogBench is ready to use!"
73
+ puts LINE
74
+ puts "View your logs: log_bench log/development.log"
75
+ end
76
+
77
+ def print_help_instructions
78
+ puts "For help: log_bench --help"
79
+ end
42
80
  end
43
81
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LogBench
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.6"
5
5
  end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "json"
5
+ require "fileutils"
6
+
7
+ module LogBench
8
+ class VersionChecker
9
+ # Cache file location
10
+ CACHE_DIR = File.expand_path("~/.cache/log_bench")
11
+ CACHE_FILE = File.join(CACHE_DIR, "version_check.json")
12
+
13
+ # Cache duration (24 hours)
14
+ CACHE_DURATION = 24 * 60 * 60
15
+
16
+ # RubyGems API endpoint
17
+ RUBYGEMS_API_URL = "https://rubygems.org/api/v1/gems/log_bench.json"
18
+
19
+ # Timeout for HTTP requests
20
+ REQUEST_TIMEOUT = 5
21
+
22
+ def self.check_for_update
23
+ new.check_for_update
24
+ end
25
+
26
+ def check_for_update
27
+ return nil unless should_check?
28
+
29
+ latest_version = fetch_latest_version
30
+ return nil unless latest_version
31
+
32
+ update_cache(latest_version)
33
+
34
+ if newer_version_available?(latest_version)
35
+ latest_version
36
+ end
37
+ rescue
38
+ # Silently fail - don't interrupt the user experience
39
+ nil
40
+ end
41
+
42
+ private
43
+
44
+ def should_check?
45
+ return true unless File.exist?(CACHE_FILE)
46
+
47
+ cache_data = read_cache
48
+ return true unless cache_data
49
+
50
+ # Check if cache is expired
51
+ Time.now - Time.parse(cache_data["checked_at"]) > CACHE_DURATION
52
+ rescue
53
+ true
54
+ end
55
+
56
+ def fetch_latest_version
57
+ uri = URI(RUBYGEMS_API_URL)
58
+
59
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: REQUEST_TIMEOUT) do |http|
60
+ request = Net::HTTP::Get.new(uri)
61
+ response = http.request(request)
62
+
63
+ return nil unless response.code == "200"
64
+
65
+ data = JSON.parse(response.body)
66
+ data["version"]
67
+ end
68
+ rescue
69
+ nil
70
+ end
71
+
72
+ def read_cache
73
+ return nil unless File.exist?(CACHE_FILE)
74
+
75
+ JSON.parse(File.read(CACHE_FILE))
76
+ rescue
77
+ nil
78
+ end
79
+
80
+ def update_cache(latest_version)
81
+ FileUtils.mkdir_p(CACHE_DIR)
82
+
83
+ cache_data = {
84
+ "latest_version" => latest_version,
85
+ "checked_at" => Time.now.iso8601
86
+ }
87
+
88
+ File.write(CACHE_FILE, JSON.pretty_generate(cache_data))
89
+ rescue
90
+ # Ignore cache write errors
91
+ nil
92
+ end
93
+
94
+ def newer_version_available?(latest_version)
95
+ Gem::Version.new(latest_version) > Gem::Version.new(LogBench::VERSION)
96
+ rescue
97
+ false
98
+ end
99
+ end
100
+ end
data/lib/log_bench.rb CHANGED
@@ -13,12 +13,25 @@ loader.setup
13
13
  module LogBench
14
14
  class Error < StandardError; end
15
15
 
16
- # Parse log lines and return an array of entries
17
- def self.parse(lines)
18
- lines = [lines] if lines.is_a?(String)
16
+ class Configuration
17
+ attr_accessor :show_init_message
18
+ end
19
+
20
+ class << self
21
+ attr_accessor :configuration
22
+
23
+ # Parse log lines and return an array of entries
24
+ def parse(lines)
25
+ lines = [lines] if lines.is_a?(String)
26
+
27
+ collection = Log::Collection.new(lines)
28
+ collection.requests
29
+ end
19
30
 
20
- collection = Log::Collection.new(lines)
21
- collection.requests
31
+ def setup
32
+ self.configuration ||= Configuration.new
33
+ yield(configuration)
34
+ end
22
35
  end
23
36
  end
24
37
 
@@ -13,49 +13,6 @@ namespace :log_bench do
13
13
  puts "4. Restart Rails server"
14
14
  end
15
15
 
16
- desc "Check LogBench configuration"
17
- task check: :environment do
18
- puts "\n" + "=" * 60
19
- puts "🔍 LogBench Configuration Check"
20
- puts "=" * 60
21
-
22
- if Rails.application.config.respond_to?(:lograge) &&
23
- Rails.application.config.lograge.enabled
24
- puts "✅ Lograge is enabled"
25
-
26
- if Rails.application.config.lograge.formatter.is_a?(Lograge::Formatters::Json)
27
- puts "✅ JSON formatter is configured"
28
- else
29
- puts "⚠️ JSON formatter is not configured"
30
- puts " LogBench requires JSON format"
31
- puts " See README.md for configuration instructions"
32
- end
33
-
34
- # Check if log file exists and has content
35
- log_file = "log/#{Rails.env}.log"
36
- if File.exist?(log_file) && File.size(log_file) > 0
37
- puts "✅ Log file exists: #{log_file}"
38
- else
39
- puts "⚠️ Log file is empty or doesn't exist: #{log_file}"
40
- puts " Make some requests to generate logs"
41
- end
42
-
43
- puts
44
- puts "🎉 LogBench is ready to use!"
45
- puts " Command: log_bench #{log_file}"
46
- else
47
- puts "❌ Lograge is not enabled"
48
- puts
49
- puts "To fix this:"
50
- puts " 1. See README.md for configuration instructions"
51
- puts " 2. Configure lograge in config/environments/development.rb"
52
- puts " 3. Restart your Rails server"
53
- puts " 4. Make some requests"
54
- puts " 5. Run: log_bench log/development.log"
55
- end
56
- puts "=" * 60 + "\n"
57
- end
58
-
59
16
  desc "Show LogBench usage instructions"
60
17
  task :help do
61
18
  puts <<~HELP
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: log_bench
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamín Silva
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-06-07 00:00:00.000000000 Z
10
+ date: 2025-06-17 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: zeitwerk
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0.14'
54
+ - !ruby/object:Gem::Dependency
55
+ name: net-http
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.6'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.6'
54
68
  - !ruby/object:Gem::Dependency
55
69
  name: rake
56
70
  requirement: !ruby/object:Gem::Requirement
@@ -121,9 +135,11 @@ files:
121
135
  - lib/log_bench/app/renderer/main.rb
122
136
  - lib/log_bench/app/renderer/request_list.rb
123
137
  - lib/log_bench/app/renderer/scrollbar.rb
138
+ - lib/log_bench/app/renderer/update_modal.rb
124
139
  - lib/log_bench/app/screen.rb
125
140
  - lib/log_bench/app/sort.rb
126
141
  - lib/log_bench/app/state.rb
142
+ - lib/log_bench/configuration_validator.rb
127
143
  - lib/log_bench/json_formatter.rb
128
144
  - lib/log_bench/log/cache_entry.rb
129
145
  - lib/log_bench/log/call_line_entry.rb
@@ -135,6 +151,7 @@ files:
135
151
  - lib/log_bench/log/request.rb
136
152
  - lib/log_bench/railtie.rb
137
153
  - lib/log_bench/version.rb
154
+ - lib/log_bench/version_checker.rb
138
155
  - lib/tasks/log_bench.rake
139
156
  - logbench-preview.png
140
157
  homepage: https://github.com/silva96/log_bench