railscope 0.1.2 → 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.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: railscope
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Phelipe Tussolini
@@ -113,10 +113,11 @@ files:
113
113
  - lib/railscope/engine.rb
114
114
  - lib/railscope/entry_data.rb
115
115
  - lib/railscope/filter.rb
116
+ - lib/railscope/flush_service.rb
116
117
  - lib/railscope/middleware.rb
117
118
  - lib/railscope/storage/base.rb
118
119
  - lib/railscope/storage/database.rb
119
- - lib/railscope/storage/redis_storage.rb
120
+ - lib/railscope/storage/redis_buffer.rb
120
121
  - lib/railscope/subscribers/base_subscriber.rb
121
122
  - lib/railscope/subscribers/command_subscriber.rb
122
123
  - lib/railscope/subscribers/exception_subscriber.rb
@@ -126,6 +127,7 @@ files:
126
127
  - lib/railscope/subscribers/request_subscriber.rb
127
128
  - lib/railscope/subscribers/view_subscriber.rb
128
129
  - lib/railscope/version.rb
130
+ - lib/tasks/railscope.rake
129
131
  - lib/tasks/railscope_sample.rake
130
132
  - public/railscope/assets/app.css
131
133
  - public/railscope/assets/app.js
@@ -1,314 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Railscope
4
- module Storage
5
- class RedisStorage < Base
6
- # Key prefixes
7
- PREFIX = "railscope"
8
- ENTRY_KEY = "#{PREFIX}:entry:%s".freeze # Hash with entry data
9
- ALL_ENTRIES_KEY = "#{PREFIX}:entries".freeze # Sorted set (score=timestamp)
10
- DISPLAYABLE_KEY = "#{PREFIX}:displayable".freeze # Sorted set of displayable entries
11
- BATCH_KEY = "#{PREFIX}:batch:%s".freeze # Sorted set per batch
12
- FAMILY_KEY = "#{PREFIX}:family:%s".freeze # Sorted set per family
13
- TYPE_KEY = "#{PREFIX}:type:%s".freeze # Sorted set per entry type
14
- TAG_KEY = "#{PREFIX}:tag:%s".freeze # Set per tag
15
-
16
- def write(attributes)
17
- entry = build_entry(attributes)
18
- store_entry(entry)
19
- entry
20
- end
21
-
22
- def update_by_batch(batch_id:, entry_type:, payload_updates:)
23
- # Find entries in this batch with the given type
24
- uuids = redis.zrevrange(batch_key(batch_id), 0, -1)
25
- return nil if uuids.empty?
26
-
27
- # Find the entry of the specified type
28
- uuids.each do |uuid|
29
- entry = find(uuid)
30
- next unless entry && entry.entry_type == entry_type
31
-
32
- # Merge payload updates
33
- updated_payload = entry.payload.merge(payload_updates)
34
- updated_entry = EntryData.new(
35
- uuid: entry.uuid,
36
- batch_id: entry.batch_id,
37
- family_hash: entry.family_hash,
38
- entry_type: entry.entry_type,
39
- payload: updated_payload,
40
- tags: entry.tags,
41
- should_display_on_index: entry.displayable?,
42
- occurred_at: entry.occurred_at,
43
- created_at: entry.created_at,
44
- updated_at: Time.current
45
- )
46
-
47
- # Re-store the entry (overwrites the existing one)
48
- ttl = Railscope.retention_days.days.to_i
49
- redis.set(entry_key(uuid), updated_entry.to_json, ex: ttl)
50
-
51
- return updated_entry
52
- end
53
-
54
- nil
55
- end
56
-
57
- def find(uuid)
58
- json = redis.get(entry_key(uuid))
59
- return nil unless json
60
-
61
- EntryData.from_json(json)
62
- end
63
-
64
- def all(filters: {}, page: 1, per_page: 25, displayable_only: true)
65
- uuids = fetch_uuids(filters, displayable_only, page, per_page)
66
- fetch_entries(uuids)
67
- end
68
-
69
- def count(filters: {}, displayable_only: true)
70
- if filters.empty?
71
- key = displayable_only ? DISPLAYABLE_KEY : ALL_ENTRIES_KEY
72
- redis.zcard(key)
73
- elsif filters[:type].present? && filters.keys == [:type]
74
- # Simple type filter - use zcard directly
75
- key = type_key(filters[:type])
76
- if displayable_only
77
- # Intersection count
78
- count_intersection(key, DISPLAYABLE_KEY)
79
- else
80
- redis.zcard(key)
81
- end
82
- elsif filters[:batch_id].present? && filters.keys == [:batch_id]
83
- redis.zcard(batch_key(filters[:batch_id]))
84
- elsif filters[:family_hash].present? && filters.keys == [:family_hash]
85
- redis.zcard(family_key(filters[:family_hash]))
86
- else
87
- # Complex filter - fetch all matching UUIDs and count
88
- count_filtered(filters, displayable_only)
89
- end
90
- end
91
-
92
- def for_batch(batch_id)
93
- uuids = redis.zrevrange(batch_key(batch_id), 0, -1)
94
- fetch_entries(uuids)
95
- end
96
-
97
- def for_family(family_hash, page: 1, per_page: 25)
98
- start_idx = (page - 1) * per_page
99
- end_idx = start_idx + per_page - 1
100
- uuids = redis.zrevrange(family_key(family_hash), start_idx, end_idx)
101
- fetch_entries(uuids)
102
- end
103
-
104
- def family_count(family_hash)
105
- redis.zcard(family_key(family_hash))
106
- end
107
-
108
- def destroy_all!
109
- keys = redis.keys("#{PREFIX}:*")
110
- return 0 if keys.empty?
111
-
112
- count = redis.zcard(ALL_ENTRIES_KEY)
113
- redis.del(*keys)
114
- count
115
- end
116
-
117
- def destroy_expired!
118
- cutoff = Railscope.retention_days.days.ago.to_f
119
-
120
- # Get expired entry UUIDs
121
- expired_uuids = redis.zrangebyscore(ALL_ENTRIES_KEY, "-inf", cutoff)
122
- return 0 if expired_uuids.empty?
123
-
124
- # Delete each entry and its index references
125
- expired_uuids.each { |uuid| delete_entry(uuid) }
126
-
127
- expired_uuids.size
128
- end
129
-
130
- def ready?
131
- Railscope.redis_available?
132
- end
133
-
134
- private
135
-
136
- def redis
137
- Railscope.redis
138
- end
139
-
140
- def build_entry(attributes)
141
- now = Time.current
142
- EntryData.new(
143
- uuid: attributes[:uuid] || SecureRandom.uuid,
144
- batch_id: attributes[:batch_id],
145
- family_hash: attributes[:family_hash],
146
- entry_type: attributes[:entry_type],
147
- payload: attributes[:payload] || {},
148
- tags: attributes[:tags] || [],
149
- should_display_on_index: attributes.fetch(:should_display_on_index, true),
150
- occurred_at: attributes[:occurred_at] || now,
151
- created_at: attributes[:created_at] || now,
152
- updated_at: attributes[:updated_at] || now
153
- )
154
- end
155
-
156
- def store_entry(entry)
157
- uuid = entry.uuid
158
- score = entry.occurred_at.to_f
159
- ttl = Railscope.retention_days.days.to_i
160
-
161
- redis.multi do |multi|
162
- # Store entry data
163
- multi.set(entry_key(uuid), entry.to_json, ex: ttl)
164
-
165
- # Add to main sorted set
166
- multi.zadd(ALL_ENTRIES_KEY, score, uuid)
167
-
168
- # Add to displayable set if applicable
169
- multi.zadd(DISPLAYABLE_KEY, score, uuid) if entry.displayable?
170
-
171
- # Add to batch set
172
- multi.zadd(batch_key(entry.batch_id), score, uuid) if entry.batch_id
173
-
174
- # Add to family set
175
- multi.zadd(family_key(entry.family_hash), score, uuid) if entry.family_hash
176
-
177
- # Add to type set
178
- multi.zadd(type_key(entry.entry_type), score, uuid) if entry.entry_type
179
-
180
- # Add to tag sets
181
- entry.tags.each do |tag|
182
- multi.sadd(tag_key(tag), uuid)
183
- end
184
-
185
- # Set TTL on index keys
186
- multi.expire(batch_key(entry.batch_id), ttl) if entry.batch_id
187
- multi.expire(family_key(entry.family_hash), ttl) if entry.family_hash
188
- end
189
- end
190
-
191
- def delete_entry(uuid)
192
- entry = find(uuid)
193
- return unless entry
194
-
195
- redis.multi do |multi|
196
- # Remove from all indexes
197
- multi.del(entry_key(uuid))
198
- multi.zrem(ALL_ENTRIES_KEY, uuid)
199
- multi.zrem(DISPLAYABLE_KEY, uuid)
200
- multi.zrem(batch_key(entry.batch_id), uuid) if entry.batch_id
201
- multi.zrem(family_key(entry.family_hash), uuid) if entry.family_hash
202
- multi.zrem(type_key(entry.entry_type), uuid) if entry.entry_type
203
- entry.tags.each { |tag| multi.srem(tag_key(tag), uuid) }
204
- end
205
- end
206
-
207
- def fetch_uuids(filters, displayable_only, page, per_page)
208
- # Determine which set to query
209
- base_key = if filters[:type].present?
210
- type_key(filters[:type])
211
- elsif filters[:batch_id].present?
212
- batch_key(filters[:batch_id])
213
- elsif filters[:family_hash].present?
214
- family_key(filters[:family_hash])
215
- elsif displayable_only
216
- DISPLAYABLE_KEY
217
- else
218
- ALL_ENTRIES_KEY
219
- end
220
-
221
- # Calculate pagination
222
- start_idx = (page - 1) * per_page
223
- end_idx = per_page == Float::INFINITY ? -1 : start_idx + per_page - 1
224
-
225
- uuids = redis.zrevrange(base_key, start_idx, end_idx)
226
-
227
- # Apply additional filters if needed
228
- if filters[:tag].present?
229
- tag_members = redis.smembers(tag_key(filters[:tag]))
230
- uuids &= tag_members
231
- end
232
-
233
- # If displayable filter and we're using a type/batch/family key
234
- if displayable_only && !%W[#{DISPLAYABLE_KEY} #{ALL_ENTRIES_KEY}].include?(base_key)
235
- displayable_members = redis.zrange(DISPLAYABLE_KEY, 0, -1)
236
- uuids &= displayable_members
237
- end
238
-
239
- uuids
240
- end
241
-
242
- def fetch_entries(uuids)
243
- return [] if uuids.empty?
244
-
245
- # Fetch all entries in a single pipeline
246
- jsons = redis.pipelined do |pipeline|
247
- uuids.each { |uuid| pipeline.get(entry_key(uuid)) }
248
- end
249
-
250
- # Parse and filter out nil results (expired entries)
251
- jsons.compact.map { |json| EntryData.from_json(json) }
252
- end
253
-
254
- # Key generators
255
- def entry_key(uuid)
256
- format(ENTRY_KEY, uuid)
257
- end
258
-
259
- def batch_key(batch_id)
260
- format(BATCH_KEY, batch_id)
261
- end
262
-
263
- def family_key(family_hash)
264
- format(FAMILY_KEY, family_hash)
265
- end
266
-
267
- def type_key(entry_type)
268
- format(TYPE_KEY, entry_type)
269
- end
270
-
271
- def tag_key(tag)
272
- format(TAG_KEY, tag)
273
- end
274
-
275
- def count_intersection(key1, key2)
276
- # Get members from both sets and count intersection
277
- members1 = redis.zrange(key1, 0, -1)
278
- members2 = redis.zrange(key2, 0, -1)
279
- (members1 & members2).size
280
- end
281
-
282
- def count_filtered(filters, displayable_only)
283
- # Get all UUIDs matching the primary filter
284
- base_key = if filters[:type].present?
285
- type_key(filters[:type])
286
- elsif filters[:batch_id].present?
287
- batch_key(filters[:batch_id])
288
- elsif filters[:family_hash].present?
289
- family_key(filters[:family_hash])
290
- elsif displayable_only
291
- DISPLAYABLE_KEY
292
- else
293
- ALL_ENTRIES_KEY
294
- end
295
-
296
- uuids = redis.zrange(base_key, 0, -1)
297
-
298
- # Apply tag filter
299
- if filters[:tag].present?
300
- tag_members = redis.smembers(tag_key(filters[:tag]))
301
- uuids &= tag_members
302
- end
303
-
304
- # Apply displayable filter
305
- if displayable_only && base_key != DISPLAYABLE_KEY
306
- displayable_members = redis.zrange(DISPLAYABLE_KEY, 0, -1)
307
- uuids &= displayable_members
308
- end
309
-
310
- uuids.size
311
- end
312
- end
313
- end
314
- end