onlylogs 0.9.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.
- checksums.yaml +4 -4
- data/README.md +11 -1
- data/app/channels/onlylogs/logs_channel.rb +82 -70
- data/app/javascript/onlylogs/controllers/keyboard_shortcuts_controller.js +15 -11
- data/app/javascript/onlylogs/controllers/log_streamer_controller.js +170 -202
- data/app/javascript/onlylogs/controllers/range_slider_controller.js +96 -0
- data/app/javascript/onlylogs/controllers/text_selection_controller.js +1 -0
- data/app/models/onlylogs/grep.rb +14 -6
- data/app/views/onlylogs/logs/index.html.erb +25 -5
- data/app/views/onlylogs/shared/_log_container.html.erb +71 -35
- data/app/views/onlylogs/shared/_log_container_styles.html.erb +246 -49
- data/app/views/onlylogs/shared/_range_slider.html.erb +9 -5
- data/lib/onlylogs/continuous_log_writer.rb +161 -0
- data/lib/onlylogs/formatter.rb +4 -3
- data/lib/onlylogs/http_device.rb +319 -84
- data/lib/onlylogs/http_logger.rb +4 -7
- data/lib/onlylogs/spool.rb +61 -13
- data/lib/onlylogs/version.rb +1 -1
- metadata +3 -1
data/app/models/onlylogs/grep.rb
CHANGED
|
@@ -94,7 +94,8 @@ module Onlylogs
|
|
|
94
94
|
reader, writer = IO.pipe
|
|
95
95
|
|
|
96
96
|
begin
|
|
97
|
-
pid = Process.spawn(*bounded(command_args, timeout),
|
|
97
|
+
pid = Process.spawn(*deprioritised(bounded(command_args, timeout)),
|
|
98
|
+
out: writer, err: ::File::NULL, pgroup: true)
|
|
98
99
|
rescue
|
|
99
100
|
reader.close
|
|
100
101
|
raise
|
|
@@ -116,6 +117,12 @@ module Onlylogs
|
|
|
116
117
|
TIMED_OUT_EXIT_STATUS = 124
|
|
117
118
|
KILL_GRACE_PERIOD = 0.5
|
|
118
119
|
|
|
120
|
+
SEARCH_NICENESS = 19
|
|
121
|
+
|
|
122
|
+
def self.deprioritised(command_args)
|
|
123
|
+
["nice", "-n", SEARCH_NICENESS.to_s, *command_args]
|
|
124
|
+
end
|
|
125
|
+
|
|
119
126
|
def self.bounded(command_args, timeout)
|
|
120
127
|
return command_args unless timeout && timeout_command_available?
|
|
121
128
|
|
|
@@ -161,17 +168,18 @@ module Onlylogs
|
|
|
161
168
|
nil
|
|
162
169
|
end
|
|
163
170
|
|
|
171
|
+
MATCH_TIMEOUT = 0.1 # seconds
|
|
172
|
+
|
|
164
173
|
def self.match_line?(line, string, regexp_mode: false)
|
|
165
174
|
# Strip ANSI color codes from the line before matching
|
|
166
175
|
stripped_line = line.gsub(/\e\[[0-9;]*m/, "")
|
|
167
176
|
# Normalize multiple spaces to single spaces
|
|
168
177
|
normalized_line = stripped_line.gsub(/\s+/, " ")
|
|
169
178
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
end
|
|
179
|
+
pattern = regexp_mode ? string : Regexp.escape(string)
|
|
180
|
+
normalized_line.match?(Regexp.new(pattern, timeout: MATCH_TIMEOUT))
|
|
181
|
+
rescue ::RegexpError
|
|
182
|
+
false
|
|
175
183
|
end
|
|
176
184
|
end
|
|
177
185
|
end
|
|
@@ -1,22 +1,42 @@
|
|
|
1
1
|
<style>
|
|
2
2
|
.grid {
|
|
3
3
|
display: grid;
|
|
4
|
-
grid-template-rows:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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="
|
|
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
|
|
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
|
-
|
|
142
|
+
■ Stop
|
|
109
143
|
</button>
|
|
110
144
|
</div>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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>
|