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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: baff3b51025f63d5ba3537ebf50f48c75478e64287ea98e9634d19fe070a42f5
4
- data.tar.gz: 47c2a4a23cfbfe44c25e0fbdbb96413e49aac96fe7ff6695c3440ec00aa0b08b
3
+ metadata.gz: 54d4a10b4637cbbcf3daafb56170213a8bba06696127e2e87def0d71e50adc2d
4
+ data.tar.gz: 36fb3bca8a7520c90396f5791485c6566c63a8b9b07abb9e4611dca7982ac2ee
5
5
  SHA512:
6
- metadata.gz: 1676249dd2d66f2b9b4032473313fac9306c2f05283dfa927c229af55f5ace1f7a740456231d15de5a92b7de5b7fc56a54ecdc643e1adc33e53718ec5e2befce
7
- data.tar.gz: 73d9f8296c302d3c84d5ae74748a0ca6601c26f00722b33a1fe067f1c1e62810b6e70fac8825e04763de0640759caa785092ba56f8df69f3dc8bee0373150056
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.
@@ -35,7 +35,7 @@ module Onlylogs
35
35
  }.freeze
36
36
 
37
37
  # Pre-built closing span (frozen for better performance)
38
- CLOSING_SPAN = '</span>'.freeze
38
+ CLOSING_SPAN = "</span>".freeze
39
39
 
40
40
  def self.parse(string)
41
41
  return string if string.blank?
@@ -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 % { file: URI.encode_www_form_component(file), file_unencoded: file, line: line }
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
- return @cached_editor_instance if @cached_editor_instance
62
- @cached_editor_instance = editor_from_symbol(Onlylogs.editor)
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
- <%= javascript_importmap_tags "application", importmap: Onlylogs.importmap %>
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 src="https://cdn.jsdelivr.net/npm/clusterize.js@0.18.1/clusterize.min.js"></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 = default_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)
@@ -1,4 +1,8 @@
1
- require "importmap-rails"
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
- initializer "onlylogs.importmap", after: "importmap" do |app|
19
- Onlylogs.importmap.draw(root.join("config/importmap.rb"))
20
- if app.config.importmap.sweep_cache && app.config.reloading_enabled?
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
@@ -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
 
@@ -1,3 +1,3 @@
1
1
  module Onlylogs
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/onlylogs.rb CHANGED
@@ -13,5 +13,7 @@ require "onlylogs/socket_logger"
13
13
  # loader.setup
14
14
 
15
15
  module Onlylogs
16
- mattr_accessor :importmap, default: Importmap::Map.new
16
+ if defined?(Importmap)
17
+ mattr_accessor :importmap, default: Importmap::Map.new
18
+ end
17
19
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: onlylogs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alessandro Rodi