Class: Onlylogs::Grep
- Inherits:
-
Object
- Object
- Onlylogs::Grep
- Defined in:
- app/models/onlylogs/grep.rb
Class Method Summary collapse
-
.drop_page_cache(file_path) ⇒ Object
Searching a large log file pulls the whole file into the OS page cache.
- .grep(pattern, file_path, start_position: 0, end_position: nil, regexp_mode: false, &block) ⇒ Object
- .match_line?(line, string, regexp_mode: false) ⇒ Boolean
Class Method Details
.drop_page_cache(file_path) ⇒ Object
Searching a large log file pulls the whole file into the OS page cache. In a container the kernel charges that cache to the cgroup, so a few searches over multi-GB logs can exhaust the memory limit and trigger an OOM kill even though no Ruby memory leaked. Hint the kernel to drop the pages we just read. Best-effort: advise is only a hint and is unsupported on some platforms, so never let it break a search.
60 61 62 63 64 |
# File 'app/models/onlylogs/grep.rb', line 60 def self.drop_page_cache(file_path) ::File.open(file_path) { |file| file.advise(:dontneed) } rescue nil end |
.grep(pattern, file_path, start_position: 0, end_position: nil, regexp_mode: false, &block) ⇒ Object
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'app/models/onlylogs/grep.rb', line 3 def self.grep(pattern, file_path, start_position: 0, end_position: nil, regexp_mode: false, &block) # Use the appropriate script based on configuration script_name = Onlylogs.ripgrep_enabled? ? "super_ripgrep" : "super_grep" super_grep_path = ::File.("../../../bin/#{script_name}", __dir__) command_args = [super_grep_path] command_args += ["--max-matches", Onlylogs.max_line_matches.to_s] if Onlylogs.max_line_matches.present? command_args << "--regexp" if regexp_mode # Add byte range parameters if specified if start_position > 0 || end_position command_args << "--start-position" << start_position.to_s command_args << "--end-position" << end_position.to_s if end_position end command_args += [pattern, file_path] results = [] # Set up parsing logic based on whether ripgrep includes byte offsets parse_line = if Onlylogs.ripgrep_enabled? ->(line) { parts = line.split(":", 2) [parts[0].to_i, parts[1] || ""] } else ->(line) { [nil, line] } end IO.popen(command_args, err: "/dev/null") do |io| io.each_line do |line| byte_offset, content = parse_line.call(line.chomp) # Use String.new to create a copy and prevent memory retention from IO buffers content = String.new(content, encoding: Encoding::UTF_8).scrub result = {byte_offset: byte_offset, content: content} if block_given? yield result else results << result end end ensure drop_page_cache(file_path) end block_given? ? nil : results end |
.match_line?(line, string, regexp_mode: false) ⇒ Boolean
66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'app/models/onlylogs/grep.rb', line 66 def self.match_line?(line, string, regexp_mode: false) # Strip ANSI color codes from the line before matching stripped_line = line.gsub(/\e\[[0-9;]*m/, "") # Normalize multiple spaces to single spaces normalized_line = stripped_line.gsub(/\s+/, " ") if regexp_mode normalized_line.match?(string) else normalized_line.match?(Regexp.escape(string)) end end |