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.
- checksums.yaml +4 -4
- data/client/src/components/ui/JsonViewer.tsx +21 -8
- data/lib/generators/railscope/templates/initializer.rb +15 -0
- data/lib/railscope/engine.rb +1 -1
- data/lib/railscope/flush_service.rb +92 -0
- data/lib/railscope/middleware.rb +121 -27
- data/lib/railscope/storage/database.rb +9 -0
- data/lib/railscope/storage/redis_buffer.rb +92 -0
- data/lib/railscope/version.rb +1 -1
- data/lib/railscope.rb +3 -2
- data/lib/tasks/railscope.rake +15 -0
- data/public/railscope/assets/app.css +1 -1
- data/public/railscope/assets/app.js +13 -13
- metadata +4 -2
- data/lib/railscope/storage/redis_storage.rb +0 -314
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.
|
|
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/
|
|
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
|