onlylogs 0.5.2 → 0.7.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/app/assets/config/onlylogs_manifest.js +2 -0
- data/app/assets/javascripts/onlylogs/clusterize.js +342 -0
- data/app/assets/stylesheets/onlylogs/clusterize.css +35 -0
- data/app/channels/onlylogs/logs_channel.rb +110 -32
- data/app/controllers/onlylogs/logs_controller.rb +0 -7
- data/app/javascript/onlylogs/controllers/log_streamer_controller.js +431 -53
- data/app/models/onlylogs/grep.rb +31 -4
- data/app/views/onlylogs/logs/index.html.erb +1 -1
- data/app/views/onlylogs/shared/_log_container.html.erb +44 -21
- data/app/views/onlylogs/shared/_log_container_styles.html.erb +193 -13
- data/app/views/onlylogs/shared/_range_slider.html.erb +47 -0
- data/bin/super_grep +4 -1
- data/bin/super_ripgrep +10 -2
- data/lib/onlylogs/engine.rb +1 -1
- data/lib/onlylogs/http_device.rb +317 -0
- data/lib/onlylogs/http_logger.rb +21 -189
- data/lib/onlylogs/multi_device.rb +30 -0
- data/lib/onlylogs/socket_device.rb +60 -0
- data/lib/onlylogs/socket_logger.rb +17 -63
- data/lib/onlylogs/spool.rb +105 -0
- data/lib/onlylogs/version.rb +1 -1
- data/lib/onlylogs.rb +4 -0
- metadata +8 -1
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
<%# locals: (log_file_path
|
|
1
|
+
<%# locals: (log_file_path:) %>
|
|
2
2
|
<script nonce="<%= request.content_security_policy_nonce %>"
|
|
3
|
-
src="
|
|
3
|
+
src="<%= asset_path('onlylogs/clusterize.js') %>"></script>
|
|
4
|
+
<link rel="stylesheet" href="<%= asset_path('onlylogs/clusterize.css') %>" />
|
|
4
5
|
<%= render "onlylogs/shared/log_container_styles" %>
|
|
5
6
|
|
|
6
7
|
<%
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
autoscroll = local_assigns[:autoscroll] != false
|
|
9
|
+
filter = local_assigns[:filter] || ""
|
|
10
|
+
regexp_mode = local_assigns[:regexp_mode] == true
|
|
11
|
+
mode = local_assigns[:mode] || "live"
|
|
12
|
+
file_size = File.size(log_file_path)
|
|
13
|
+
start_position = local_assigns[:start_position]
|
|
14
|
+
end_position = local_assigns[:end_position]
|
|
15
|
+
byte_offset = request.params[:byte_offset]
|
|
9
16
|
|
|
10
17
|
raise SecurityError, "File path not allowed" unless Onlylogs.file_path_permitted?(log_file_path)
|
|
11
18
|
|
|
@@ -14,11 +21,13 @@
|
|
|
14
21
|
|
|
15
22
|
<div data-controller="log-streamer text-selection keyboard-shortcuts"
|
|
16
23
|
data-log-streamer-file-path-value="<%= encrypted_log_file_path %>"
|
|
17
|
-
data-log-streamer-
|
|
24
|
+
<% if start_position.present? %>data-log-streamer-start-position-value="<%= start_position %>"<% end %>
|
|
25
|
+
<% if end_position.present? %>data-log-streamer-end-position-value="<%= end_position %>"<% end %>
|
|
18
26
|
data-log-streamer-filter-value="<%= filter %>"
|
|
19
27
|
data-log-streamer-auto-scroll-value="<%= autoscroll %>"
|
|
20
28
|
data-log-streamer-regexp-mode-value="<%= regexp_mode %>"
|
|
21
29
|
data-log-streamer-mode-value="<%= mode %>"
|
|
30
|
+
data-log-streamer-file-size-value="<%= file_size %>"
|
|
22
31
|
data-action="text-selection:start->log-streamer#pauseForSelection"
|
|
23
32
|
class="onlylogs-log-container" >
|
|
24
33
|
<div data-log-streamer-target="logLines" data-text-selection-target="logLines" id="scrollArea" class="onlylogs-log-lines clusterize-scroll">
|
|
@@ -28,44 +37,42 @@
|
|
|
28
37
|
|
|
29
38
|
<button type="button"
|
|
30
39
|
data-text-selection-target="button"
|
|
31
|
-
class="onlylogs-context-menu"
|
|
40
|
+
class="onlylogs-context-menu hidden-button"
|
|
32
41
|
data-action="click->text-selection#searchSelectedText"
|
|
33
|
-
title="Search selected text"
|
|
34
|
-
style="display: none;">
|
|
42
|
+
title="Search selected text">
|
|
35
43
|
🔍 Search
|
|
36
44
|
</button>
|
|
37
45
|
|
|
38
46
|
<div class="onlylogs-log-toolbar">
|
|
39
47
|
<div>
|
|
40
|
-
<label
|
|
48
|
+
<label class="toolbar-label">
|
|
41
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">
|
|
42
50
|
<u>L</u>ive Mode
|
|
43
51
|
</label>
|
|
44
52
|
</div>
|
|
45
53
|
<div>
|
|
46
|
-
<label
|
|
54
|
+
<label class="toolbar-label">
|
|
47
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">
|
|
48
56
|
<u>A</u>utoscroll
|
|
49
57
|
</label>
|
|
50
58
|
</div>
|
|
51
59
|
<div>
|
|
52
|
-
<label
|
|
60
|
+
<label class="toolbar-label">
|
|
53
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">
|
|
54
62
|
<u>R</u>egexp Mode
|
|
55
63
|
</label>
|
|
56
64
|
</div>
|
|
57
65
|
<div>
|
|
58
|
-
<label
|
|
66
|
+
<label class="toolbar-label">
|
|
59
67
|
Filter:
|
|
60
|
-
<div
|
|
68
|
+
<div class="filter-input-wrapper">
|
|
61
69
|
<input type="text"
|
|
62
70
|
name="filter"
|
|
63
71
|
value="<%= filter %>"
|
|
64
72
|
placeholder="Enter filter text..."
|
|
65
73
|
data-log-streamer-target="filterInput"
|
|
66
74
|
data-text-selection-target="filterInput"
|
|
67
|
-
data-action="input->log-streamer#applyFilter"
|
|
68
|
-
style="padding-right: 1.5rem;">
|
|
75
|
+
data-action="input->log-streamer#applyFilter">
|
|
69
76
|
<button type="button"
|
|
70
77
|
data-action="click->log-streamer#clearFilter"
|
|
71
78
|
class="clear-filter-button"
|
|
@@ -75,18 +82,34 @@
|
|
|
75
82
|
</div>
|
|
76
83
|
</label>
|
|
77
84
|
</div>
|
|
85
|
+
<div class="range-slider-wrapper">
|
|
86
|
+
<%= render "onlylogs/shared/range_slider",
|
|
87
|
+
start_name: "start_position",
|
|
88
|
+
end_name: "end_position",
|
|
89
|
+
start_value: 0,
|
|
90
|
+
end_value: file_size,
|
|
91
|
+
min: 0,
|
|
92
|
+
max: file_size,
|
|
93
|
+
step: 1000,
|
|
94
|
+
label: "Range" %>
|
|
95
|
+
<button type="button"
|
|
96
|
+
data-action="click->log-streamer#resetRange"
|
|
97
|
+
class="reset-range-button"
|
|
98
|
+
title="Reset to full file">
|
|
99
|
+
↻
|
|
100
|
+
</button>
|
|
101
|
+
</div>
|
|
78
102
|
<div>
|
|
79
103
|
<button type="button"
|
|
80
104
|
data-log-streamer-target="stopButton"
|
|
105
|
+
class="stop-search-button hidden-button"
|
|
81
106
|
data-action="click->log-streamer#stopSearch"
|
|
82
|
-
|
|
83
|
-
title="Stop current search"
|
|
84
|
-
style="display: none;">
|
|
107
|
+
title="Stop current search">
|
|
85
108
|
⏹️ Stop
|
|
86
109
|
</button>
|
|
87
110
|
</div>
|
|
88
111
|
<div>
|
|
89
|
-
<span data-log-streamer-target="results"
|
|
112
|
+
<span data-log-streamer-target="results" class="results-text">Results: 0</span>
|
|
90
113
|
</div>
|
|
91
114
|
<div data-log-streamer-target="message"></div>
|
|
92
115
|
<% unless Onlylogs.ripgrep_enabled? %>
|
|
@@ -95,11 +118,11 @@
|
|
|
95
118
|
<div class="file-size" title="File size: <%= number_to_human_size(File.size(log_file_path)) %>">
|
|
96
119
|
<%= number_to_human_size(File.size(log_file_path)) %>
|
|
97
120
|
</div>
|
|
98
|
-
<div
|
|
121
|
+
<div class="clear-button-wrapper">
|
|
99
122
|
<button type="button"
|
|
100
123
|
data-log-streamer-target="clearButton"
|
|
101
|
-
data-action="click->log-streamer#clearLogs"
|
|
102
124
|
class="clear-logs-button"
|
|
125
|
+
data-action="click->log-streamer#clearLogs"
|
|
103
126
|
title="Clear all log lines">
|
|
104
127
|
🗑️ Clear
|
|
105
128
|
</button>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
height: var(--onlylogs-height, 100%); /* Take full height of parent container */
|
|
6
6
|
min-height: var(--onlylogs-min-height, 400px);
|
|
7
7
|
display: grid;
|
|
8
|
-
grid-template-rows: auto var(--onlylogs-toolbar-height,
|
|
8
|
+
grid-template-rows: auto var(--onlylogs-toolbar-height, 80px);
|
|
9
9
|
|
|
10
10
|
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
11
11
|
font-size: var(--onlylogs-font-size, 0.8rem);
|
|
@@ -24,8 +24,25 @@
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
&[data-log-streamer-mode-value="static"] .onlylogs-expand-btn {
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
padding: 0 0.5rem;
|
|
30
|
+
margin-right: 0.25rem;
|
|
31
|
+
background: none;
|
|
32
|
+
border: none;
|
|
33
|
+
color: #666;
|
|
34
|
+
font-weight: bold;
|
|
35
|
+
font-size: 1.1em;
|
|
36
|
+
line-height: 1;
|
|
37
|
+
|
|
38
|
+
&:hover {
|
|
39
|
+
color: #333;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
.clusterize-content {
|
|
28
44
|
outline: 0;
|
|
45
|
+
min-width: max-content;
|
|
29
46
|
}
|
|
30
47
|
|
|
31
48
|
.color-success {
|
|
@@ -76,6 +93,11 @@
|
|
|
76
93
|
margin: 0 !important;
|
|
77
94
|
padding: 0.2rem;
|
|
78
95
|
word-break: break-word; /* allow breaking long tokens like UUIDs/SQL */
|
|
96
|
+
white-space: pre;
|
|
97
|
+
|
|
98
|
+
&.highlighted-context-line {
|
|
99
|
+
background-color: rgba(255, 193, 7, 0.3);
|
|
100
|
+
}
|
|
79
101
|
}
|
|
80
102
|
|
|
81
103
|
a {
|
|
@@ -106,7 +128,11 @@
|
|
|
106
128
|
|
|
107
129
|
&:hover {
|
|
108
130
|
background-color: #bbdefb;
|
|
109
|
-
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.hidden-button {
|
|
135
|
+
display: none;
|
|
110
136
|
}
|
|
111
137
|
|
|
112
138
|
@keyframes spin {
|
|
@@ -116,20 +142,15 @@
|
|
|
116
142
|
|
|
117
143
|
.onlylogs-log-toolbar {
|
|
118
144
|
flex-shrink: 0;
|
|
119
|
-
padding:
|
|
120
|
-
display: flex;
|
|
121
|
-
|
|
122
|
-
|
|
145
|
+
padding: 0.5rem;
|
|
146
|
+
display: flex;
|
|
147
|
+
flex-wrap: wrap;
|
|
148
|
+
align-items: center;
|
|
149
|
+
gap: 0.75rem;
|
|
123
150
|
|
|
124
|
-
.live-mode-sticky {
|
|
125
|
-
opacity: 0.7;
|
|
126
|
-
cursor: not-allowed;
|
|
127
151
|
|
|
128
|
-
input[type="checkbox"] {
|
|
129
|
-
cursor: not-allowed;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
152
|
|
|
153
|
+
}
|
|
133
154
|
.clear-filter-button {
|
|
134
155
|
position: absolute;
|
|
135
156
|
right: 0.25rem;
|
|
@@ -217,6 +238,165 @@
|
|
|
217
238
|
border-radius: 0.25rem;
|
|
218
239
|
display: inline-block;
|
|
219
240
|
}
|
|
241
|
+
|
|
242
|
+
.range-slider {
|
|
243
|
+
--range-track-height: 0.25rem;
|
|
244
|
+
--range-thumb-size: 0.6rem;
|
|
245
|
+
--range-color: #2563eb;
|
|
246
|
+
--range-bg: #d1d5db;
|
|
247
|
+
--range-start-percent: 0%;
|
|
248
|
+
--range-end-percent: 100%;
|
|
249
|
+
|
|
250
|
+
display: contents;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Toolbar utilities */
|
|
254
|
+
.toolbar-label {
|
|
255
|
+
margin-bottom: 0;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.filter-input-wrapper {
|
|
259
|
+
display: inline-flex;
|
|
260
|
+
align-items: center;
|
|
261
|
+
position: relative;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.filter-input-wrapper input {
|
|
265
|
+
padding-right: 1.5rem;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.clear-button-wrapper {
|
|
269
|
+
width: 67px;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.results-text {
|
|
273
|
+
color: #666;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.hidden-button {
|
|
277
|
+
display: none;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.range-slider-wrapper {
|
|
281
|
+
display: flex;
|
|
282
|
+
align-items: center;
|
|
283
|
+
gap: 0.5rem;
|
|
284
|
+
min-width: 250px;
|
|
285
|
+
flex-shrink: 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.range-slider__container {
|
|
289
|
+
display: flex;
|
|
290
|
+
align-items: center;
|
|
291
|
+
gap: 0.4rem;
|
|
292
|
+
flex: 1;
|
|
293
|
+
height: 1.5rem;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.range-slider__label {
|
|
297
|
+
font-size: 0.75rem;
|
|
298
|
+
font-weight: 500;
|
|
299
|
+
white-space: nowrap;
|
|
300
|
+
flex-shrink: 0;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.range-slider__slider {
|
|
304
|
+
display: grid;
|
|
305
|
+
gap: 0.15rem;
|
|
306
|
+
flex: 1;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.range-slider__values {
|
|
310
|
+
display: flex;
|
|
311
|
+
gap: 0.15rem;
|
|
312
|
+
font-size: 0.6rem;
|
|
313
|
+
font-variant-numeric: tabular-nums;
|
|
314
|
+
justify-content: center;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
.range-slider__track {
|
|
318
|
+
position: relative;
|
|
319
|
+
height: var(--range-thumb-size);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.range-slider__track::before,
|
|
323
|
+
.range-slider__selected {
|
|
324
|
+
position: absolute;
|
|
325
|
+
top: 50%;
|
|
326
|
+
height: var(--range-track-height);
|
|
327
|
+
transform: translateY(-50%);
|
|
328
|
+
border-radius: 999px;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.range-slider__track::before {
|
|
332
|
+
content: "";
|
|
333
|
+
left: 0;
|
|
334
|
+
right: 0;
|
|
335
|
+
background: var(--range-bg);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.range-slider__selected {
|
|
339
|
+
left: var(--range-start-percent);
|
|
340
|
+
right: calc(100% - var(--range-end-percent));
|
|
341
|
+
background: var(--range-color);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.range-slider__input {
|
|
345
|
+
position: absolute;
|
|
346
|
+
inset: 0;
|
|
347
|
+
width: 100%;
|
|
348
|
+
appearance: none;
|
|
349
|
+
background: none;
|
|
350
|
+
pointer-events: none;
|
|
351
|
+
margin: 0;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.range-slider__input::-webkit-slider-thumb {
|
|
355
|
+
appearance: none;
|
|
356
|
+
width: var(--range-thumb-size);
|
|
357
|
+
height: var(--range-thumb-size);
|
|
358
|
+
border-radius: 50%;
|
|
359
|
+
background: var(--range-color);
|
|
360
|
+
cursor: pointer;
|
|
361
|
+
pointer-events: auto;
|
|
362
|
+
position: relative;
|
|
363
|
+
z-index: 2;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.range-slider__input::-moz-range-thumb {
|
|
367
|
+
width: var(--range-thumb-size);
|
|
368
|
+
height: var(--range-thumb-size);
|
|
369
|
+
border: 0;
|
|
370
|
+
border-radius: 50%;
|
|
371
|
+
background: var(--range-color);
|
|
372
|
+
cursor: pointer;
|
|
373
|
+
pointer-events: auto;
|
|
374
|
+
position: relative;
|
|
375
|
+
z-index: 2;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.reset-range-button {
|
|
379
|
+
background-color: transparent;
|
|
380
|
+
color: #666;
|
|
381
|
+
border: 1px solid #ccc;
|
|
382
|
+
padding: 0.3rem 0.5rem;
|
|
383
|
+
border-radius: 2px;
|
|
384
|
+
cursor: pointer;
|
|
385
|
+
font-size: 0.8rem;
|
|
386
|
+
transition: background-color 0.2s;
|
|
387
|
+
height: 1.5rem;
|
|
388
|
+
display: flex;
|
|
389
|
+
align-items: center;
|
|
390
|
+
flex-shrink: 0;
|
|
391
|
+
|
|
392
|
+
&:hover {
|
|
393
|
+
background-color: #f0f0f0;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
&:active {
|
|
397
|
+
background-color: #e0e0e0;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
220
400
|
}
|
|
221
401
|
}
|
|
222
402
|
</style>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<%# app/views/onlylogs/shared/_range_slider.html.erb %>
|
|
2
|
+
<%# locals: (start_name:, end_name:, start_value:, end_value:, min: 0, max: 100, step: 1, label: nil, show_reset: false) %>
|
|
3
|
+
|
|
4
|
+
<div class="range-slider range-slider--double"
|
|
5
|
+
data-log-streamer-target="rangeSliderContainer"
|
|
6
|
+
style="--range-min: <%= min %>; --range-max: <%= max %>; --range-start: <%= start_value %>; --range-end: <%= end_value %>;">
|
|
7
|
+
|
|
8
|
+
<div class="range-slider__container">
|
|
9
|
+
<% if label.present? %>
|
|
10
|
+
<label class="range-slider__label">
|
|
11
|
+
<%= label %>
|
|
12
|
+
</label>
|
|
13
|
+
<% end %>
|
|
14
|
+
|
|
15
|
+
<div class="range-slider__slider">
|
|
16
|
+
<div class="range-slider__track">
|
|
17
|
+
<div class="range-slider__selected"></div>
|
|
18
|
+
|
|
19
|
+
<input class="range-slider__input range-slider__input--start"
|
|
20
|
+
type="range"
|
|
21
|
+
name="<%= start_name %>"
|
|
22
|
+
min="<%= min %>"
|
|
23
|
+
max="<%= max %>"
|
|
24
|
+
step="<%= step %>"
|
|
25
|
+
value="<%= start_value %>"
|
|
26
|
+
data-log-streamer-target="startSlider"
|
|
27
|
+
data-action="input->log-streamer#updateRangeVisuals input->range-slider#updateVisuals change->log-streamer#updateRangeVisuals change->range-slider#updateVisuals">
|
|
28
|
+
|
|
29
|
+
<input class="range-slider__input range-slider__input--end"
|
|
30
|
+
type="range"
|
|
31
|
+
name="<%= end_name %>"
|
|
32
|
+
min="<%= min %>"
|
|
33
|
+
max="<%= max %>"
|
|
34
|
+
step="<%= step %>"
|
|
35
|
+
value="<%= end_value %>"
|
|
36
|
+
data-log-streamer-target="endSlider"
|
|
37
|
+
data-action="input->log-streamer#updateRangeVisuals input->range-slider#updateVisuals change->log-streamer#updateRangeVisuals change->range-slider#updateVisuals">
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="range-slider__values">
|
|
41
|
+
<output data-log-streamer-target="startOutput"><%= start_value %></output>
|
|
42
|
+
<span>–</span>
|
|
43
|
+
<output data-log-streamer-target="endOutput"><%= end_value %></output>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
data/bin/super_grep
CHANGED
|
@@ -5,7 +5,10 @@ export LC_ALL=C
|
|
|
5
5
|
max_matches=""
|
|
6
6
|
start_position=""
|
|
7
7
|
end_position=""
|
|
8
|
-
|
|
8
|
+
# Block size for dd operations. Performance testing shows no significant
|
|
9
|
+
# difference across block sizes (4K-8M) on typical log files. 1M balances
|
|
10
|
+
# memory usage and I/O efficiency.
|
|
11
|
+
block_size="${BLOCK_SIZE:-1M}"
|
|
9
12
|
|
|
10
13
|
while [[ $# -gt 0 ]]; do
|
|
11
14
|
case "$1" in
|
data/bin/super_ripgrep
CHANGED
|
@@ -5,7 +5,10 @@ export LC_ALL=C
|
|
|
5
5
|
max_matches=""
|
|
6
6
|
start_position=""
|
|
7
7
|
end_position=""
|
|
8
|
-
|
|
8
|
+
# Block size for dd operations. Performance testing shows no significant
|
|
9
|
+
# difference across block sizes (4K-8M) on typical log files. 1M balances
|
|
10
|
+
# memory usage and I/O efficiency.
|
|
11
|
+
block_size="${BLOCK_SIZE:-1M}"
|
|
9
12
|
|
|
10
13
|
while [[ $# -gt 0 ]]; do
|
|
11
14
|
case "$1" in
|
|
@@ -56,7 +59,12 @@ actual_color_regex='\x1b\[[0-9;]*m'
|
|
|
56
59
|
query_regex="${query_regex//$placeholder/$actual_color_regex}"
|
|
57
60
|
|
|
58
61
|
# Build ripgrep command
|
|
59
|
-
|
|
62
|
+
# --no-mmap: read the file with read() instead of memory-mapping it. mmap'ing a
|
|
63
|
+
# multi-GB log file maps the whole file into the process and, in a container,
|
|
64
|
+
# charges those pages to the cgroup as active memory, which can trigger an OOM
|
|
65
|
+
# kill. Plain read() pages are ordinary, readily-reclaimable page cache.
|
|
66
|
+
# --byte-offset: include byte offset in output for the logs-around-line feature
|
|
67
|
+
rg_cmd="rg --color=never --no-filename --byte-offset --no-mmap"
|
|
60
68
|
[ -n "$max_matches" ] && rg_cmd="$rg_cmd --max-count=$max_matches"
|
|
61
69
|
|
|
62
70
|
# Handle byte range if specified
|
data/lib/onlylogs/engine.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Onlylogs
|
|
|
9
9
|
isolate_namespace Onlylogs
|
|
10
10
|
|
|
11
11
|
initializer "onlylogs.assets" do |app|
|
|
12
|
-
%w[images stylesheets builds fonts].each do |subdir|
|
|
12
|
+
%w[images javascript stylesheets builds fonts].each do |subdir|
|
|
13
13
|
path = root.join("app/assets", subdir)
|
|
14
14
|
app.config.assets.paths << path if path.exist?
|
|
15
15
|
end
|