onlylogs 0.2.1 → 0.3.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 +48 -1
- data/app/models/onlylogs/ansi_color_parser.rb +1 -1
- data/app/models/onlylogs/batch_sender.rb +2 -2
- data/app/models/onlylogs/file_path_parser.rb +18 -7
- data/app/views/layouts/onlylogs/application.html.erb +3 -1
- data/app/views/onlylogs/shared/_log_container.html.erb +16 -15
- data/config/importmap.rb +0 -1
- data/lib/onlylogs/configuration.rb +6 -6
- data/lib/onlylogs/engine.rb +13 -8
- data/lib/onlylogs/formatter.rb +8 -0
- data/lib/onlylogs/socket_logger.rb +1 -2
- data/lib/onlylogs/version.rb +1 -1
- data/lib/onlylogs.rb +3 -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: 54d4a10b4637cbbcf3daafb56170213a8bba06696127e2e87def0d71e50adc2d
|
|
4
|
+
data.tar.gz: 36fb3bca8a7520c90396f5791485c6566c63a8b9b07abb9e4611dca7982ac2ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 43e0af4fc1f92eb0a5b8c4ef7cea17310811bb816b942438dc72c1182c70ef76b4201c6d53d9d8f0f560bcf698b9e5f8119556404ca6da6917ff87ac6267142a
|
|
7
|
+
data.tar.gz: 6a6f1ac76f19c9c718064012ba3b3d9556ddd0b27b13c6ba1d58d48454fa454c8d3dbe564841cd20d9b7f14621d872d0bdc160ab6cfa1795e27725be6d6d2f7c
|
data/README.md
CHANGED
|
@@ -12,7 +12,13 @@ stream them to https://onlylogs.io and continue enjoying the same features.
|
|
|
12
12
|
> [!IMPORTANT]
|
|
13
13
|
> https://onlylogs.io is still in beta. Send us an email to a@renuo.ch if you want access to the platform.
|
|
14
14
|
|
|
15
|
-
## Installation
|
|
15
|
+
## Installation as self-hosted
|
|
16
|
+
|
|
17
|
+
If you already have a disk, you can just keep there also your log files (as well as you probably already do).
|
|
18
|
+
|
|
19
|
+
This section explains how to setup onlylogs to self host your logs and access them directly from your Rails app.
|
|
20
|
+
|
|
21
|
+
If instead you want to stream your logs to https://onlylogs.io, head to the onlylogs.io instructions page.
|
|
16
22
|
|
|
17
23
|
Add this line to your application's Gemfile:
|
|
18
24
|
|
|
@@ -63,6 +69,18 @@ Please be sure to secure them properly.
|
|
|
63
69
|
> [!IMPORTANT]
|
|
64
70
|
> By default, onlylogs endpoints are completely inaccessible until basic auth credentials are configured.
|
|
65
71
|
|
|
72
|
+
### Notes about Docker
|
|
73
|
+
|
|
74
|
+
If your app is running in a Docker container, for example with Kamal, remember to mount your logs folder:
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
# config/deploy.yml
|
|
78
|
+
volumes:
|
|
79
|
+
- "storage:/rails/storage"
|
|
80
|
+
- "cache:/rails/tmp/cache"
|
|
81
|
+
- "logs:/rails/log"
|
|
82
|
+
```
|
|
83
|
+
|
|
66
84
|
### Basic Authentication Setup
|
|
67
85
|
|
|
68
86
|
Credentials can be configured using environment variables, Rails credentials, or programmatically.
|
|
@@ -254,6 +272,18 @@ Onlylogs.configure do |config|
|
|
|
254
272
|
end
|
|
255
273
|
```
|
|
256
274
|
|
|
275
|
+
### Filtering Log Lines with a Denylist
|
|
276
|
+
|
|
277
|
+
The `Onlylogs::Formatter` supports a denylist: an array of regular expressions that prevents matching lines from being logged. This is useful for filtering out noisy or irrelevant entries like health checks or asset requests.
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
# config/environments/production.rb
|
|
281
|
+
config.logger = Onlylogs::Logger.new(Rails.root.join("log", "production.log"))
|
|
282
|
+
config.logger.formatter.denylist = [/health_check/, /ping/, /\.css\z/]
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Any log message matching at least one pattern in the denylist will be silently dropped.
|
|
286
|
+
|
|
257
287
|
## Development & Contributing
|
|
258
288
|
|
|
259
289
|
You are more than welcome to help and contribute to this package.
|
|
@@ -292,6 +322,23 @@ For testing how onlylogs behaves under production-like network conditions, you c
|
|
|
292
322
|
./bin/simulate_latency disable
|
|
293
323
|
```
|
|
294
324
|
|
|
325
|
+
### Performance Testing
|
|
326
|
+
|
|
327
|
+
Performance tests require large log files that are not included in the repository. You can download them using the provided script:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
bin/download_performance_fixtures
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Once the fixtures are downloaded, you can run the performance tests locally:
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
bin/rails test test/models/onlylogs/grep_performance_test.rb
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
> [!NOTE]
|
|
340
|
+
> Performance tests are automatically skipped in CI environments or if the large fixture files are missing.
|
|
341
|
+
|
|
295
342
|
### Plans for the future
|
|
296
343
|
|
|
297
344
|
We believe that by simply analysing your logs you can also have a fancy errors report.
|
|
@@ -55,13 +55,13 @@ module Onlylogs
|
|
|
55
55
|
|
|
56
56
|
def send_batch
|
|
57
57
|
lines_to_send = nil
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
@mutex.synchronize do
|
|
60
60
|
return if @buffer.empty?
|
|
61
61
|
lines_to_send = @buffer.dup
|
|
62
62
|
@buffer.clear
|
|
63
63
|
end
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
return if lines_to_send.empty?
|
|
66
66
|
|
|
67
67
|
@channel.send(:transmit, {
|
|
@@ -9,10 +9,10 @@ module Onlylogs
|
|
|
9
9
|
{ symbols: [ :atom ], sniff: /atom/i, url: "atom://core/open/file?filename=%{file}&line=%{line}" },
|
|
10
10
|
{ symbols: [ :emacs, :emacsclient ], sniff: /emacs/i, url: "emacs://open?url=file://%{file}&line=%{line}" },
|
|
11
11
|
{ symbols: [ :idea ], sniff: /idea/i, url: "idea://open?file=%{file}&line=%{line}" },
|
|
12
|
-
{ symbols: [ :macvim, :mvim ], sniff: /vim/i, url: "mvim://open?url=file://%{file_unencoded}&line=%{line}" },
|
|
13
|
-
{ symbols: [ :rubymine ], sniff: /mine/i, url: "x-mine://open?file=%{file}&line=%{line}" },
|
|
12
|
+
{ symbols: [ :macvim, :mvim, :vim ], sniff: /vim/i, url: "mvim://open?url=file://%{file_unencoded}&line=%{line}" },
|
|
13
|
+
{ symbols: [ :rubymine, :mine ], sniff: /mine/i, url: "x-mine://open?file=%{file}&line=%{line}" },
|
|
14
14
|
{ symbols: [ :sublime, :subl, :st ], sniff: /subl/i, url: "subl://open?url=file://%{file}&line=%{line}" },
|
|
15
|
-
{ symbols: [ :textmate, :txmt, :tm ], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" },
|
|
15
|
+
{ symbols: [ :textmate, :txmt, :tm, :mate ], sniff: /mate/i, url: "txmt://open?url=file://%{file}&line=%{line}" },
|
|
16
16
|
{ symbols: [ :vscode, :code ], sniff: /code/i, url: "vscode://file/%{file}:%{line}" },
|
|
17
17
|
{ symbols: [ :vscodium, :codium ], sniff: /codium/i, url: "vscodium://file/%{file}:%{line}" }
|
|
18
18
|
].freeze
|
|
@@ -46,7 +46,11 @@ module Onlylogs
|
|
|
46
46
|
|
|
47
47
|
def self.for_formatting_string(formatting_string)
|
|
48
48
|
new proc { |file, line|
|
|
49
|
-
formatting_string % {
|
|
49
|
+
formatting_string % {
|
|
50
|
+
file: URI.encode_www_form_component(file),
|
|
51
|
+
file_unencoded: file,
|
|
52
|
+
line: line
|
|
53
|
+
}
|
|
50
54
|
}
|
|
51
55
|
end
|
|
52
56
|
|
|
@@ -57,11 +61,18 @@ module Onlylogs
|
|
|
57
61
|
# Cache for the editor instance
|
|
58
62
|
@cached_editor_instance = nil
|
|
59
63
|
|
|
64
|
+
def self.clear_editor_cache
|
|
65
|
+
@cached_editor_instance = nil
|
|
66
|
+
end
|
|
67
|
+
|
|
60
68
|
def self.cached_editor_instance
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
@cached_editor_instance ||= if ENV["ONLYLOGS_EDITOR_URL"]
|
|
70
|
+
for_formatting_string(ENV["ONLYLOGS_EDITOR_URL"])
|
|
71
|
+
else
|
|
72
|
+
editor_from_symbol(Onlylogs.editor)
|
|
73
|
+
end
|
|
63
74
|
end
|
|
64
|
-
|
|
75
|
+
|
|
65
76
|
|
|
66
77
|
def self.editor_from_symbol(symbol)
|
|
67
78
|
KNOWN_EDITORS.each do |preset|
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
<%= yield :head %>
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
<% if defined?(Importmap) %>
|
|
11
|
+
<%= javascript_importmap_tags "application", importmap: Onlylogs.importmap %>
|
|
12
|
+
<% end %>
|
|
11
13
|
|
|
12
14
|
<style>
|
|
13
15
|
html, body {
|
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
<%# locals: (log_file_path:, tail: 100, filter: "", autoscroll: true) %>
|
|
2
|
-
<script
|
|
2
|
+
<script nonce="<%= request.content_security_policy_nonce %>"
|
|
3
|
+
src="https://cdn.jsdelivr.net/npm/clusterize.js@0.18.1/clusterize.min.js"></script>
|
|
3
4
|
<%= render "onlylogs/shared/log_container_styles" %>
|
|
4
5
|
|
|
5
6
|
<%
|
|
6
7
|
mode = filter.blank? ? "live" : "search"
|
|
7
8
|
cursor_position = mode == "search" ? 0 : [File.size(log_file_path) - (tail * 100), 0].max
|
|
8
|
-
|
|
9
|
+
|
|
9
10
|
raise SecurityError, "File path not allowed" unless Onlylogs.allowed_file_path?(log_file_path)
|
|
10
|
-
|
|
11
|
+
|
|
11
12
|
encrypted_log_file_path = Onlylogs::SecureFilePath.encrypt(log_file_path)
|
|
12
13
|
%>
|
|
13
14
|
|
|
14
15
|
<div data-controller="log-streamer text-selection keyboard-shortcuts"
|
|
15
|
-
data-log-streamer-file-path-value="<%= encrypted_log_file_path %>"
|
|
16
|
+
data-log-streamer-file-path-value="<%= encrypted_log_file_path %>"
|
|
16
17
|
data-log-streamer-cursor-position-value="<%= cursor_position %>"
|
|
17
18
|
data-log-streamer-filter-value="<%= filter %>"
|
|
18
|
-
data-log-streamer-auto-scroll-value="<%= autoscroll %>"
|
|
19
|
+
data-log-streamer-auto-scroll-value="<%= autoscroll %>"
|
|
19
20
|
data-log-streamer-mode-value="<%= mode %>"
|
|
20
21
|
class="onlylogs-log-container" >
|
|
21
22
|
<div data-log-streamer-target="logLines" data-text-selection-target="logLines" id="scrollArea" class="onlylogs-log-lines clusterize-scroll">
|
|
22
23
|
<div id="contentArea" class="clusterize-content">
|
|
23
24
|
</div>
|
|
24
25
|
</div>
|
|
25
|
-
|
|
26
|
-
<button type="button"
|
|
27
|
-
data-text-selection-target="button"
|
|
28
|
-
class="onlylogs-context-menu"
|
|
29
|
-
data-action="click->text-selection#searchSelectedText"
|
|
26
|
+
|
|
27
|
+
<button type="button"
|
|
28
|
+
data-text-selection-target="button"
|
|
29
|
+
class="onlylogs-context-menu"
|
|
30
|
+
data-action="click->text-selection#searchSelectedText"
|
|
30
31
|
title="Search selected text"
|
|
31
32
|
style="display: none;">
|
|
32
33
|
🔍 Search
|
|
@@ -55,15 +56,15 @@
|
|
|
55
56
|
<label style="margin-bottom: 0;">
|
|
56
57
|
Filter:
|
|
57
58
|
<div style="display: inline-flex; align-items: center; position: relative;">
|
|
58
|
-
<input type="text"
|
|
59
|
-
name="filter"
|
|
59
|
+
<input type="text"
|
|
60
|
+
name="filter"
|
|
60
61
|
value="<%= filter %>"
|
|
61
62
|
placeholder="Enter filter text..."
|
|
62
63
|
data-log-streamer-target="filterInput"
|
|
63
64
|
data-text-selection-target="filterInput"
|
|
64
65
|
data-action="input->log-streamer#applyFilter"
|
|
65
66
|
style="padding-right: 1.5rem;">
|
|
66
|
-
<button type="button"
|
|
67
|
+
<button type="button"
|
|
67
68
|
data-action="click->log-streamer#clearFilter"
|
|
68
69
|
class="clear-filter-button"
|
|
69
70
|
title="Clear filter">
|
|
@@ -73,7 +74,7 @@
|
|
|
73
74
|
</label>
|
|
74
75
|
</div>
|
|
75
76
|
<div>
|
|
76
|
-
<button type="button"
|
|
77
|
+
<button type="button"
|
|
77
78
|
data-log-streamer-target="stopButton"
|
|
78
79
|
data-action="click->log-streamer#stopSearch"
|
|
79
80
|
class="stop-search-button"
|
|
@@ -93,7 +94,7 @@
|
|
|
93
94
|
<%= number_to_human_size(File.size(log_file_path)) %>
|
|
94
95
|
</div>
|
|
95
96
|
<div style="width:67px">
|
|
96
|
-
<button type="button"
|
|
97
|
+
<button type="button"
|
|
97
98
|
data-log-streamer-target="clearButton"
|
|
98
99
|
data-action="click->log-streamer#clearLogs"
|
|
99
100
|
class="clear-logs-button"
|
data/config/importmap.rb
CHANGED
|
@@ -3,4 +3,3 @@ pin "@rails/actioncable", to: "actioncable.esm.js"
|
|
|
3
3
|
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
|
|
4
4
|
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
|
|
5
5
|
pin_all_from Onlylogs::Engine.root.join("app/javascript/onlylogs/controllers"), under: "controllers", to: "onlylogs/controllers"
|
|
6
|
-
|
|
@@ -14,7 +14,7 @@ module Onlylogs
|
|
|
14
14
|
@parent_controller = nil
|
|
15
15
|
@disable_basic_authentication = false
|
|
16
16
|
@ripgrep_enabled = default_ripgrep_enabled
|
|
17
|
-
@editor =
|
|
17
|
+
@editor = nil
|
|
18
18
|
@max_line_matches = 100000
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -26,20 +26,20 @@ module Onlylogs
|
|
|
26
26
|
if (credentials_editor = Rails.application.credentials.dig(:onlylogs, :editor))
|
|
27
27
|
return credentials_editor
|
|
28
28
|
end
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
# 2. Check environment variables (ONLYLOGS_EDITOR > RAILS_EDITOR > EDITOR)
|
|
31
31
|
if ENV["ONLYLOGS_EDITOR"]
|
|
32
32
|
return ENV["ONLYLOGS_EDITOR"].to_sym
|
|
33
33
|
end
|
|
34
|
-
|
|
34
|
+
|
|
35
35
|
if ENV["RAILS_EDITOR"]
|
|
36
36
|
return ENV["RAILS_EDITOR"].to_sym
|
|
37
37
|
end
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
if ENV["EDITOR"]
|
|
40
40
|
return ENV["EDITOR"].to_sym
|
|
41
41
|
end
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
# 3. Default fallback
|
|
44
44
|
:vscode
|
|
45
45
|
end
|
|
@@ -114,7 +114,7 @@ module Onlylogs
|
|
|
114
114
|
end
|
|
115
115
|
|
|
116
116
|
def self.editor
|
|
117
|
-
configuration.default_editor
|
|
117
|
+
configuration.editor || configuration.default_editor
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
def self.editor=(editor_symbol)
|
data/lib/onlylogs/engine.rb
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
begin
|
|
2
|
+
require "importmap-rails"
|
|
3
|
+
rescue LoadError
|
|
4
|
+
# importmap-rails is optional
|
|
5
|
+
end
|
|
2
6
|
|
|
3
7
|
module Onlylogs
|
|
4
8
|
class Engine < ::Rails::Engine
|
|
@@ -14,14 +18,15 @@ module Onlylogs
|
|
|
14
18
|
app.config.assets.precompile += %w[ onlylogs_manifest ]
|
|
15
19
|
end
|
|
16
20
|
|
|
21
|
+
if defined?(Importmap)
|
|
22
|
+
initializer "onlylogs.importmap", after: "importmap" do |app|
|
|
23
|
+
Onlylogs.importmap.draw(root.join("config/importmap.rb"))
|
|
24
|
+
if app.config.importmap.sweep_cache && app.config.reloading_enabled?
|
|
25
|
+
Onlylogs.importmap.cache_sweeper(watches: root.join("app/javascript"))
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Onlylogs.importmap.cache_sweeper(watches: root.join("app/javascript"))
|
|
22
|
-
|
|
23
|
-
ActiveSupport.on_load(:action_controller_base) do
|
|
24
|
-
before_action { Onlylogs.importmap.cache_sweeper.execute_if_updated }
|
|
27
|
+
ActiveSupport.on_load(:action_controller_base) do
|
|
28
|
+
before_action { Onlylogs.importmap.cache_sweeper.execute_if_updated }
|
|
29
|
+
end
|
|
25
30
|
end
|
|
26
31
|
end
|
|
27
32
|
end
|
data/lib/onlylogs/formatter.rb
CHANGED
|
@@ -2,8 +2,16 @@ module Onlylogs
|
|
|
2
2
|
class Formatter < ActiveSupport::Logger::SimpleFormatter
|
|
3
3
|
include ActiveSupport::TaggedLogging::Formatter
|
|
4
4
|
|
|
5
|
+
attr_accessor :denylist
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
super
|
|
9
|
+
@denylist = []
|
|
10
|
+
end
|
|
11
|
+
|
|
5
12
|
def call(severity, time, progname, msg)
|
|
6
13
|
return nil if "Onlylogs::LogsChannel".in?(msg)
|
|
14
|
+
return nil if denylist.any? { |pattern| pattern.match?(msg) }
|
|
7
15
|
tags = [ time.iso8601, severity[0].upcase ]
|
|
8
16
|
push_tags tags
|
|
9
17
|
str = super
|
|
@@ -18,7 +18,6 @@ module Onlylogs
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def add(severity, message = nil, progname = nil, &block)
|
|
21
|
-
|
|
22
21
|
if message.nil?
|
|
23
22
|
if block_given?
|
|
24
23
|
message = block.call
|
|
@@ -44,7 +43,7 @@ module Onlylogs
|
|
|
44
43
|
puts "Onlylogs::SocketLogger error: #{e.message}"
|
|
45
44
|
reconnect_socket
|
|
46
45
|
rescue => e
|
|
47
|
-
puts"Onlylogs::SocketLogger unexpected error: #{e.class}: #{e.message}"
|
|
46
|
+
puts "Onlylogs::SocketLogger unexpected error: #{e.class}: #{e.message}"
|
|
48
47
|
reconnect_socket
|
|
49
48
|
end
|
|
50
49
|
|
data/lib/onlylogs/version.rb
CHANGED
data/lib/onlylogs.rb
CHANGED