rails_event_viewer 0.1.1 → 0.1.3

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: 752061254efb5dd9bb402c098fd0b53241f5919f312650510213b36046792884
4
- data.tar.gz: 131edfa9428def3ae0da02c9f622162ee52a0de11a263a3c7ff7ba86249481af
3
+ metadata.gz: df70eeee67ca7c67116c852f98659be2d9ee3803a1bd4b949d3190f0aed1d40b
4
+ data.tar.gz: 0a83f0ce0b0504c7ba9823a1e2c3eaa1e9a4f79297aae5ff35e8f742c01b51ea
5
5
  SHA512:
6
- metadata.gz: 6f6945accdea24afb5c058910e690fbbd152c32bb70cc74655a1eefd6a7d176f819621962d0709327f2b723f74e5a544e2889b5fec82d294da516e694a6d3178
7
- data.tar.gz: cbf5155e5905e351f368254188a71a93c9bf54f28cd358bc2c72c9173ff825aa9cc416e99983e3b1ed541c536276808c1abc5a705714bc3a619ed64e08e6f083
6
+ metadata.gz: 23eaca12170699b79b5fd635204e24a25bfc6a8eb8bbef0605aeaea40fab2701dda65ca4101fb9d64fea1a5c57fc77a9f94d6362a90f91fea587b196b5d38667
7
+ data.tar.gz: 8d981f6fe1a2673e91453ddf400be91537bd8b3686217c471e783939f9814ecc0b6240c368689c45219011e6d19ccdfed2e98ea19b61f7dcfcd2e0b5095267a9
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.3 (2026-10-01)
4
+
5
+ - The flusher thread starts on the first buffered event, not at boot, so consoles and rake tasks no longer run an idle thread.
6
+ - Flusher start/stop messages log at debug level.
7
+ - Events emitted after shutdown are written immediately instead of lost.
8
+ - Web requests never write to storage. A full buffer wakes the flusher thread instead.
9
+ - The buffer is capped at 10 times `buffer_size`. Overflow drops the oldest events and logs a warning.
10
+ - Removed the unused ActionCable broadcast.
11
+
12
+ ## 0.1.2 (2026-09-30)
13
+
14
+ - Removed the `pagy` dependency. Pagination is handled by a small built-in class, so host apps on any Pagy version, or none, can install the gem.
15
+ - Page links now keep the active filters, search query, and `per_page`. Previously every page link dropped them, so page 2 of a filtered list showed unfiltered results.
16
+ - A page number past the end now shows the last page instead of an empty one.
17
+
3
18
  ## 0.1.1 (2026-09-30)
4
19
 
5
20
  - The dashboard now works under a strict Content Security Policy. Tailwind CSS is compiled into the gem, and Chart.js, its date adapter, and Chartkick are served from the host app with CSP nonces instead of from CDNs.
data/README.md CHANGED
@@ -61,6 +61,8 @@ RailsEventViewer.configure do |config|
61
61
  end
