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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2129cbf1d3b38903693c9f13efc70a1d01ab73b668796461a30e46793e6922e6
4
- data.tar.gz: 8bc054a8e475485ef2023bf66e30db6353dbfe66cf403a809369723a8ff01f0b
3
+ metadata.gz: 202c43226cc39fb8457f7c2bce99e17a048aa680bf9594f9186520d34f2852ea
4
+ data.tar.gz: 8fcd799173130062385dd69ca0c4145d904470af1f95644776c467a06de83438
5
5
  SHA512:
6
- metadata.gz: a16cd16974c7c83bde07265c51ffcbf77f6c809a19cbdc0206e3613694a89d01a8abab9086ee5a2700be7ee02f2a8c51f961cc711013fa3149e05e9024daa805
7
- data.tar.gz: 210d660288a5e921035d51926588e830d8bc0ad3c890a2c68c64455f54b38a5c02b240e774ec8331b1d79712bcd595ed3ff5165d2d04a0e8379152dec5c5e2e9
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
- max_width = detail_win.maxx - 6 # Leave margin for borders and scrollbar
127
-
128
- # Convert request to log format for compatibility with original implementation
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 log[:method]
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: #{log[:method]}",
141
+ text: "Method: #{request.method}",
143
142
  color: nil,
144
143
  segments: [
145
144
  {text: "Method: ", color: color_pair(1)},
146
- {text: log[:method], color: method_color}
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, log, max_width)
152
- add_status_duration_lines(lines, log)
153
- add_controller_lines(lines, log)
154
- add_request_id_lines(lines, log)
155
- add_params_lines(lines, log, max_width)
156
- add_related_logs_section(lines, log)
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 request_to_log_format(request)
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 = log[: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, log)
229
- if log[:status]
197
+ def add_status_duration_lines(lines, request)
198
+ if request.status
230
199
  # Add status color coding
231
- status_color = case log[:status]
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: log[:status].to_s, color: status_color}
210
+ {text: request.status.to_s, color: status_color}
242
211
  ]
243
212
 
244
- if log[:duration]
213
+ if request.duration
245
214
  segments << {text: " | Duration: ", color: color_pair(1)}
246
- segments << {text: "#{log[:duration]}ms", color: nil} # Default white color
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, log)
259
- if log[:controller]
260
- controller_value = "#{log[:controller]}##{log[:action]}"
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, log, max_width)
273
- return unless log[:params]
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(log[: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, log)
347
- if log[:request_id]
315
+ def add_request_id_lines(lines, request)
316
+ if request.request_id
348
317
  lines << {
349
- text: "Request ID: #{log[: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: log[:request_id], color: nil} # Default white color
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, log)
336
+ def add_related_logs_section(lines, request)
368
337
  # Related Logs (grouped by request_id) - only show non-HTTP request logs
369
- if log[:request_id] && log[:related_logs] && !log[:related_logs].empty?
370
- related_logs = log[: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
- # Calculate query statistics (use original logs for stats)
379
- query_stats = calculate_query_stats(related_logs)
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
- summary_line = " #{query_stats[:total_queries]} queries"
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
- summary_line += " (#{query_stats[:total_time]}ms total"
392
- if query_stats[:cached_queries] > 0
393
- summary_line += ", #{query_stats[:cached_queries]} cached"
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
- summary_line += " (#{query_stats[:cached_queries]} cached)"
363
+ summary_parts << "(#{query_stats[:cached_queries]} cached)"
398
364
  end
399
- lines << {text: summary_line, color: color_pair(2)}
400
-
401
- # Breakdown by operation type
402
- breakdown_parts = []
403
- breakdown_parts << "#{query_stats[:select]} SELECT" if query_stats[:select] > 0
404
- breakdown_parts << "#{query_stats[:insert]} INSERT" if query_stats[:insert] > 0
405
- breakdown_parts << "#{query_stats[:update]} UPDATE" if query_stats[:update] > 0
406
- breakdown_parts << "#{query_stats[:delete]} DELETE" if query_stats[:delete] > 0
407
- breakdown_parts << "#{query_stats[:transaction]} TRANSACTION" if query_stats[:transaction] > 0
408
- breakdown_parts << "#{query_stats[:cache]} CACHE" if query_stats[:cache] > 0
409
-
410
- if !breakdown_parts.empty?
411
- breakdown_line = " " + breakdown_parts.join(", ")
412
- lines << {text: breakdown_line, color: color_pair(2)}
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[:type]
403
+ case related.type
438
404
  when :sql, :cache
439
- render_padded_text_with_spacing(related[:content], lines, extra_empty_lines: 0)
405
+ render_padded_text_with_spacing(related.content, lines, extra_empty_lines: 0)
440
406
  else
441
- render_padded_text_with_spacing(related[:content], lines, extra_empty_lines: 1)
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 calculate_query_stats(related_logs)
413
+ def build_query_stats_from_request(request)
414
+ # Use memoized methods from request object for better performance
448
415
  stats = {
449
- total_queries: 0,
450
- total_time: 0.0,
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
- related_logs.each do |log|
461
- next unless [:sql, :cache].include?(log[:type])
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
- # Categorize by SQL operation and check for cache
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
- if log[:content] && state.detail_filter.matches?(log[:content])
517
- matched_indices.add(index)
518
-
519
- # Add context lines based on log type
520
- case log[:type]
521
- when :sql_call_line
522
- # If match is a sql_call_line, include the line below (the actual SQL query)
523
- if index + 1 < related_logs.size
524
- matched_indices.add(index + 1)
525
- end
526
- when :sql, :cache
527
- # If match is a sql or cache, include the line above (the call stack line)
528
- if index > 0 && related_logs[index - 1][:type] == :sql_call_line
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.to_a.sort.map { |index| related_logs[index] }
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 do
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 = [state.detail_scroll_offset, max_scroll].min
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 request_index == state.selected
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, request_index)
119
- draw_path_column(request, request_index)
120
- draw_status_column(request, request_index)
121
- draw_duration_column(request, request_index)
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, request_index)
125
- method_color = method_color_for(request.method)
125
+ def draw_method_badge(request, is_selected)
126
+ method_text = " #{request.method.ljust(7)} "
126
127
 
127
- if request_index == state.selected
128
- log_win.attron(color_pair(10) | A_DIM) do
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
- log_win.attron(color_pair(method_color) | A_BOLD) do
133
- log_win.addstr(" #{request.method.ljust(7)} ")
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, request_index)
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 request_index == state.selected
143
- log_win.attron(color_pair(10) | A_DIM) do
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(path.ljust(path_width))
144
+ log_win.addstr(path_text)
148
145
  end
