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 +4 -4
- data/README.md +5 -0
- data/lib/log_bench/app/input_handler.rb +8 -2
- data/lib/log_bench/app/main.rb +16 -2
- data/lib/log_bench/app/mouse_handler.rb +0 -2
- data/lib/log_bench/app/renderer/details.rb +81 -4
- data/lib/log_bench/app/renderer/header.rb +1 -1
- data/lib/log_bench/app/renderer/main.rb +14 -5
- data/lib/log_bench/app/renderer/update_modal.rb +116 -0
- data/lib/log_bench/app/state.rb +17 -1
- data/lib/log_bench/configuration_validator.rb +85 -0
- data/lib/log_bench/log/request.rb +17 -2
- data/lib/log_bench/railtie.rb +61 -23
- data/lib/log_bench/version.rb +1 -1
- data/lib/log_bench/version_checker.rb +100 -0
- data/lib/log_bench.rb +18 -5
- data/lib/tasks/log_bench.rake +0 -43
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f47f54da37cac6796e02b70422d8fc1ccede25a68cb9397474c366b7495f334
|
4
|
+
data.tar.gz: 95716c0f2be707a223760b7bfcf54a92ccb67a9854d067b575c393b44f8d2a25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/log_bench/app/main.rb
CHANGED
@@ -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
|
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 <<
|
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 <<
|
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 <<
|
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 <<
|
539
|
+
lines << EMPTY_LINE
|
463
540
|
end
|
464
541
|
|
465
542
|
text_chunks.length
|
@@ -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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
data/lib/log_bench/app/state.rb
CHANGED
@@ -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
|
data/lib/log_bench/railtie.rb
CHANGED
@@ -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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
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
|
data/lib/log_bench/version.rb
CHANGED
@@ -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
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
21
|
-
|
31
|
+
def setup
|
32
|
+
self.configuration ||= Configuration.new
|
33
|
+
yield(configuration)
|
34
|
+
end
|
22
35
|
end
|
23
36
|
end
|
24
37
|
|
data/lib/tasks/log_bench.rake
CHANGED
@@ -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
|
+
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-
|
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
|