Skip to content

Commit 83e2aed

Browse files
committed
Add explore mode to view logs around a specific byte offset
Introduce byte-offset navigation: track byte offsets while reading logs, include them in ripgrep output and parse them in grep, and add an explore mode that displays logs around a specific offset with an input field and expand button on search results. Always allow switching to live mode visually, build the pre element client-side, and adjust tests to the new ripgrep output format.
1 parent 0c8f5eb commit 83e2aed

11 files changed

Lines changed: 312 additions & 53 deletions

File tree

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
onlylogs (0.5.3)
4+
onlylogs (0.5.1)
55
rails (~> 8.0)
66

77
GEM

app/channels/onlylogs/logs_channel.rb

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,19 @@ def initialize_watcher(data)
4242
cursor_position = data["cursor_position"] || 0
4343
filter = data["filter"].presence
4444
mode = data["mode"] || "live"
45+
search_type = data["search_type"]
4546
regexp_mode = data["regexp_mode"] == true || data["regexp_mode"] == "true"
4647
start_position = data["start_position"]&.to_i || 0
4748
end_position = data["end_position"]&.to_i
4849

49-
if mode == "search"
50-
# For search mode, read the entire file with filter and send all matching lines
50+
if mode == "search" && search_type != "byteoffset"
51+
# For filter-based search, read the entire file with filter and send all matching lines
5152
read_entire_file_with_filter(file_path, filter, regexp_mode, start_position, end_position)
53+
elsif search_type == "byteoffset"
54+
# For byteoffset search, read a fixed range
55+
start_log_watcher(file_path, cursor_position, filter, regexp_mode, end_position)
5256
else
53-
# For live mode, start the watcher
57+
# For live mode, stream indefinitely without end position
5458
start_log_watcher(file_path, cursor_position, filter, regexp_mode)
5559
end
5660
end
@@ -75,12 +79,13 @@ def cleanup_existing_operations
7579
stop_log_watcher
7680
end
7781

78-
def start_log_watcher(file_path, cursor_position, filter = nil, regexp_mode = false)
82+
def start_log_watcher(file_path, cursor_position, filter = nil, regexp_mode = false, end_position = nil)
7983
return if @log_watcher_running
8084

8185
@log_watcher_running = true
8286
@filter = filter
8387
@regexp_mode = regexp_mode
88+
@end_position = end_position
8489

8590
transmit({action: "message", content: "Reading file. Please wait..."})
8691

@@ -90,19 +95,27 @@ def start_log_watcher(file_path, cursor_position, filter = nil, regexp_mode = fa
9095

9196
@log_watcher_thread = Thread.new do
9297
Rails.logger.silence(Logger::ERROR) do
98+
current_byte_offset = cursor_position
9399
@log_file.watch do |new_lines|
94100
break unless @log_watcher_running
95101

96102
# Collect all filtered lines from this batch
97103
lines_to_send = []
98104

99105
new_lines.each do |log_line|
106+
# Stop if we've reached the end position (only when expanding context)
107+
if @end_position && @end_position > 0 && current_byte_offset >= @end_position
108+
@log_watcher_running = false
109+
break
110+
end
111+
100112
# Filters in live mode are not yet implemented
101113
# if @filter.present? && !Onlylogs::Grep.match_line?(log_line.text, @filter, regexp_mode: @regexp_mode)
102114
# next
103115
# end
104116

105-
lines_to_send << render_log_line(log_line)
117+
lines_to_send << render_log_line(log_line, byte_offset: current_byte_offset)
118+
current_byte_offset += log_line.bytesize
106119
end
107120

108121
if lines_to_send.any?
@@ -118,6 +131,8 @@ def start_log_watcher(file_path, cursor_position, filter = nil, regexp_mode = fa
118131
Rails.logger.error e.backtrace.join("\n")
119132
ensure
120133
@log_watcher_running = false
134+
# Send finish message if we had an end position (byteoffset mode)
135+
transmit({action: "finish", content: "Context loaded."}) if @end_position.present?
121136
end
122137
end
123138

@@ -156,11 +171,15 @@ def read_entire_file_with_filter(file_path, filter = nil, regexp_mode = false, s
156171

157172
begin
158173
Rails.logger.silence(Logger::ERROR) do
159-
@log_file.grep(filter, regexp_mode: regexp_mode, start_position: start_position, end_position: end_position) do |log_line|
174+
@log_file.grep(filter, regexp_mode: regexp_mode, start_position: start_position, end_position: end_position) do |result|
160175
break if @batch_sender.nil?
161176

177+
# Result is now a hash with {byte_offset, content}
178+
byte_offset = result[:byte_offset]
179+
log_line = result[:content]
180+
162181
# Add to batch buffer (sender thread will handle sending)
163-
@batch_sender.add_line(render_log_line(log_line))
182+
@batch_sender.add_line(render_log_line(log_line, byte_offset: byte_offset, show_expand_button: true))
164183

165184
line_count += 1
166185
end
@@ -184,8 +203,14 @@ def read_entire_file_with_filter(file_path, filter = nil, regexp_mode = false, s
184203
end
185204
end
186205

187-
def render_log_line(log_line)
188-
"<pre>#{FilePathParser.parse(AnsiColorParser.parse(ERB::Util.html_escape(log_line)))}</pre>"
206+
def render_log_line(log_line, byte_offset: nil, show_expand_button: false)
207+
parsed = FilePathParser.parse(AnsiColorParser.parse(ERB::Util.html_escape(log_line)))
208+
209+
{
210+
content: parsed,
211+
byte_offset: byte_offset,
212+
show_expand_button: show_expand_button
213+
}
189214
end
190215
end
191216
end

app/controllers/onlylogs/logs_controller.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ def index
1212
@autoscroll = params[:autoscroll] != "false"
1313
@regexp_mode = params[:regexp_mode] == "true"
1414
@mode = @filter.blank? ? (params[:mode] || "live") : "search" # "live" or "search"
15+
@search_type = @filter.present? ? "filter" : nil
16+
@start_position = nil
17+
@end_position = nil
18+
19+
handle_byte_offset if params[:byte_offset].present?
1520
end
1621

1722
def download
@@ -31,6 +36,18 @@ def download
3136

3237
private
3338

39+
def handle_byte_offset
40+
byte_offset = params[:byte_offset]&.to_i
41+
return unless byte_offset.present?
42+
43+
@start_position = [byte_offset - 10000, 0].max
44+
@end_position = byte_offset + 10000
45+
@filter = nil
46+
@mode = "search"
47+
@search_type = "byteoffset"
48+
@autoscroll = false
49+
end
50+
3451
def selected_log_file_path
3552
return default_log_file_path if params[:log_file_path].blank?
3653
authorized_log_file_path(params[:log_file_path])

0 commit comments

Comments
 (0)