149
146
  end
150
147
 
151
- def draw_status_column(request, request_index)
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
- if request.status
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
- status_text = request.status.to_s.rjust(3)
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, request_index)
168
- duration_col_start = screen.panel_width - 9
163
+ def draw_duration_column(request, is_selected)
164
+ return unless request.duration
169
165
 
170
- if request.duration
171
- duration_text = "#{request.duration.to_i}ms".ljust(6)
166
+ duration_col_start = screen.panel_width - 9
167
+ duration_text = "#{request.duration.to_i}ms".ljust(6) + " "
172
168
 
173
- log_win.setpos(log_win.cury, duration_col_start)
174
- if request_index == state.selected
175
- log_win.attron(color_pair(10) | A_DIM) { log_win.addstr(duration_text + " ") }
176
- else
177
- log_win.attron(A_DIM) { log_win.addstr(duration_text + " ") }
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)
@@ -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
@@ -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 == :sql
27
+ return unless [:sql, :cache].include?(entry.type)
27
28
 
28
- new(raw_line)
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
- return 0.0 unless timing
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 slow?(threshold_ms = 100)
74
- duration_ms > threshold_ms
74
+ def cached?
75
+ @cached
75
76
  end
76
77
 
77
- def to_h
78
- super.merge(
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
- clean_content = remove_ansi_codes(content)
103
- self.timing = extract_timing(clean_content)
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(text)
108
- match = text.match(/\(([0-9.]+ms)\)/)
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(text)
113
- SQL_OPERATIONS.find { |op| text.include?(op) }
108
+ def extract_operation
109
+ SQL_OPERATIONS.find { |op| clean_content.include?(op) }
114
110
  end
115
111
 
116
- def remove_ansi_codes(text)
117
- text.gsub(/\e\[[0-9;]*m/, "")
112
+ def clean_content
113
+ @clean_content ||= content&.gsub(/\e\[[0-9;]*m/, "") || ""
118
114
  end
119
115
 
120
- def has_ansi_codes?(text)
121
- text.match?(/\e\[[0-9;]*m/)
116
+ def has_ansi_codes?
117
+ @has_ansi_codes ||= content&.match?(/\e\[[0-9;]*m/) || false
122
118
  end
123
119
 
124
- private
120
+ def calculate_duration_ms
121
+ return 0.0 unless timing
125
122
 
126
- attr_accessor :operation
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
- related_logs << log_entry if log_entry.related_log?
25
- self.related_logs = related_logs.sort_by(&:timestamp)
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?(CacheEntry) }
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LogBench
4
- VERSION = "0.2.4"
4
+ VERSION = "0.2.5"
5
5
  end
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
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-08 00:00:00.000000000 Z
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