onlylogs 0.10.0 → 0.11.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 +28 -1
- data/app/channels/onlylogs/logs_channel.rb +8 -0
- data/app/javascript/onlylogs/controllers/log_streamer_controller.js +10 -4
- data/app/models/onlylogs/file.rb +13 -2
- data/app/views/onlylogs/shared/_log_container.html.erb +14 -3
- data/app/views/onlylogs/shared/_log_container_styles.html.erb +2 -1
- data/lib/onlylogs/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4707246899a958c58bf6ea58aba6f21234d938c660ce23dbad8df598654a4b5c
|
|
4
|
+
data.tar.gz: c46cf3df22e80cd90749bf144f62a271a9556b13ee718640377aa86f34608e05
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7eabe1b6124704eee89f759001cb7df9bf8fa5bb71ad98ac9c26bbc091db38c93ee3c7fdb4b35729f33cc9eeb2d0ac3170b2d2ef11ae5b979b7ec454664bc435
|
|
7
|
+
data.tar.gz: fa8226aa140b44b8eac0f3a03000aac95615470343d90de827d98da79ba4045c776b052171b3555ef955db2747dc760640cd787f6207ae95f6e67e70e152dca1
|
data/README.md
CHANGED
|
@@ -58,6 +58,33 @@ Here you can grep your logs with regular expressions.
|
|
|
58
58
|
> If ripgrep is not installed, onlylogs falls back to `grep`.
|
|
59
59
|
> A warning icon (⚠️) will be displayed in the toolbar when using `grep` to indicate slower search performance.
|
|
60
60
|
|
|
61
|
+
## Logging in production
|
|
62
|
+
|
|
63
|
+
Rails apps log to `STDOUT` in production and let the platform collect the stream.
|
|
64
|
+
onlylogs keeps that default: `Onlylogs::HttpLogger.new` writes every line to `$stdout` and, when
|
|
65
|
+
`ONLYLOGS_DRAIN_URL` is set, ships it to https://onlylogs.io as well. Nothing is written to disk.
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
# config/environments/production.rb
|
|
69
|
+
config.logger = Onlylogs::HttpLogger.new
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Keep this default on an **ephemeral filesystem** (Heroku, Deploio, Docker, Kubernetes). A log file
|
|
73
|
+
there is lost on every deploy and, while it grows, its page cache counts towards the container's
|
|
74
|
+
memory limit: it shows up as a memory leak that resets on each release.
|
|
75
|
+
|
|
76
|
+
If you have a **persistent disk** and want the files, for instance to browse them with the engine,
|
|
77
|
+
pass a rotating log device as the local fallback. Rails rotates at 100 MB in development, which is a
|
|
78
|
+
good size here too:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
# config/environments/production.rb
|
|
82
|
+
log_file = Logger::LogDevice.new(Rails.root.join("log", "production.log"), shift_age: 5, shift_size: 100.megabytes)
|
|
83
|
+
config.logger = Onlylogs::HttpLogger.new(local_fallback: log_file)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Any IO or log device works and is used as is. `Onlylogs::SocketLogger` accepts the same argument.
|
|
87
|
+
|
|
61
88
|
## Authentication
|
|
62
89
|
|
|
63
90
|
Yes, we should do this right away, because this engine gives access to your log files, so you want to be sure.
|
|
@@ -294,7 +321,7 @@ The `Onlylogs::Formatter` supports a denylist: an array of regular expressions t
|
|
|
294
321
|
|
|
295
322
|
```ruby
|
|
296
323
|
# config/environments/production.rb
|
|
297
|
-
config.logger = Onlylogs::
|
|
324
|
+
config.logger = Onlylogs::HttpLogger.new
|
|
298
325
|
config.logger.formatter.denylist = [/health_check/, /ping/, /\.css\z/]
|
|
299
326
|
```
|
|
300
327
|
|
|
@@ -92,6 +92,10 @@ module Onlylogs
|
|
|
92
92
|
# an explicit cursor (matches the default whole-file live-mode page load).
|
|
93
93
|
LIVE_TAIL_BYTES = 10_000
|
|
94
94
|
|
|
95
|
+
# The button re-runs the viewer's own reset, which reopens the file at its current tail.
|
|
96
|
+
ROTATED_MESSAGE = 'The file was rotated. <button type="button" class="reload-button" ' \
|
|
97
|
+
'data-action="click->log-streamer#reset">Reload</button>'
|
|
98
|
+
|
|
95
99
|
def start_log_watcher(file_path, filter = nil, regexp_mode = false)
|
|
96
100
|
return if @log_watcher_running
|
|
97
101
|
|
|
@@ -130,6 +134,10 @@ module Onlylogs
|
|
|
130
134
|
})
|
|
131
135
|
end
|
|
132
136
|
end
|
|
137
|
+
|
|
138
|
+
# watch returns on its own only when the file was rotated: nothing more will
|
|
139
|
+
# arrive under this name from where we are, so the viewer has to start over.
|
|
140
|
+
transmit({action: "finish", content: ROTATED_MESSAGE}) if @log_watcher_running
|
|
133
141
|
end
|
|
134
142
|
rescue => e
|
|
135
143
|
Rails.logger.error "Log watcher error: #{e.message}"
|
|
@@ -7,6 +7,8 @@ export default class LogStreamerController extends Controller {
|
|
|
7
7
|
autoScroll: { type: Boolean, default: true },
|
|
8
8
|
filter: { type: String, default: '' },
|
|
9
9
|
mode: { type: String, default: 'live' },
|
|
10
|
+
// false for a file nothing writes to any more: the viewer only searches, never tails.
|
|
11
|
+
liveEnabled: { type: Boolean, default: true },
|
|
10
12
|
regexpMode: { type: Boolean, default: false },
|
|
11
13
|
fileSize: { type: Number, default: 0 },
|
|
12
14
|
startPosition: { type: Number, default: 0 },
|
|
@@ -125,7 +127,7 @@ export default class LogStreamerController extends Controller {
|
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
switchToLive() {
|
|
128
|
-
if (this.isLiveMode()) return;
|
|
130
|
+
if (this.isLiveMode() || !this.liveEnabledValue) return;
|
|
129
131
|
|
|
130
132
|
this.#clearHighlighting();
|
|
131
133
|
this.#setMode('live');
|
|
@@ -178,8 +180,11 @@ export default class LogStreamerController extends Controller {
|
|
|
178
180
|
// The only writer of modeValue. Everything that changes the mode goes through
|
|
179
181
|
// here so the switch, the URL and the toolbar layout can never disagree.
|
|
180
182
|
#setMode(mode) {
|
|
183
|
+
if (mode === 'live' && !this.liveEnabledValue) return;
|
|
184
|
+
|
|
181
185
|
this.modeValue = mode;
|
|
182
|
-
|
|
186
|
+
// Search is the only mode when live is disabled, so the URL need not say so.
|
|
187
|
+
this.#updateUrlParam('mode', mode === 'live' || !this.liveEnabledValue ? null : 'static');
|
|
183
188
|
this.#liveFilterStartedAt = mode === 'live' ? new Date() : null;
|
|
184
189
|
this.#syncModeControls();
|
|
185
190
|
}
|
|
@@ -435,8 +440,9 @@ export default class LogStreamerController extends Controller {
|
|
|
435
440
|
const end = endParam ? parseInt(endParam) : this.fileSizeValue;
|
|
436
441
|
this.#setRange(start, end);
|
|
437
442
|
|
|
438
|
-
// Calculate mode: check mode param, default to live
|
|
439
|
-
|
|
443
|
+
// Calculate mode: check mode param, default to live unless live is disabled
|
|
444
|
+
const wantsStatic = params.get('mode') === 'static' || !this.liveEnabledValue;
|
|
445
|
+
this.modeValue = wantsStatic ? 'static' : 'live';
|
|
440
446
|
this.#liveFilterStartedAt = this.isLiveMode() ? new Date() : null;
|
|
441
447
|
this.#syncModeControls();
|
|
442
448
|
}
|
data/app/models/onlylogs/file.rb
CHANGED
|
@@ -8,6 +8,7 @@ module Onlylogs
|
|
|
8
8
|
self.path = path
|
|
9
9
|
self.last_position = last_position
|
|
10
10
|
validate!
|
|
11
|
+
@inode = ::File.stat(path).ino
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
def go_to_position(position)
|
|
@@ -16,11 +17,12 @@ module Onlylogs
|
|
|
16
17
|
self.last_position = position
|
|
17
18
|
end
|
|
18
19
|
|
|
20
|
+
# Yields new lines until the file is rotated away from under its name; then
|
|
21
|
+
# returns, since the position no longer means anything on the file now there.
|
|
19
22
|
def watch(&block)
|
|
20
|
-
# return enum_for(:watch) unless block
|
|
21
|
-
|
|
22
23
|
loop do
|
|
23
24
|
sleep 0.5
|
|
25
|
+
return if rotated?
|
|
24
26
|
|
|
25
27
|
new_lines = read_new_lines
|
|
26
28
|
next if new_lines.empty?
|
|
@@ -29,6 +31,15 @@ module Onlylogs
|
|
|
29
31
|
end
|
|
30
32
|
end
|
|
31
33
|
|
|
34
|
+
# A rename rotation puts another inode under the name, a copytruncate rotation
|
|
35
|
+
# keeps the inode but makes the file smaller than what was already read.
|
|
36
|
+
def rotated?
|
|
37
|
+
return true unless exist?
|
|
38
|
+
|
|
39
|
+
stat = ::File.stat(path)
|
|
40
|
+
stat.ino != @inode || stat.size < last_position
|
|
41
|
+
end
|
|
42
|
+
|
|
32
43
|
def size
|
|
33
44
|
::File.size(path)
|
|
34
45
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%# locals: (log_file_path:) %>
|
|
1
|
+
<%# locals: (log_file_path:, live_enabled: true) %>
|
|
2
2
|
<script nonce="<%= request.content_security_policy_nonce %>"
|
|
3
3
|
src="<%= asset_path('onlylogs/clusterize.js') %>"></script>
|
|
4
4
|
<link rel="stylesheet" href="<%= asset_path('onlylogs/clusterize.css') %>" />
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
autoscroll = local_assigns[:autoscroll] != false
|
|
9
9
|
filter = local_assigns[:filter] || ""
|
|
10
10
|
regexp_mode = local_assigns[:regexp_mode] == true
|
|
11
|
-
|
|
11
|
+
# A file nothing writes to any more (no drain attached, a rotated log) has nothing
|
|
12
|
+
# to follow, so live mode is not offered at all and the viewer opens in search.
|
|
13
|
+
mode = live_enabled ? (local_assigns[:mode] || "live") : "static"
|
|
12
14
|
file_size = File.size(log_file_path)
|
|
13
15
|
start_position = local_assigns[:start_position]
|
|
14
16
|
end_position = local_assigns[:end_position]
|
|
@@ -27,6 +29,7 @@
|
|
|
27
29
|
data-log-streamer-auto-scroll-value="<%= autoscroll %>"
|
|
28
30
|
data-log-streamer-regexp-mode-value="<%= regexp_mode %>"
|
|
29
31
|
data-log-streamer-mode-value="<%= mode %>"
|
|
32
|
+
data-log-streamer-live-enabled-value="<%= live_enabled %>"
|
|
30
33
|
data-log-streamer-file-size-value="<%= file_size %>"
|
|
31
34
|
data-action="text-selection:start->log-streamer#pauseForSelection text-selection:search->log-streamer#switchToSearch"
|
|
32
35
|
class="onlylogs-log-container" >
|
|
@@ -39,7 +42,13 @@
|
|
|
39
42
|
at all until something is typed. This says so instead of looking idle. %>
|
|
40
43
|
<div class="onlylogs-search-placeholder" data-log-streamer-target="searchPlaceholder" hidden>
|
|
41
44
|
<p class="onlylogs-search-placeholder__title">Nothing to search for yet</p>
|
|
42
|
-
<p class="onlylogs-search-placeholder__hint">
|
|
45
|
+
<p class="onlylogs-search-placeholder__hint">
|
|
46
|
+
<% if live_enabled %>
|
|
47
|
+
Type a query to search this file, or switch to Live to follow it as it is written.
|
|
48
|
+
<% else %>
|
|
49
|
+
Type a query to search this file.
|
|
50
|
+
<% end %>
|
|
51
|
+
</p>
|
|
43
52
|
</div>
|
|
44
53
|
|
|
45
54
|
<button type="button"
|
|
@@ -52,6 +61,7 @@
|
|
|
52
61
|
|
|
53
62
|
<div class="onlylogs-log-toolbar">
|
|
54
63
|
<div class="onlylogs-toolbar-row onlylogs-toolbar-row--controls">
|
|
64
|
+
<% if live_enabled %>
|
|
55
65
|
<div class="onlylogs-mode-switch" role="group" aria-label="Log mode">
|
|
56
66
|
<button type="button"
|
|
57
67
|
class="onlylogs-mode-switch__button"
|
|
@@ -74,6 +84,7 @@
|
|
|
74
84
|
<span>🔍 <u>S</u>earch</span>
|
|
75
85
|
</button>
|
|
76
86
|
</div>
|
|
87
|
+
<% end %>
|
|
77
88
|
<div>
|
|
78
89
|
<label class="toolbar-label">
|
|
79
90
|
<span data-onlylogs-mode="live">Follow:</span>
|
data/lib/onlylogs/version.rb
CHANGED