onlylogs 0.8.0 → 0.10.0

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.
@@ -29,7 +29,11 @@ module Onlylogs
29
29
  ->(line) { [nil, line] }
30
30
  end
31
31
 
32
- begin
32
+ matches = 0
33
+
34
+ ActiveSupport::Notifications.instrument("search.onlylogs", file_path: file_path,
35
+ query: pattern, regexp: regexp_mode, start_position: start_position,
36
+ end_position: end_position, max_matches: max_matches) do |payload|
33
37
  each_output_line(command_args, timeout: timeout) do |line|
34
38
  byte_offset, content = parse_line.call(line.chomp)
35
39
 
@@ -37,6 +41,7 @@ module Onlylogs
37
41
  content = String.new(content, encoding: Encoding::UTF_8).scrub
38
42
 
39
43
  result = {byte_offset: byte_offset, content: content}
44
+ matches += 1
40
45
 
41
46
  if block_given?
42
47
  yield result
@@ -44,7 +49,12 @@ module Onlylogs
44
49
  results << result
45
50
  end
46
51
  end
52
+ rescue TimeoutError
53
+ payload[:timed_out] = true
54
+ raise
47
55
  ensure
56
+ payload[:matches] = matches
57
+ payload[:timed_out] ||= false
48
58
  drop_page_cache(file_path)
49
59
  end
50
60
 
@@ -84,7 +94,8 @@ module Onlylogs
84
94
  reader, writer = IO.pipe
85
95
 
86
96
  begin
87
- pid = Process.spawn(*bounded(command_args, timeout), out: writer, err: ::File::NULL, pgroup: true)
97
+ pid = Process.spawn(*deprioritised(bounded(command_args, timeout)),
98
+ out: writer, err: ::File::NULL, pgroup: true)
88
99
  rescue
89
100
  reader.close
90
101
  raise
@@ -106,6 +117,12 @@ module Onlylogs
106
117
  TIMED_OUT_EXIT_STATUS = 124
107
118
  KILL_GRACE_PERIOD = 0.5
108
119
 
120
+ SEARCH_NICENESS = 19
121
+
122
+ def self.deprioritised(command_args)
123
+ ["nice", "-n", SEARCH_NICENESS.to_s, *command_args]
124
+ end
125
+
109
126
  def self.bounded(command_args, timeout)
110
127
  return command_args unless timeout && timeout_command_available?
111
128
 
@@ -151,17 +168,18 @@ module Onlylogs
151
168
  nil
152
169
  end
153
170
 
171
+ MATCH_TIMEOUT = 0.1 # seconds
172
+
154
173
  def self.match_line?(line, string, regexp_mode: false)
155
174
  # Strip ANSI color codes from the line before matching
