log_bench 0.2.4 → 0.2.5
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/lib/log_bench/app/renderer/details.rb +103 -165
- data/lib/log_bench/app/renderer/request_list.rb +35 -40
- data/lib/log_bench/log/call_line_entry.rb +0 -9
- data/lib/log_bench/log/collection.rb +0 -8
- data/lib/log_bench/log/entry.rb +0 -9
- data/lib/log_bench/log/parser.rb +1 -3
- data/lib/log_bench/log/query_entry.rb +46 -32
- data/lib/log_bench/log/request.rb +27 -22
- data/lib/log_bench/version.rb +1 -1
- metadata +2 -3
- data/lib/log_bench/log/cache_entry.rb +0 -79
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 202c43226cc39fb8457f7c2bce99e17a048aa680bf9594f9186520d34f2852ea
|
4
|
+
data.tar.gz: 8fcd799173130062385dd69ca0c4145d904470af1f95644776c467a06de83438
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0b73a0d54830c2e30567e3b3eaab18d6f3086ee12e6851c1236c62a205986e4aafee729e2114baf128e8d72f94e58831a2c1fc30e24f45aa5666fbffd3407df4
|
7
|
+
data.tar.gz: 78b435a7bbd1b7d9e15ea1e462b3ce4b3267d4d00e75cd4dec7a3859ebf1e45acfd92fdca53855677d3eaaf04e691cf96ed8bd6a56a30e43ca8b28ff3a815e2b
|
@@ -123,13 +123,12 @@ module LogBench
|
|
123
123
|
|
124
124
|
def build_detail_lines(request)
|
125
125
|
lines = []
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
log = request_to_log_format(request)
|
126
|
+
# Cache window width to avoid repeated method calls
|
127
|
+
window_width = detail_win.maxx
|
128
|
+
max_width = window_width - 6 # Leave margin for borders and scrollbar
|
130
129
|
|
131
130
|
# Method - separate label and value colors
|
132
|
-
method_color = case
|
131
|
+
method_color = case request.method
|
133
132
|
when "GET" then color_pair(3) | A_BOLD
|
134
133
|
when "POST" then color_pair(4) | A_BOLD
|
135
134
|
when "PUT" then color_pair(5) | A_BOLD
|
@@ -139,58 +138,28 @@ module LogBench
|
|
139
138
|
|
140
139
|
lines << EMPTY_LINE
|
141
140
|
lines << {
|
142
|
-
text: "Method: #{
|
141
|
+
text: "Method: #{request.method}",
|
143
142
|
color: nil,
|
144
143
|
segments: [
|
145
144
|
{text: "Method: ", color: color_pair(1)},
|
146
|
-
{text:
|
145
|
+
{text: request.method, color: method_color}
|
147
146
|
]
|
148
147
|
}
|
149
148
|
|
150
149
|
# Path - allow multiple lines with proper color separation
|
151
|
-
add_path_lines(lines,
|
152
|
-
add_status_duration_lines(lines,
|
153
|
-
add_controller_lines(lines,
|
154
|
-
add_request_id_lines(lines,
|
155
|
-
add_params_lines(lines,
|
156
|
-
add_related_logs_section(lines,
|
150
|
+
add_path_lines(lines, request, max_width)
|
151
|
+
add_status_duration_lines(lines, request)
|
152
|
+
add_controller_lines(lines, request)
|
153
|
+
add_request_id_lines(lines, request)
|
154
|
+
add_params_lines(lines, request, max_width)
|
155
|
+
add_related_logs_section(lines, request)
|
157
156
|
|
158
157
|
lines
|
159
158
|
end
|
160
159
|
|
161
|
-
def
|
162
|
-
{
|
163
|
-
method: request.method,
|
164
|
-
path: request.path,
|
165
|
-
status: request.status,
|
166
|
-
duration: request.duration,
|
167
|
-
controller: request.controller,
|
168
|
-
action: request.action,
|
169
|
-
params: request.params,
|
170
|
-
request_id: request.request_id,
|
171
|
-
related_logs: build_related_logs(request)
|
172
|
-
}
|
173
|
-
end
|
174
|
-
|
175
|
-
def build_related_logs(request)
|
176
|
-
related = []
|
177
|
-
|
178
|
-
# Add all related logs from the request
|
179
|
-
request.related_logs.each do |log|
|
180
|
-
related << {
|
181
|
-
type: log.type,
|
182
|
-
content: log.content,
|
183
|
-
timing: log.timing,
|
184
|
-
timestamp: log.timestamp
|
185
|
-
}
|
186
|
-
end
|
187
|
-
|
188
|
-
related
|
189
|
-
end
|
190
|
-
|
191
|
-
def add_path_lines(lines, log, max_width)
|
160
|
+
def add_path_lines(lines, request, max_width)
|
192
161
|
path_prefix = "Path: "
|
193
|
-
remaining_path =
|
162
|
+
remaining_path = request.path
|
194
163
|
|
195
164
|
# First line starts after "Path: " (6 characters)
|
196
165
|
first_line_width = max_width - path_prefix.length
|
@@ -225,10 +194,10 @@ module LogBench
|
|
225
194
|
end
|
226
195
|
end
|
227
196
|
|
228
|
-
def add_status_duration_lines(lines,
|
229
|
-
if
|
197
|
+
def add_status_duration_lines(lines, request)
|
198
|
+
if request.status
|
230
199
|
# Add status color coding
|
231
|
-
status_color = case
|
200
|
+
status_color = case request.status
|
232
201
|
when 200..299 then color_pair(3) # Green
|
233
202
|
when 300..399 then color_pair(4) # Yellow
|
234
203
|
when 400..599 then color_pair(6) # Red
|
@@ -238,12 +207,12 @@ module LogBench
|
|
238
207
|
# Build segments for mixed coloring
|
239
208
|
segments = [
|
240
209
|
{text: "Status: ", color: color_pair(1)},
|
241
|
-
{text:
|
210
|
+
{text: request.status.to_s, color: status_color}
|
242
211
|
]
|
243
212
|
|
244
|
-
if
|
213
|
+
if request.duration
|
245
214
|
segments << {text: " | Duration: ", color: color_pair(1)}
|
246
|
-
segments << {text: "#{
|
215
|
+
segments << {text: "#{request.duration}ms", color: nil} # Default white color
|
247
216
|
end
|
248
217
|
|
249
218
|
status_text = segments.map { |s| s[:text] }.join
|
@@ -255,9 +224,9 @@ module LogBench
|
|
255
224
|
end
|
256
225
|
end
|
257
226
|
|
258
|
-
def add_controller_lines(lines,
|
259
|
-
if
|
260
|
-
controller_value = "#{
|
227
|
+
def add_controller_lines(lines, request)
|
228
|
+
if request.controller
|
229
|
+
controller_value = "#{request.controller}##{request.action}"
|
261
230
|
lines << {
|
262
231
|
text: "Controller: #{controller_value}",
|
263
232
|
color: nil,
|
@@ -269,8 +238,8 @@ module LogBench
|
|
269
238
|
end
|
270
239
|
end
|
271
240
|
|
272
|
-
def add_params_lines(lines,
|
273
|
-
return unless
|
241
|
+
def add_params_lines(lines, request, max_width)
|
242
|
+
return unless request.params
|
274
243
|
|
275
244
|
lines << EMPTY_LINE
|
276
245
|
lines << {
|
@@ -281,7 +250,7 @@ module LogBench
|
|
281
250
|
]
|
282
251
|
}
|
283
252
|
|
284
|
-
params_text = format_params(
|
253
|
+
params_text = format_params(request.params)
|
285
254
|
indent = " "
|
286
255
|
|
287
256
|
# Split the params text into lines that fit within the available width
|
@@ -343,14 +312,14 @@ module LogBench
|
|
343
312
|
end
|
344
313
|
end
|
345
314
|
|
346
|
-
def add_request_id_lines(lines,
|
347
|
-
if
|
315
|
+
def add_request_id_lines(lines, request)
|
316
|
+
if request.request_id
|
348
317
|
lines << {
|
349
|
-
text: "Request ID: #{
|
318
|
+
text: "Request ID: #{request.request_id}",
|
350
319
|
color: nil,
|
351
320
|
segments: [
|
352
321
|
{text: "Request ID: ", color: color_pair(1)},
|
353
|
-
{text:
|
322
|
+
{text: request.request_id, color: nil} # Default white color
|
354
323
|
]
|
355
324
|
}
|
356
325
|
end
|
@@ -364,19 +333,16 @@ module LogBench
|
|
364
333
|
screen.detail_win
|
365
334
|
end
|
366
335
|
|
367
|
-
def add_related_logs_section(lines,
|
336
|
+
def add_related_logs_section(lines, request)
|
368
337
|
# Related Logs (grouped by request_id) - only show non-HTTP request logs
|
369
|
-
if
|
370
|
-
related_logs =
|
371
|
-
|
372
|
-
# Sort by timestamp
|
373
|
-
related_logs.sort_by! { |l| l[:timestamp] || Time.at(0) }
|
338
|
+
if request.request_id && request.related_logs && !request.related_logs.empty?
|
339
|
+
related_logs = request.related_logs
|
374
340
|
|
375
341
|
# Apply detail filter to related logs
|
376
342
|
filtered_related_logs = filter_related_logs(related_logs)
|
377
343
|
|
378
|
-
#
|
379
|
-
query_stats =
|
344
|
+
# Use memoized query statistics from request object
|
345
|
+
query_stats = build_query_stats_from_request(request)
|
380
346
|
|
381
347
|
# Add query summary
|
382
348
|
lines << EMPTY_LINE
|
@@ -386,30 +352,30 @@ module LogBench
|
|
386
352
|
lines << {text: summary_title, color: color_pair(1) | A_BOLD}
|
387
353
|
|
388
354
|
if query_stats[:total_queries] > 0
|
389
|
-
|
355
|
+
# Build summary line with string interpolation
|
356
|
+
summary_parts = ["#{query_stats[:total_queries]} queries"]
|
357
|
+
|
390
358
|
if query_stats[:total_time] > 0
|
391
|
-
|
392
|
-
if query_stats[:cached_queries] > 0
|
393
|
-
|
394
|
-
end
|
395
|
-
summary_line += ")"
|
359
|
+
time_part = "#{query_stats[:total_time].round(1)}ms total"
|
360
|
+
time_part += ", #{query_stats[:cached_queries]} cached" if query_stats[:cached_queries] > 0
|
361
|
+
summary_parts << "(#{time_part})"
|
396
362
|
elsif query_stats[:cached_queries] > 0
|
397
|
-
|
363
|
+
summary_parts << "(#{query_stats[:cached_queries]} cached)"
|
398
364
|
end
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
breakdown_parts
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
lines << {text:
|
365
|
+
|
366
|
+
lines << {text: " #{summary_parts.join(" ")}", color: color_pair(2)}
|
367
|
+
|
368
|
+
# Breakdown by operation type - build array efficiently
|
369
|
+
breakdown_parts = [
|
370
|
+
("#{query_stats[:select]} SELECT" if query_stats[:select] > 0),
|
371
|
+
("#{query_stats[:insert]} INSERT" if query_stats[:insert] > 0),
|
372
|
+
("#{query_stats[:update]} UPDATE" if query_stats[:update] > 0),
|
373
|
+
("#{query_stats[:delete]} DELETE" if query_stats[:delete] > 0),
|
374
|
+
("#{query_stats[:transaction]} TRANSACTION" if query_stats[:transaction] > 0)
|
375
|
+
].compact
|
376
|
+
|
377
|
+
unless breakdown_parts.empty?
|
378
|
+
lines << {text: " #{breakdown_parts.join(", ")}", color: color_pair(2)}
|
413
379
|
end
|
414
380
|
end
|
415
381
|
|
@@ -434,77 +400,56 @@ module LogBench
|
|
434
400
|
|
435
401
|
# Use filtered logs for display
|
436
402
|
filtered_related_logs.each do |related|
|
437
|
-
case related
|
403
|
+
case related.type
|
438
404
|
when :sql, :cache
|
439
|
-
render_padded_text_with_spacing(related
|
405
|
+
render_padded_text_with_spacing(related.content, lines, extra_empty_lines: 0)
|
440
406
|
else
|
441
|
-
render_padded_text_with_spacing(related
|
407
|
+
render_padded_text_with_spacing(related.content, lines, extra_empty_lines: 1)
|
442
408
|
end
|
443
409
|
end
|
444
410
|
end
|
445
411
|
end
|
446
412
|
|
447
|
-
def
|
413
|
+
def build_query_stats_from_request(request)
|
414
|
+
# Use memoized methods from request object for better performance
|
448
415
|
stats = {
|
449
|
-
total_queries:
|
450
|
-
total_time:
|
416
|
+
total_queries: request.query_count,
|
417
|
+
total_time: request.total_query_time,
|
418
|
+
cached_queries: request.cached_query_count,
|
451
419
|
select: 0,
|
452
420
|
insert: 0,
|
453
421
|
update: 0,
|
454
422
|
delete: 0,
|
455
|
-
transaction: 0
|
456
|
-
cache: 0,
|
457
|
-
cached_queries: 0
|
423
|
+
transaction: 0
|
458
424
|
}
|
459
425
|
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
stats[:total_queries] += 1
|
464
|
-
|
465
|
-
# Extract timing from the content
|
466
|
-
if log[:timing]
|
467
|
-
# Parse timing like "(1.2ms)" or "1.2ms"
|
468
|
-
timing_str = log[:timing].gsub(/[()ms]/, "")
|
469
|
-
timing_value = timing_str.to_f
|
470
|
-
stats[:total_time] += timing_value
|
471
|
-
end
|
426
|
+
# Categorize by operation type for breakdown
|
427
|
+
request.related_logs.each do |log|
|
428
|
+
next unless [:sql, :cache].include?(log.type)
|
472
429
|
|
473
|
-
|
474
|
-
content = log[:content].upcase
|
475
|
-
if content.include?("CACHE")
|
476
|
-
stats[:cached_queries] += 1
|
477
|
-
# Still categorize cached queries by their operation type
|
478
|
-
if content.include?("SELECT")
|
479
|
-
stats[:select] += 1
|
480
|
-
elsif content.include?("INSERT")
|
481
|
-
stats[:insert] += 1
|
482
|
-
elsif content.include?("UPDATE")
|
483
|
-
stats[:update] += 1
|
484
|
-
elsif content.include?("DELETE")
|
485
|
-
stats[:delete] += 1
|
486
|
-
elsif content.include?("TRANSACTION") || content.include?("BEGIN") || content.include?("COMMIT") || content.include?("ROLLBACK")
|
487
|
-
stats[:transaction] += 1
|
488
|
-
end
|
489
|
-
elsif content.include?("SELECT")
|
490
|
-
stats[:select] += 1
|
491
|
-
elsif content.include?("INSERT")
|
492
|
-
stats[:insert] += 1
|
493
|
-
elsif content.include?("UPDATE")
|
494
|
-
stats[:update] += 1
|
495
|
-
elsif content.include?("DELETE")
|
496
|
-
stats[:delete] += 1
|
497
|
-
elsif content.include?("TRANSACTION") || content.include?("BEGIN") || content.include?("COMMIT") || content.include?("ROLLBACK") || content.include?("SAVEPOINT")
|
498
|
-
stats[:transaction] += 1
|
499
|
-
end
|
430
|
+
categorize_sql_operation(log, stats)
|
500
431
|
end
|
501
432
|
|
502
|
-
# Round total time to 1 decimal place
|
503
|
-
stats[:total_time] = stats[:total_time].round(1)
|
504
|
-
|
505
433
|
stats
|
506
434
|
end
|
507
435
|
|
436
|
+
def categorize_sql_operation(log, stats)
|
437
|
+
# Use unified QueryEntry for both SQL and CACHE entries
|
438
|
+
return unless log.is_a?(LogBench::Log::QueryEntry)
|
439
|
+
|
440
|
+
if log.select?
|
441
|
+
stats[:select] += 1
|
442
|
+
elsif log.insert?
|
443
|
+
stats[:insert] += 1
|
444
|
+
elsif log.update?
|
445
|
+
stats[:update] += 1
|
446
|
+
elsif log.delete?
|
447
|
+
stats[:delete] += 1
|
448
|
+
elsif log.transaction? || log.begin? || log.commit? || log.rollback? || log.savepoint?
|
449
|
+
stats[:transaction] += 1
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
508
453
|
def filter_related_logs(related_logs)
|
509
454
|
# Filter related logs (SQL, cache, etc.) in the detail pane
|
510
455
|
return related_logs unless state.detail_filter.present?
|
@@ -513,27 +458,25 @@ module LogBench
|
|
513
458
|
|
514
459
|
# First pass: find direct matches
|
515
460
|
related_logs.each_with_index do |log, index|
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
matched_indices.add(index - 1)
|
530
|
-
end
|
461
|
+
next unless log.content && state.detail_filter.matches?(log.content)
|
462
|
+
|
463
|
+
matched_indices.add(index)
|
464
|
+
|
465
|
+
# Add context lines based on log type
|
466
|
+
case log.type
|
467
|
+
when :sql_call_line
|
468
|
+
# If match is a sql_call_line, include the line below (the actual SQL query)
|
469
|
+
matched_indices.add(index + 1) if index + 1 < related_logs.size
|
470
|
+
when :sql, :cache
|
471
|
+
# If match is a sql or cache, include the line above (the call stack line)
|
472
|
+
if index > 0 && related_logs[index - 1].type == :sql_call_line
|
473
|
+
matched_indices.add(index - 1)
|
531
474
|
end
|
532
475
|
end
|
533
476
|
end
|
534
477
|
|
535
|
-
# Return logs in original order
|
536
|
-
matched_indices.
|
478
|
+
# Return logs in original order - optimize array operations
|
479
|
+
matched_indices.sort.map { |index| related_logs[index] }
|
537
480
|
end
|
538
481
|
|
539
482
|
def render_padded_text_with_spacing(text, lines, extra_empty_lines: 1)
|
@@ -561,17 +504,12 @@ module LogBench
|
|
561
504
|
end
|
562
505
|
|
563
506
|
# Add extra empty lines after all chunks
|
564
|
-
extra_empty_lines.times
|
565
|
-
lines << EMPTY_LINE
|
566
|
-
end
|
567
|
-
|
568
|
-
text_chunks.length
|
507
|
+
extra_empty_lines.times { lines << EMPTY_LINE }
|
569
508
|
end
|
570
509
|
|
571
510
|
def adjust_detail_scroll(total_lines, visible_height)
|
572
511
|
max_scroll = [total_lines - visible_height, 0].max
|
573
|
-
state.detail_scroll_offset =
|
574
|
-
state.detail_scroll_offset = [state.detail_scroll_offset, 0].max
|
512
|
+
state.detail_scroll_offset = state.detail_scroll_offset.clamp(0, max_scroll)
|
575
513
|
end
|
576
514
|
end
|
577
515
|
end
|
@@ -107,75 +107,70 @@ module LogBench
|
|
107
107
|
|
108
108
|
def draw_row(request, request_index, y_position)
|
109
109
|
log_win.setpos(y_position, 1)
|
110
|
+
is_selected = request_index == state.selected
|
110
111
|
|
111
|
-
if
|
112
|
+
if is_selected
|
112
113
|
log_win.attron(color_pair(10) | A_DIM) do
|
113
114
|
log_win.addstr(" " * (screen.panel_width - 4))
|
114
115
|
end
|
115
116
|
log_win.setpos(y_position, 1)
|
116
117
|
end
|
117
118
|
|
118
|
-
draw_method_badge(request,
|
119
|
-
draw_path_column(request,
|
120
|
-
draw_status_column(request,
|
121
|
-
draw_duration_column(request,
|
119
|
+
draw_method_badge(request, is_selected)
|
120
|
+
draw_path_column(request, is_selected)
|
121
|
+
draw_status_column(request, is_selected)
|
122
|
+
draw_duration_column(request, is_selected)
|
122
123
|
end
|
123
124
|
|
124
|
-
def draw_method_badge(request,
|
125
|
-
|
125
|
+
def draw_method_badge(request, is_selected)
|
126
|
+
method_text = " #{request.method.ljust(7)} "
|
126
127
|
|
127
|
-
if
|
128
|
-
log_win.attron(color_pair(10) | A_DIM)
|
129
|
-
log_win.addstr(" #{request.method.ljust(7)} ")
|
130
|
-
end
|
128
|
+
if is_selected
|
129
|
+
log_win.attron(color_pair(10) | A_DIM) { log_win.addstr(method_text) }
|
131
130
|
else
|
132
|
-
|
133
|
-
|
134
|
-
end
|
131
|
+
method_color = method_color_for(request.method)
|
132
|
+
log_win.attron(color_pair(method_color) | A_BOLD) { log_win.addstr(method_text) }
|
135
133
|
end
|
136
134
|
end
|
137
135
|
|
138
|
-
def draw_path_column(request,
|
136
|
+
def draw_path_column(request, is_selected)
|
139
137
|
path_width = screen.panel_width - 27
|
140
138
|
path = request.path[0, path_width] || ""
|
139
|
+
path_text = path.ljust(path_width)
|
141
140
|
|
142
|
-
if
|
143
|
-
log_win.attron(color_pair(10) | A_DIM)
|
144
|
-
log_win.addstr(path.ljust(path_width))
|
145
|
-
end
|
141
|
+
if is_selected
|
142
|
+
log_win.attron(color_pair(10) | A_DIM) { log_win.addstr(path_text) }
|
146
143
|
else
|
147
|
-
log_win.addstr(
|
144
|
+
log_win.addstr(path_text)
|
148
145
|
end
|
149
146
|
end
|
150
147
|
|
151
|
-
def draw_status_column(request,
|
148
|
+
def draw_status_column(request, is_selected)
|
149
|
+
return unless request.status
|
150
|
+
|
152
151
|
status_col_start = screen.panel_width - 14
|
152
|
+
status_text = "#{request.status.to_s.rjust(3)} "
|
153
153
|
|
154
|
-
|
154
|
+
log_win.setpos(log_win.cury, status_col_start)
|
155
|
+
if is_selected
|
156
|
+
log_win.attron(color_pair(10) | A_DIM) { log_win.addstr(status_text) }
|
157
|
+
else
|
155
158
|
status_color = status_color_for(request.status)
|
156
|
-
|
157
|
-
|
158
|
-
log_win.setpos(log_win.cury, status_col_start)
|
159
|
-
if request_index == state.selected
|
160
|
-
log_win.attron(color_pair(10) | A_DIM) { log_win.addstr(status_text + " ") }
|
161
|
-
else
|
162
|
-
log_win.attron(color_pair(status_color)) { log_win.addstr(status_text + " ") }
|
163
|
-
end
|
159
|
+
log_win.attron(color_pair(status_color)) { log_win.addstr(status_text) }
|
164
160
|
end
|
165
161
|
end
|
166
162
|
|
167
|
-
def draw_duration_column(request,
|
168
|
-
|
163
|
+
def draw_duration_column(request, is_selected)
|
164
|
+
return unless request.duration
|
169
165
|
|
170
|
-
|
171
|
-
|
166
|
+
duration_col_start = screen.panel_width - 9
|
167
|
+
duration_text = "#{request.duration.to_i}ms".ljust(6) + " "
|
172
168
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
end
|
169
|
+
log_win.setpos(log_win.cury, duration_col_start)
|
170
|
+
if is_selected
|
171
|
+
log_win.attron(color_pair(10) | A_DIM) { log_win.addstr(duration_text) }
|
172
|
+
else
|
173
|
+
log_win.attron(A_DIM) { log_win.addstr(duration_text) }
|
179
174
|
end
|
180
175
|
end
|
181
176
|
|
@@ -17,15 +17,6 @@ module LogBench
|
|
17
17
|
new(raw_line)
|
18
18
|
end
|
19
19
|
|
20
|
-
def to_h
|
21
|
-
super.merge(
|
22
|
-
content: content,
|
23
|
-
file_path: file_path,
|
24
|
-
line_number: line_number,
|
25
|
-
method_name: method_name
|
26
|
-
)
|
27
|
-
end
|
28
|
-
|
29
20
|
private
|
30
21
|
|
31
22
|
attr_accessor :file_path, :line_number, :method_name
|
@@ -27,14 +27,6 @@ module LogBench
|
|
27
27
|
entries.select { |entry| entry.is_a?(Request) }
|
28
28
|
end
|
29
29
|
|
30
|
-
def queries
|
31
|
-
entries.flat_map(&:queries)
|
32
|
-
end
|
33
|
-
|
34
|
-
def cache_operations
|
35
|
-
entries.flat_map(&:cache_operations)
|
36
|
-
end
|
37
|
-
|
38
30
|
def filter_by_method(method)
|
39
31
|
filtered_requests = requests.select { |req| req.method == method.upcase }
|
40
32
|
create_collection_from_requests(filtered_requests)
|
data/lib/log_bench/log/entry.rb
CHANGED
@@ -31,15 +31,6 @@ module LogBench
|
|
31
31
|
!http_request?
|
32
32
|
end
|
33
33
|
|
34
|
-
def to_h
|
35
|
-
{
|
36
|
-
raw: raw_line,
|
37
|
-
timestamp: timestamp,
|
38
|
-
request_id: request_id,
|
39
|
-
type: type
|
40
|
-
}
|
41
|
-
end
|
42
|
-
|
43
34
|
private
|
44
35
|
|
45
36
|
attr_writer :type, :raw_line, :timestamp, :request_id, :content, :timing
|
data/lib/log_bench/log/parser.rb
CHANGED
@@ -23,10 +23,8 @@ module LogBench
|
|
23
23
|
case entry.type
|
24
24
|
when :http_request
|
25
25
|
Request.build(entry.raw_line)
|
26
|
-
when :sql
|
26
|
+
when :sql, :cache
|
27
27
|
QueryEntry.build(entry.raw_line)
|
28
|
-
when :cache
|
29
|
-
CacheEntry.build(entry.raw_line)
|
30
28
|
when :sql_call_line
|
31
29
|
CallLineEntry.build(entry.raw_line)
|
32
30
|
else
|
@@ -14,24 +14,25 @@ module LogBench
|
|
14
14
|
SAVEPOINT = "SAVEPOINT"
|
15
15
|
SQL_OPERATIONS = [SELECT, INSERT, UPDATE, DELETE, TRANSACTION, BEGIN_TRANSACTION, COMMIT, ROLLBACK, SAVEPOINT].freeze
|
16
16
|
|
17
|
-
def initialize(raw_line)
|
18
|
-
super
|
19
|
-
self.type = :sql
|
17
|
+
def initialize(raw_line, cached: false)
|
18
|
+
super(raw_line)
|
19
|
+
self.type = cached ? :cache : :sql
|
20
|
+
@cached = cached
|
20
21
|
end
|
21
22
|
|
22
23
|
def self.build(raw_line)
|
23
24
|
return unless parseable?(raw_line)
|
24
25
|
|
25
26
|
entry = Entry.new(raw_line)
|
26
|
-
return unless entry.type
|
27
|
+
return unless [:sql, :cache].include?(entry.type)
|
27
28
|
|
28
|
-
|
29
|
+
# Create QueryEntry for both SQL and CACHE entries
|
30
|
+
cached = entry.type == :cache
|
31
|
+
new(raw_line, cached: cached)
|
29
32
|
end
|
30
33
|
|
31
34
|
def duration_ms
|
32
|
-
|
33
|
-
|
34
|
-
timing.gsub(/[()ms]/, "").to_f
|
35
|
+
@duration_ms ||= calculate_duration_ms
|
35
36
|
end
|
36
37
|
|
37
38
|
def select?
|
@@ -70,28 +71,24 @@ module LogBench
|
|
70
71
|
operation == SAVEPOINT
|
71
72
|
end
|
72
73
|
|
73
|
-
def
|
74
|
-
|
74
|
+
def cached?
|
75
|
+
@cached
|
75
76
|
end
|
76
77
|
|
77
|
-
def
|
78
|
-
|
79
|
-
content: content,
|
80
|
-
timing: timing,
|
81
|
-
operation: operation,
|
82
|
-
duration_ms: duration_ms,
|
83
|
-
has_ansi: has_ansi_codes?(content)
|
84
|
-
)
|
78
|
+
def hit?
|
79
|
+
cached? && content.include?("CACHE")
|
85
80
|
end
|
86
81
|
|
87
82
|
private
|
88
83
|
|
84
|
+
attr_accessor :operation
|
85
|
+
|
89
86
|
def extract_from_json(data)
|
90
87
|
# Call parent method which checks for request_id
|
91
88
|
return false unless super
|
92
89
|
|
93
90
|
message = data["message"] || ""
|
94
|
-
return false unless sql_message?(data)
|
91
|
+
return false unless sql_message?(data) || cache_message?(data)
|
95
92
|
|
96
93
|
self.content = message.strip
|
97
94
|
extract_timing_and_operation
|
@@ -99,31 +96,48 @@ module LogBench
|
|
99
96
|
end
|
100
97
|
|
101
98
|
def extract_timing_and_operation
|
102
|
-
|
103
|
-
self.
|
104
|
-
self.operation = extract_operation(clean_content)
|
99
|
+
self.timing = extract_timing
|
100
|
+
self.operation = extract_operation
|
105
101
|
end
|
106
102
|
|
107
|
-
def extract_timing
|
108
|
-
match =
|
103
|
+
def extract_timing
|
104
|
+
match = clean_content.match(/\(([0-9.]+ms)\)/)
|
109
105
|
match ? match[1] : nil
|
110
106
|
end
|
111
107
|
|
112
|
-
def extract_operation
|
113
|
-
SQL_OPERATIONS.find { |op|
|
108
|
+
def extract_operation
|
109
|
+
SQL_OPERATIONS.find { |op| clean_content.include?(op) }
|
114
110
|
end
|
115
111
|
|
116
|
-
def
|
117
|
-
|
112
|
+
def clean_content
|
113
|
+
@clean_content ||= content&.gsub(/\e\[[0-9;]*m/, "") || ""
|
118
114
|
end
|
119
115
|
|
120
|
-
def has_ansi_codes?
|
121
|
-
|
116
|
+
def has_ansi_codes?
|
117
|
+
@has_ansi_codes ||= content&.match?(/\e\[[0-9;]*m/) || false
|
122
118
|
end
|
123
119
|
|
124
|
-
|
120
|
+
def calculate_duration_ms
|
121
|
+
return 0.0 unless timing
|
125
122
|
|
126
|
-
|
123
|
+
timing.gsub(/[()ms]/, "").to_f
|
124
|
+
end
|
125
|
+
|
126
|
+
def clear_memoized_values
|
127
|
+
@duration_ms = nil
|
128
|
+
@clean_content = nil
|
129
|
+
@has_ansi_codes = nil
|
130
|
+
end
|
131
|
+
|
132
|
+
def content=(value)
|
133
|
+
super
|
134
|
+
clear_memoized_values
|
135
|
+
end
|
136
|
+
|
137
|
+
def timing=(value)
|
138
|
+
super
|
139
|
+
clear_memoized_values
|
140
|
+
end
|
127
141
|
end
|
128
142
|
end
|
129
143
|
end
|
@@ -3,8 +3,7 @@
|
|
3
3
|
module LogBench
|
4
4
|
module Log
|
5
5
|
class Request < Entry
|
6
|
-
attr_reader :method, :path, :status, :duration, :controller, :action, :params
|
7
|
-
attr_accessor :related_logs
|
6
|
+
attr_reader :method, :path, :status, :duration, :controller, :action, :params, :related_logs
|
8
7
|
|
9
8
|
def initialize(raw_line)
|
10
9
|
super
|
@@ -21,28 +20,34 @@ module LogBench
|
|
21
20
|
end
|
22
21
|
|
23
22
|
def add_related_log(log_entry)
|
24
|
-
|
25
|
-
|
23
|
+
if log_entry.related_log?
|
24
|
+
related_logs << log_entry
|
25
|
+
clear_memoized_values
|
26
|
+
end
|
26
27
|
end
|
27
28
|
|
28
29
|
def queries
|
29
|
-
related_logs.select { |log| log.is_a?(QueryEntry) }
|
30
|
+
@queries ||= related_logs.select { |log| log.is_a?(QueryEntry) }
|
30
31
|
end
|
31
32
|
|
32
33
|
def cache_operations
|
33
|
-
related_logs.select { |log| log.is_a?(
|
34
|
+
@cache_operations ||= related_logs.select { |log| log.is_a?(QueryEntry) && log.cached? }
|
35
|
+
end
|
36
|
+
|
37
|
+
def sql_queries
|
38
|
+
@sql_queries ||= related_logs.select { |log| log.is_a?(QueryEntry) && !log.cached? }
|
34
39
|
end
|
35
40
|
|
36
41
|
def query_count
|
37
|
-
queries.size
|
42
|
+
@query_count ||= queries.size
|
38
43
|
end
|
39
44
|
|
40
45
|
def total_query_time
|
41
|
-
queries.sum(&:duration_ms)
|
46
|
+
@total_query_time ||= queries.sum(&:duration_ms)
|
42
47
|
end
|
43
48
|
|
44
49
|
def cached_query_count
|
45
|
-
cache_operations.size
|
50
|
+
@cached_query_count ||= cache_operations.size
|
46
51
|
end
|
47
52
|
|
48
53
|
def success?
|
@@ -57,23 +62,23 @@ module LogBench
|
|
57
62
|
status && status >= 500
|
58
63
|
end
|
59
64
|
|
60
|
-
def to_h
|
61
|
-
super.merge(
|
62
|
-
method: method,
|
63
|
-
path: path,
|
64
|
-
status: status,
|
65
|
-
duration: duration,
|
66
|
-
controller: controller,
|
67
|
-
action: action,
|
68
|
-
params: params,
|
69
|
-
related_logs: related_logs.map(&:to_h)
|
70
|
-
)
|
71
|
-
end
|
72
|
-
|
73
65
|
private
|
74
66
|
|
75
67
|
attr_writer :method, :path, :status, :duration, :controller, :action, :params
|
76
68
|
|
69
|
+
def related_logs=(value)
|
70
|
+
@related_logs = value
|
71
|
+
clear_memoized_values
|
72
|
+
end
|
73
|
+
|
74
|
+
def clear_memoized_values
|
75
|
+
@queries = nil
|
76
|
+
@cache_operations = nil
|
77
|
+
@query_count = nil
|
78
|
+
@total_query_time = nil
|
79
|
+
@cached_query_count = nil
|
80
|
+
end
|
81
|
+
|
77
82
|
def extract_from_json(data)
|
78
83
|
return false unless super
|
79
84
|
|
data/lib/log_bench/version.rb
CHANGED
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.2.
|
4
|
+
version: 0.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Benjamín Silva
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-07-
|
10
|
+
date: 2025-07-10 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: zeitwerk
|
@@ -143,7 +143,6 @@ files:
|
|
143
143
|
- lib/log_bench/configuration_validator.rb
|
144
144
|
- lib/log_bench/current.rb
|
145
145
|
- lib/log_bench/json_formatter.rb
|
146
|
-
- lib/log_bench/log/cache_entry.rb
|
147
146
|
- lib/log_bench/log/call_line_entry.rb
|
148
147
|
- lib/log_bench/log/collection.rb
|
149
148
|
- lib/log_bench/log/entry.rb
|
@@ -1,79 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module LogBench
|
4
|
-
module Log
|
5
|
-
class CacheEntry < Entry
|
6
|
-
SQL_OPERATIONS = %w[SELECT INSERT UPDATE DELETE].freeze
|
7
|
-
|
8
|
-
def initialize(raw_line)
|
9
|
-
super
|
10
|
-
self.type = :cache
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.build(raw_line)
|
14
|
-
return unless parseable?(raw_line)
|
15
|
-
|
16
|
-
entry = Entry.new(raw_line)
|
17
|
-
return unless entry.type == :cache
|
18
|
-
|
19
|
-
new(raw_line)
|
20
|
-
end
|
21
|
-
|
22
|
-
def duration_ms
|
23
|
-
return 0.0 unless timing
|
24
|
-
|
25
|
-
timing.gsub(/[()ms]/, "").to_f
|
26
|
-
end
|
27
|
-
|
28
|
-
def hit?
|
29
|
-
content.include?("CACHE")
|
30
|
-
end
|
31
|
-
|
32
|
-
def miss?
|
33
|
-
!hit?
|
34
|
-
end
|
35
|
-
|
36
|
-
def to_h
|
37
|
-
super.merge(
|
38
|
-
content: content,
|
39
|
-
timing: timing,
|
40
|
-
operation: operation,
|
41
|
-
duration_ms: duration_ms,
|
42
|
-
hit: hit?
|
43
|
-
)
|
44
|
-
end
|
45
|
-
|
46
|
-
private
|
47
|
-
|
48
|
-
attr_writer :operation
|
49
|
-
|
50
|
-
def extract_from_json(data)
|
51
|
-
super
|
52
|
-
message = data["message"] || ""
|
53
|
-
return unless cache_message?(data)
|
54
|
-
|
55
|
-
self.content = message.strip
|
56
|
-
extract_timing_and_operation
|
57
|
-
end
|
58
|
-
|
59
|
-
def extract_timing_and_operation
|
60
|
-
clean_content = remove_ansi_codes(content)
|
61
|
-
self.timing = extract_timing(clean_content)
|
62
|
-
self.operation = extract_operation(clean_content)
|
63
|
-
end
|
64
|
-
|
65
|
-
def extract_timing(text)
|
66
|
-
match = text.match(/\(([0-9.]+ms)\)/)
|
67
|
-
match ? match[1] : nil
|
68
|
-
end
|
69
|
-
|
70
|
-
def extract_operation(text)
|
71
|
-
SQL_OPERATIONS.find { |op| text.include?(op) }
|
72
|
-
end
|
73
|
-
|
74
|
-
def remove_ansi_codes(text)
|
75
|
-
text.gsub(/\e\[[0-9;]*m/, "")
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|