62
62
  ```
63
63
 
64
+ With `async`, events are written by a background thread, never by your web requests. The buffer holds at most 10 times `buffer_size` events. If storage can't keep up, the oldest are dropped and a warning is logged. Events still in the buffer are written on a normal shutdown but lost if the process is killed.
65
+
64
66
  Internal Rails events such as `active_record.*` and `action_controller.*` are ignored by default. Remove a pattern from `ignored_events` to capture it.
65
67
 
66
68
  ## Authentication
@@ -1,7 +1,5 @@
1
1
  module RailsEventViewer
2
2
  class ApplicationController < ActionController::Base
3
- include Pagy::Method
4
-
5
3
  layout "rails_event_viewer/application"
6
4
 
7
5
  before_action :authenticate!
@@ -72,13 +70,9 @@ module RailsEventViewer
72
70
  (params[:per_page] || RailsEventViewer.per_page).to_i.clamp(1, 100)
73
71
  end
74
72
 
75
- # Helper to create paginated results from EventsRelation
76
- # Uses a countable wrapper to make EventsRelation work with Pagy
77
- def pagy_events(relation)
78
- countable = RailsEventViewer::PagyCountable.new(relation)
79
- pagy, records = pagy(:offset, countable, limit: per_page)
80
-
81
- [pagy, records.to_a]
73
+ def paginate(relation)
74
+ pagination = Page.new(count: relation.count, per_page: per_page, page: params[:page])
75
+ [pagination, relation.offset(pagination.offset).limit(pagination.per_page).to_a]
82
76
  end
83
77
  end
84
78
  end
@@ -8,8 +8,8 @@ module RailsEventViewer
8
8
  @event_type_name = params[:name]
9
9
  total_events = RailsEventViewer.events.with_name(@event_type_name)
10
10
 
11
- @pagy, @events = pagy_events(total_events)
12
- @total_count = @pagy.count
11
+ @pagination, @events = paginate(total_events)
12
+ @total_count = @pagination.count
13
13
  @events_today = total_events.since(Time.current.beginning_of_day).count
14
14
  end
15
15
  end
@@ -2,7 +2,7 @@ module RailsEventViewer
2
2
  class EventsController < ApplicationController
3
3
  def index
4
4
  relation = apply_filters(RailsEventViewer.events)
5
- @pagy, @events = pagy_events(relation)
5
+ @pagination, @events = paginate(relation)
6
6
  @event_names = current_adapter.distinct_event_names
7
7
  end
8
8
 
@@ -20,7 +20,7 @@ module RailsEventViewer
20
20
  def search
21
21
  @query = params[:q]
22
22
  relation = RailsEventViewer.events.search(@query)
23
- @pagy, @events = pagy_events(relation)
23
+ @pagination, @events = paginate(relation)
24
24
  @event_names = current_adapter.distinct_event_names
25
25
 
26
26
  render :index
@@ -27,8 +27,8 @@ module RailsEventViewer
27
27
  RailsEventViewer.events.with_context(@key, @value)
28
28
  end
29
29
 
30
- @pagy, @events = pagy_events(events)
31
- @total_count = @pagy.count
30
+ @pagination, @events = paginate(events)
31
+ @total_count = @pagination.count
32
32
  @event_names = @events.map { |e| event_name(e) }.uniq
33
33
 
34
34
  @first_event_at, @last_event_at = current_adapter.event_time_span(events)
@@ -41,6 +41,10 @@ module RailsEventViewer
41
41
  end
42
42
  end
43
43
 
44
+ def page_path(page)
45
+ "#{request.path}?#{request.query_parameters.merge("page" => page.to_s).to_query}"
46
+ end
47
+
44
48
  def timezone_options
45
49
  ActiveSupport::TimeZone.all.map { |tz| [tz.to_s, tz.name] }
46
50
  end
@@ -0,0 +1,35 @@
1
+ module RailsEventViewer
2
+ class Page
3
+ attr_reader :page, :per_page, :count
4
+
5
+ def initialize(count:, per_page:, page:)
6
+ @count = count
7
+ @per_page = per_page
8
+ @page = page.to_i.clamp(1, pages)
9
+ end
10
+
11
+ def pages
12
+ [(count.to_f / per_page).ceil, 1].max
13
+ end
14
+
15
+ def offset
16
+ (page - 1) * per_page
17
+ end
18
+
19
+ def from
20
+ count.zero? ? 0 : offset + 1
21
+ end
22
+
23
+ def to
24
+ [offset + per_page, count].min
25
+ end
26
+
27
+ def previous
28
+ page - 1 if page > 1
29
+ end
30
+
31
+ def next
32
+ page + 1 if page < pages
33
+ end
34
+ end
35
+ end
@@ -31,7 +31,7 @@
31
31
  icon: "calendar" %>
32
32
  <%= render "rails_event_viewer/shared/stat_card",
33
33
  title: "Page",
34
- value: "#{@pagy.page} of #{@pagy.pages}",
34
+ value: "#{@pagination.page} of #{@pagination.pages}",
35
35
  icon: "tag" %>
36
36
  </div>
37
37
 
@@ -92,7 +92,7 @@
92
92
  </table>
93
93
  </div>
94
94
 
95
- <%= render "rails_event_viewer/shared/pagination", pagy: @pagy %>
95
+ <%= render "rails_event_viewer/shared/pagination", pagination: @pagination %>
96
96
  <% else %>
97
97
  <div class="px-6 py-12 text-center text-gray-500">
98
98
  No events of this type yet.
@@ -11,7 +11,7 @@
11
11
  <% end %>
12
12
  </h1>
13
13
  <div class="text-sm text-gray-500">
14
- <%= @pagy.count %> total events
14
+ <%= @pagination.count %> total events
15
15
  </div>
16
16
  </div>
17
17
 
@@ -43,7 +43,7 @@
43
43
  </table>
44
44
  </div>
45
45
 
46
- <%= render "rails_event_viewer/shared/pagination", pagy: @pagy %>
46
+ <%= render "rails_event_viewer/shared/pagination", pagination: @pagination %>
47
47
  <% else %>
48
48
  <div class="px-6 py-12 text-center text-gray-500">
49
49
  <svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -124,7 +124,7 @@
124
124
  </div>
125
125
  </div>
126
126
 
127
- <%= render "rails_event_viewer/shared/pagination", pagy: @pagy %>
127
+ <%= render "rails_event_viewer/shared/pagination", pagination: @pagination %>
128
128
  <% else %>
129
129
  <div class="px-6 py-12 text-center text-gray-500">
130
130
  No events found in this group.
@@ -1,20 +1,20 @@
1
- <% if pagy.pages > 1 %>
1
+ <% if pagination.pages > 1 %>
2
2
  <div class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
3
3
  <div class="flex-1 flex items-center justify-between">
4
4
  <div>
5
5
  <p class="text-sm text-gray-700">
6
6
  Showing
7
- <span class="font-medium"><%= pagy.from %></span>
7
+ <span class="font-medium"><%= pagination.from %></span>
8
8
  to
9
- <span class="font-medium"><%= pagy.to %></span>
9
+ <span class="font-medium"><%= pagination.to %></span>
10
10
  of
11
- <span class="font-medium"><%= pagy.count %></span>
11
+ <span class="font-medium"><%= pagination.count %></span>
12
12
  results
13
13
  </p>
14
14
  </div>
15
15
  <nav class="flex items-center space-x-1" aria-label="Pagination">
16
- <% if pagy.previous %>
17
- <%= link_to url_for(page: pagy.previous), class: "relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50" do %>
16
+ <% if pagination.previous %>
17
+ <%= link_to page_path(pagination.previous), class: "relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50" do %>
18
18
  <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
19
19
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
20
20
  </svg>
@@ -29,18 +29,18 @@
29
29
  </span>
30
30
  <% end %>
31
31
 
32
- <% (1..pagy.pages).each do |page_num| %>
33
- <% if page_num == pagy.page %>
32
+ <% (1..pagination.pages).each do |page_num| %>
33
+ <% if page_num == pagination.page %>
34
34
  <span class="relative inline-flex items-center px-3 py-2 text-sm font-medium text-white bg-indigo-600 border border-indigo-600 rounded-md"><%= page_num %></span>
35
- <% elsif page_num == 1 || page_num == pagy.pages || (page_num >= pagy.page - 2 && page_num <= pagy.page + 2) %>
36
- <%= link_to page_num, url_for(page: page_num), class: "relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50" %>
37
- <% elsif page_num == pagy.page - 3 || page_num == pagy.page + 3 %>
35
+ <% elsif page_num == 1 || page_num == pagination.pages || (page_num >= pagination.page - 2 && page_num <= pagination.page + 2) %>
36
+ <%= link_to page_num, page_path(page_num), class: "relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50" %>
37
+ <% elsif page_num == pagination.page - 3 || page_num == pagination.page + 3 %>
38
38
  <span class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-700">...</span>
39
39
  <% end %>
40
40
  <% end %>
41
41
 
42
- <% if pagy.next %>
43
- <%= link_to url_for(page: pagy.next), class: "relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50" do %>
42
+ <% if pagination.next %>
43
+ <%= link_to page_path(pagination.next), class: "relative inline-flex items-center px-3 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50" do %>
44
44
  <span class="mr-1">Next</span>
45
45
  <svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
46
46
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
@@ -4,16 +4,16 @@ module RailsEventViewer
4
4
  class Subscriber
5
5
  NANOSECONDS_PER_SECOND = 1_000_000_000.0
6
6
  SHUTDOWN_TIMEOUT_SECONDS = 5
7
- ACTION_CABLE_CHANNEL = "rails_event_viewer:events"
7
+ MAX_BUFFER_MULTIPLIER = 10
8
8
 
9
9
  def initialize
10
10
  @buffer = Queue.new
11
+ @flush_requests = Queue.new
11
12
  @mutex = Mutex.new
12
13
  @flusher_thread = nil
13
14
  @shutdown_requested = false
15
+ @dropped_count = 0
14
16
  @pid = Process.pid
15
-
16
- ensure_flusher_thread!
17
17
  end
18
18
 
19
19
  def emit(event)
@@ -41,7 +41,7 @@ module RailsEventViewer
41
41
  return if events_to_write.empty?
42
42
 
43
43
  write_immediately(events_to_write)
44
- broadcast_events(events_to_write)
44
+ report_dropped_events
45
45
  end
46
46
 
47
47
  def stop!
@@ -50,7 +50,7 @@ module RailsEventViewer
50
50
  end
51
51
 
52
52
  if @flusher_thread&.alive?
53
- @flusher_thread.wakeup rescue nil
53
+ @flush_requests << true
54
54
  @flusher_thread.join(SHUTDOWN_TIMEOUT_SECONDS)
55
55
  end
56
56
 
@@ -58,7 +58,7 @@ module RailsEventViewer
58
58
  @flusher_thread = nil
59
59
  end
60
60
 
61
- flush!
61
+ flush! until @buffer.empty?
62
62
  end
63
63
 
64
64
  def running?
@@ -77,25 +77,56 @@ module RailsEventViewer
77
77
  if @pid != Process.pid
78
78
  @pid = Process.pid
79
79
  @buffer = Queue.new
80
+ @flush_requests = Queue.new
80
81
  @flusher_thread = nil
81
82
  @shutdown_requested = false
83
+ @dropped_count = 0
82
84
  end
83
85
  end
84
- ensure_flusher_thread!
85
86
  end
86
87
 
87
88
  def drain_buffer
88
89
  events = []
89
- loop { events << @buffer.pop(true) }
90
+ [@buffer.size, max_buffer_size].min.times { events << @buffer.pop(true) }
91
+ events
90
92
  rescue ThreadError
91
93
  events
92
94
  end
93
95
 
94
96
  def buffer_event(entry)
95
97
  @buffer << entry
98
+ return flush! if shutdown_requested?
99
+
96
100
  ensure_flusher_thread!
101
+ drop_oldest_events if @buffer.size > max_buffer_size
102
+ request_flush if @buffer.size >= RailsEventViewer.buffer_size
103
+ end
97
104
 
98
- flush! if @buffer.size >= RailsEventViewer.buffer_size
105
+ def max_buffer_size
106
+ RailsEventViewer.buffer_size * MAX_BUFFER_MULTIPLIER
107
+ end
108
+
109
+ def drop_oldest_events
110
+ dropped = 0
111
+ while @buffer.size > max_buffer_size
112
+ @buffer.pop(true)
113
+ dropped += 1
114
+ end
115
+ rescue ThreadError
116
+ nil
117
+ ensure
118
+ @mutex.synchronize { @dropped_count += dropped } if dropped.positive?
119
+ end
120
+
121
+ def report_dropped_events
122
+ dropped = @mutex.synchronize { @dropped_count.tap { @dropped_count = 0 } }
123
+ return if dropped.zero?
124
+
125
+ effective_logger&.warn("[RailsEventViewer] Dropped #{dropped} events because the buffer was full")
126
+ end
127
+
128
+ def request_flush
129
+ @flush_requests << true if @flush_requests.empty?
99
130
  end
100
131
 
101
132
  def ensure_flusher_thread!
@@ -110,18 +141,21 @@ module RailsEventViewer
110
141
  end
111
142
 
112
143
  def run_flusher_loop
113
- log_info("[RailsEventViewer] Flusher thread started (PID: #{Process.pid})")
144
+ effective_logger&.debug("[RailsEventViewer] Flusher thread started (PID: #{Process.pid})")
114
145
 
115
146
  until shutdown_requested?
116
147
  begin
117
- sleep(RailsEventViewer.flush_interval)
118
- flush! unless @buffer.empty?
148
+ @flush_requests.pop(timeout: RailsEventViewer.flush_interval)
149
+ loop do
150
+ flush!
151
+ break if @buffer.size < RailsEventViewer.buffer_size || shutdown_requested?
152
+ end
119
153
  rescue => e
120
154
  log_error("[RailsEventViewer] Flusher error: #{e.message}")
121
155
  end
122
156
  end
123
157
 
124
- log_info("[RailsEventViewer] Flusher thread stopped (PID: #{Process.pid})")
158
+ effective_logger&.debug("[RailsEventViewer] Flusher thread stopped (PID: #{Process.pid})")
125
159
  end
126
160
 
127
161
  def shutdown_requested?
@@ -136,22 +170,6 @@ module RailsEventViewer
136
170
  log_error("[RailsEventViewer] Failed to write #{events.size} events: #{e.message}")
137
171
  end
138
172
 
139
- def broadcast_events(events)
140
- return unless defined?(ActionCable)
141
- return unless events.any?
142
-
143
- ActionCable.server.broadcast(
144
- ACTION_CABLE_CHANNEL,
145
- {
146
- action: "new_events",
147
- count: events.size,
148
- events: events.map { |e| { name: e[:name], occurred_at: e[:occurred_at]&.iso8601 } }
149
- }
150
- )
151
- rescue => e
152
- log_error("[RailsEventViewer] Broadcast error: #{e.message}")
153
- end
154
-
155
173
  def should_capture?(event)
156
174
  return false unless event.is_a?(Hash)
157
175
  return false if event[:name].blank?
@@ -234,10 +252,6 @@ module RailsEventViewer
234
252
  log_error("[RailsEventViewer] Failed to capture event '#{event[:name]}': #{error.message}")
235
253
  end
236
254
 
237
- def log_info(message)
238
- effective_logger&.info(message)
239
- end
240
-
241
255
  def log_error(message)
242
256
  logger = effective_logger
243
257
 
@@ -1,3 +1,3 @@
1
1
  module RailsEventViewer
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.3"
3
3
  end
@@ -1,4 +1,3 @@
1
- require "pagy"
2
1
  require "chartkick"
3
2
  require "groupdate"
4
3
 
@@ -118,5 +117,4 @@ require "rails_event_viewer/adapter"
118
117
  require "rails_event_viewer/events_relation"
119
118
  require "rails_event_viewer/subscriber"
120
119
  require "rails_event_viewer/json_query"
121
- require "rails_event_viewer/pagy_countable"
122
120
  require "rails_event_viewer/time_utils"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_event_viewer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keshav Biswa
@@ -23,20 +23,6 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: 8.1.0
26
- - !ruby/object:Gem::Dependency
27
- name: pagy
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: '43.0'
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '43.0'
40
26
  - !ruby/object:Gem::Dependency
41
27
  name: chartkick
42
28
  requirement: !ruby/object:Gem::Requirement
@@ -93,6 +79,7 @@ files:
93
79
  - app/helpers/rails_event_viewer/application_helper.rb
94
80
  - app/models/rails_event_viewer/application_record.rb
95
81
  - app/models/rails_event_viewer/entry.rb
82
+ - app/models/rails_event_viewer/page.rb
96
83
  - app/views/layouts/rails_event_viewer/application.html.erb
97
84
  - app/views/rails_event_viewer/analytics/overview.html.erb
98
85
  - app/views/rails_event_viewer/dashboard/index.html.erb
@@ -123,7 +110,6 @@ files:
123
110
  - lib/rails_event_viewer/engine.rb
124
111
  - lib/rails_event_viewer/events_relation.rb
125
112
  - lib/rails_event_viewer/json_query.rb
126
- - lib/rails_event_viewer/pagy_countable.rb
127
113
  - lib/rails_event_viewer/subscriber.rb
128
114
  - lib/rails_event_viewer/time_utils.rb
129
115
  - lib/rails_event_viewer/version.rb
@@ -1,31 +0,0 @@
1
- module RailsEventViewer
2
- class PagyCountable
3
- def initialize(relation)
4
- @relation = relation
5
- end
6
-
7
- def count(*)
8
- @relation.count
9
- end
10
-
11
- def offset(value)
12
- OffsetWrapper.new(@relation, value)
13
- end
14
-
15
- # Intermediate wrapper that captures offset and applies limit
16
- class OffsetWrapper
17
- def initialize(relation, offset_value)
18
- @relation = relation
19
- @offset_value = offset_value
20
- end
21
-
22
- def limit(value)
23
- @relation.offset(@offset_value).limit(value).to_a
24
- end
25
-
26
- def to_a
27
- @relation.offset(@offset_value).to_a
28
- end
29
- end
30
- end
31
- end