156
175
  stripped_line = line.gsub(/\e\[[0-9;]*m/, "")
157
176
  # Normalize multiple spaces to single spaces
158
177
  normalized_line = stripped_line.gsub(/\s+/, " ")
159
178
 
160
- if regexp_mode
161
- normalized_line.match?(string)
162
- else
163
- normalized_line.match?(Regexp.escape(string))
164
- end
179
+ pattern = regexp_mode ? string : Regexp.escape(string)
180
+ normalized_line.match?(Regexp.new(pattern, timeout: MATCH_TIMEOUT))
181
+ rescue ::RegexpError
182
+ false
165
183
  end
166
184
  end
167
185
  end
@@ -1,22 +1,42 @@
1
1
  <style>
2
2
  .grid {
3
3
  display: grid;
4
- grid-template-rows: 32px 1fr;
4
+ grid-template-rows: 38px 1fr;
5
5
  height: 100vh;
6
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
7
+ font-size: 0.8rem;
6
8
  }
7
9
 
8
10
  .grid-header {
9
11
  display: flex;
10
12
  align-items: center;
11
- border-bottom: 1px solid lightgray;
13
+ gap: 0.5rem;
14
+ padding: 0 0.65rem;
15
+ background-color: #f3f5f9;
16
+ border-bottom: 1px solid #dde3ec;
17
+ color: #58626f;
12
18
  }
13
19
 
14
- .log-file-dropdown {
15
- margin-left: 0.5rem;
20
+ .log-file-dropdown select {
21
+ font: inherit;
22
+ padding: 0.2rem 0.4rem;
23
+ border: 1px solid #b9c4d3;
24
+ border-radius: 3px;
25
+ background-color: #fff;
26
+ color: #12171f;
16
27
  }
17
28
 
18
29
  .download-link {
19
- margin-left: 0.5rem;
30
+ color: #1a4fc4;
31
+ text-decoration: none;
32
+ padding: 0.2rem 0.45rem;
33
+ border: 1px solid #9dbcf7;
34
+ border-radius: 3px;
35
+ background-color: #e8f0fe;
36
+ }
37
+
38
+ .download-link:hover {
39
+ background-color: #d7e5fd;
20
40
  }
21
41
  </style>
22
42
  <div class="grid">
@@ -28,13 +28,20 @@
28
28
  data-log-streamer-regexp-mode-value="<%= regexp_mode %>"
29
29
  data-log-streamer-mode-value="<%= mode %>"
30
30
  data-log-streamer-file-size-value="<%= file_size %>"
31
- data-action="text-selection:start->log-streamer#pauseForSelection"
31
+ data-action="text-selection:start->log-streamer#pauseForSelection text-selection:search->log-streamer#switchToSearch"
32
32
  class="onlylogs-log-container" >
33
33
  <div data-log-streamer-target="logLines" data-text-selection-target="logLines" id="scrollArea" class="onlylogs-log-lines clusterize-scroll">
34
34
  <div id="contentArea" class="clusterize-content">
35
35
  </div>
36
36
  </div>
37
37
 
38
+ <%# Search with an empty query would stream the entire file, so it is not run
39
+ at all until something is typed. This says so instead of looking idle. %>
40
+ <div class="onlylogs-search-placeholder" data-log-streamer-target="searchPlaceholder" hidden>
41
+ <p class="onlylogs-search-placeholder__title">Nothing to search for yet</p>
42
+ <p class="onlylogs-search-placeholder__hint">Type a query to search this file, or switch to Live to follow it as it is written.</p>
43
+ </div>
44
+
38
45
  <button type="button"
39
46
  data-text-selection-target="button"
40
47
  class="onlylogs-context-menu hidden-button"
@@ -44,32 +51,38 @@
44
51
  </button>
45
52
 
46
53
  <div class="onlylogs-log-toolbar">
47
- <div>
48
- <label class="toolbar-label">
49
- <input id="liveMode" type="checkbox" <%= mode == "live" ? "checked" : "" %> name="liveMode" data-log-streamer-target="liveMode" data-keyboard-shortcuts-target="liveMode" data-action="change->log-streamer#toggleLiveMode">
50
- <u>L</u>ive Mode
51
- </label>
52
- </div>
53
- <div>
54
- <label class="toolbar-label">
55
- <input id="autoscroll" type="checkbox" <%= autoscroll ? "checked" : "" %> name="autoscroll" data-log-streamer-target="autoscroll" data-keyboard-shortcuts-target="autoscroll" data-action="change->log-streamer#toggleAutoScroll">
56
- <u>A</u>utoscroll
57
- </label>
58
- </div>
59
- <div>
60
- <label class="toolbar-label">
61
- <input id="regexpMode" type="checkbox" <%= regexp_mode ? "checked" : "" %> name="regexpMode" data-log-streamer-target="regexpMode" data-text-selection-target="regexpMode" data-action="change->log-streamer#toggleRegexpMode">
62
- <u>R</u>egexp Mode
63
- </label>
54
+ <div class="onlylogs-toolbar-row onlylogs-toolbar-row--controls">
55
+ <div class="onlylogs-mode-switch" role="group" aria-label="Log mode">
56
+ <button type="button"
57
+ class="onlylogs-mode-switch__button"
58
+ data-onlylogs-mode-button="live"
59
+ data-log-streamer-target="liveButton"
60
+ data-keyboard-shortcuts-target="liveButton"
61
+ data-action="click->log-streamer#switchToLive"
62
+ aria-pressed="<%= mode == "live" %>"
63
+ title="Follow the file as it is written (L)">
64
+ <span>▶ <u>L</u>ive</span>
65
+ </button>
66
+ <button type="button"
67
+ class="onlylogs-mode-switch__button"
68
+ data-onlylogs-mode-button="static"
69
+ data-log-streamer-target="searchButton"
70
+ data-keyboard-shortcuts-target="searchButton"
71
+ data-action="click->log-streamer#switchToSearch"
72
+ aria-pressed="<%= mode != "live" %>"
73
+ title="Search what is already in the file (S)">
74
+ <span>🔍 <u>S</u>earch</span>
75
+ </button>
64
76
  </div>
65
77
  <div>
66
78
  <label class="toolbar-label">
67
- Filter:
79
+ <span data-onlylogs-mode="live">Follow:</span>
80
+ <span data-onlylogs-mode="static">Query:</span>
68
81
  <div class="filter-input-wrapper">
69
82
  <input type="text"
70
83
  name="filter"
71
84
  value="<%= filter %>"
72
- placeholder="Enter filter text..."
85
+ placeholder="<%= mode == "live" ? "follow lines matching…" : "search the whole file…" %>"
73
86
  data-log-streamer-target="filterInput"
74
87
  data-text-selection-target="filterInput"
75
88
  data-action="input->log-streamer#applyFilter">
@@ -82,7 +95,28 @@
82
95
  </div>
83
96
  </label>
84
97
  </div>
85
- <div class="range-slider-wrapper">
98
+ <div>
99
+ <label class="toolbar-label">
100
+ <input id="regexpMode" type="checkbox" <%= regexp_mode ? "checked" : "" %> name="regexpMode" data-log-streamer-target="regexpMode" data-text-selection-target="regexpMode" data-action="change->log-streamer#toggleRegexpMode">
101
+ <span><u>R</u>egexp</span>
102
+ </label>
103
+ </div>
104
+ <div data-onlylogs-mode="live">
105
+ <label class="toolbar-label">
106
+ <input id="autoscroll" type="checkbox" <%= autoscroll ? "checked" : "" %> name="autoscroll" data-log-streamer-target="autoscroll" data-keyboard-shortcuts-target="autoscroll" data-action="change->log-streamer#toggleAutoScroll">
107
+ <span><u>A</u>utoscroll</span>
108
+ </label>
109
+ </div>
110
+ <div data-onlylogs-mode="live">
111
+ <button type="button"
112
+ data-log-streamer-target="searchWholeFileButton"
113
+ class="search-whole-file-button"
114
+ data-action="click->log-streamer#searchWholeFile"
115
+ title="Search this text across the whole file">
116
+ Search whole file →
117
+ </button>
118
+ </div>
119
+ <div class="range-slider-wrapper" data-onlylogs-mode="static">
86
120
  <%= render "onlylogs/shared/range_slider",
87
121
  start_name: "start_position",
88
122
  end_name: "end_position",
@@ -105,28 +139,30 @@
105
139
  class="stop-search-button hidden-button"
106
140
  data-action="click->log-streamer#stopSearch"
107
141
  title="Stop current search">
108
- ⏹️ Stop
142
+ ■ Stop
109
143
  </button>
110
144
  </div>
111
- <div>
112
- <span data-log-streamer-target="results" class="results-text">Results: 0</span>
113
- </div>
114
- <div data-log-streamer-target="message"></div>
115
- <% unless Onlylogs.ripgrep_enabled? %>
116
- <span class="grep-warning" title="Search is slow. Install ripgrep for better performance">⚠️</span>
117
- <% end %>
118
- <div class="file-size" title="File size: <%= number_to_human_size(File.size(log_file_path)) %>">
119
- <%= number_to_human_size(File.size(log_file_path)) %>
120
- </div>
121
- <div class="clear-button-wrapper">
145
+ </div>
146
+
147
+ <div class="onlylogs-toolbar-row onlylogs-toolbar-row--status">
148
+ <span data-log-streamer-target="results" class="results-text">Results: 0</span>
149
+ <span data-log-streamer-target="message" class="status-message"></span>
150
+ <div class="onlylogs-toolbar-row__end">
151
+ <% unless Onlylogs.ripgrep_enabled? %>
152
+ <span class="grep-warning" title="Search is slow. Install ripgrep for better performance">⚠️</span>
153
+ <% end %>
154
+ <span class="file-size" title="File size: <%= number_to_human_size(File.size(log_file_path)) %>">
155
+ <%= number_to_human_size(File.size(log_file_path)) %>
156
+ </span>
122
157
  <button type="button"
123
158
  data-log-streamer-target="clearButton"
124
159
  class="clear-logs-button"
125
160
  data-action="click->log-streamer#clearLogs"
161
+ aria-label="Clear all log lines"
126
162
  title="Clear all log lines">
127
- 🗑️ Clear
163
+ 🗑
128
164
  </button>
165
+ <span data-log-streamer-target="websocketStatus" class="websocket-status websocket-status--disconnected" title="WebSocket Disconnected">🔴</span>
129
166
  </div>
130
- <div data-log-streamer-target="websocketStatus" class="websocket-status websocket-status--disconnected" title="WebSocket Disconnected">🔴</div>
131
167
  </div>
132
168
  </div>