railscope 0.1.3 → 0.1.4

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: 365fa77a20cf2375e26da9f92c52dae76d20ff8cce31a793f921e7e392208149
4
- data.tar.gz: 838667bc52bf55f90c2372d713d4677407675f2ec8b27cdcc460eb701ea89931
3
+ metadata.gz: 407d8ab344bde0dfb68984086462f3aef07114a6d372ce5f6a2a4fb4ca4c5fd1
4
+ data.tar.gz: 1dff09145783ff05482bf3d9d118401d325d2373e0bdc8fa3a1ae74885713623
5
5
  SHA512:
6
- metadata.gz: 25d34d2449ae7cd8c9b0940438a93de8357cb4f5a73b85fab3f762bbd46cb0a41f87e905c474d467b15330f83ce7c35f3e7d9adbaf4a3bd88a8f0c0f8ee683b1
7
- data.tar.gz: 392dfb08fe6a2fbda012364e0a0e705a913337626e4fef890deb1bff469cb009ee68e8bb851988e8814a7e7f578cd04df6295e0f821dd5364205b337af2c30b8
6
+ metadata.gz: 3283c7964a1b56b8b89ee354204394d8d5aff9b1d0d7c36d49e2ed45d1d3a0c0382da567e869b9b3b836fbf4a4d9c261beed24efee8931f96e529e8e04d329e0
7
+ data.tar.gz: 8ef7eaf49c58775642fa9c0cb8a05c451d7f9e07b87315dbdb5c5990eed590c2d66a8d889b40feef213c97cf0ebb6f18edd3606fcc9215dde30e5ae578a43905
data/README.md CHANGED
@@ -13,7 +13,8 @@ Railscope provides insight into the requests, exceptions, database queries, jobs
13
13
  - **Context Correlation** - Link all events from the same request via `request_id`
14
14
  - **Sensitive Data Filtering** - Automatic masking of passwords, tokens, and secrets
15
15
  - **Dark Mode UI** - Beautiful GitHub-inspired dark interface
16
- - **Zero Dependencies** - Works with any Rails 7+ application
16
+ - **Storage Backends** - Direct database writes or Redis buffer with batch flush
17
+ - **Zero Dependencies** - Works with any Rails 7+ application (Redis optional)
17
18
 
18
19
  ## Installation
19
20
 
@@ -55,6 +56,9 @@ Create `config/initializers/railscope.rb`:
55
56
 
56
57
  ```ruby
57
58
  Railscope.configure do |config|
59
+ # Storage backend: :database (default) or :redis (buffer)
60
+ config.storage_backend = :database
61
+
58
62
  # Data retention (default: 7 days)
59
63
  config.retention_days = 30
60
64
 
@@ -66,11 +70,49 @@ Railscope.configure do |config|
66
70
  end
67
71
  ```
68
72
 
73
+ ### Storage Backends
74
+
75
+ Railscope supports two storage modes:
76
+
77
+ | Mode | Write | Read | Requires |
78
+ |------|-------|------|----------|
79
+ | `:database` | Direct INSERT (sync) | PostgreSQL | PostgreSQL |
80
+ | `:redis` | Redis buffer (async) | PostgreSQL | PostgreSQL + Redis |
81
+
82
+ **`:database`** (default) -- Entries are written directly to PostgreSQL during the request. Simplest setup, no Redis needed.
83
+
84
+ **`:redis`** -- Entries are buffered in Redis (~0.1ms per write) and batch-flushed to PostgreSQL periodically. Reduces request latency in high-throughput applications.
85
+
86
+ When using `:redis`, you need to flush the buffer periodically:
87
+
88
+ ```ruby
89
+ # From a background job (Sidekiq, GoodJob, SolidQueue, etc.)
90
+ class RailscopeFlushJob < ApplicationJob
91
+ queue_as :low
92
+
93
+ def perform
94
+ Railscope::FlushService.call
95
+ end
96
+ end
97
+
98
+ # From a cron/scheduler
99
+ every 5.seconds do
100
+ runner "Railscope::FlushService.call"
101
+ end
102
+
103
+ # Or via rake
104
+ # $ rake railscope:flush
105
+ ```
106
+
107
+ > **Note:** Entries only appear in the dashboard after being flushed to PostgreSQL.
108
+
69
109
  ### Environment Variables
70
110
 
71
111
  | Variable | Description | Default |
72
112
  |----------|-------------|---------|
73
113
  | `RAILSCOPE_ENABLED` | Enable/disable recording | `false` |
114
+ | `RAILSCOPE_STORAGE` | Storage backend (`database` or `redis`) | `database` |
115
+ | `RAILSCOPE_REDIS_URL` | Redis connection URL | Falls back to `REDIS_URL` |
74
116
  | `RAILSCOPE_RETENTION_DAYS` | Days to keep entries | `7` |
75
117
 
76
118
  ## Authorization
@@ -144,6 +186,16 @@ Entries are automatically tagged:
144
186
  - **Exceptions**: `exception`, exception class name
145
187
  - **Jobs**: `job`, `enqueue`/`perform`, queue name, `failed`
146
188
 
189
+ ### Rake Tasks
190
+
191
+ ```bash
192
+ # Flush buffered entries from Redis to database (redis mode only)
193
+ rake railscope:flush
194
+
195
+ # Purge expired entries (older than retention_days)
196
+ rake railscope:purge
197
+ ```
198
+
147
199
  ### Purging Old Entries
148
200
 
149
201
  Run the purge job to remove entries older than `retention_days`:
@@ -212,12 +264,20 @@ Railscope::Entry.expired
212
264
 
213
265
  Railscope is designed to have minimal impact:
214
266
 
215
- - Events are recorded synchronously but quickly
216
267
  - Ignored paths skip all processing
217
268
  - Sensitive data filtering is done once before save
218
269
  - Purge job removes old entries to control database size
219
270
 
271
+ **With `:database` backend:**
272
+ - Entries are written synchronously via INSERT during the request
273
+
274
+ **With `:redis` backend:**
275
+ - Writes go to Redis (~0.1ms per entry), near-zero impact on request latency
276
+ - `Entry.insert_all` batches up to 1000 records per flush for efficient persistence
277
+ - Flush is safe to run concurrently (Redis `LPOP` is atomic)
278
+
220
279
  For high-traffic production environments, consider:
280
+ - Using `:redis` backend for lower request latency
221
281
  - Shorter retention periods
222
282
  - Adding high-traffic paths to ignore list
223
283
  - Running purge job more frequently
@@ -4,9 +4,30 @@ module Railscope
4
4
  class ApplicationRecord < ActiveRecord::Base
5
5
  self.abstract_class = true
6
6
 
7
- # Support for separate database connection when RAILSCOPE_DATABASE_URL is configured
8
- # This isolates Railscope's writes from the main application database,
9
- # preventing lock contention during high-traffic periods.
10
- connects_to database: { writing: :railscope, reading: :railscope } if ENV["RAILSCOPE_DATABASE_URL"].present?
7
+ # Support for separate database connection.
8
+ # Activated when a "railscope" database is defined in database.yml
9
+ # or when RAILSCOPE_DATABASE_URL is set.
10
+ #
11
+ # Example database.yml:
12
+ # development:
13
+ # primary:
14
+ # <<: *default
15
+ # database: myapp_development
16
+ # migrations_paths: db/migrate
17
+ # railscope:
18
+ # <<: *default
19
+ # database: myapp_railscope_development
20
+ # migrations_paths: db/railscope_migrate
21
+ #
22
+ def self.railscope_separate_database?
23
+ return true if ENV["RAILSCOPE_DATABASE_URL"].present?
24
+
25
+ configs = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env)
26
+ configs.any? { |c| c.name == "railscope" }
27
+ rescue StandardError
28
+ false
29
+ end
30
+
31
+ connects_to database: { writing: :railscope, reading: :railscope } if railscope_separate_database?
11
32
  end
12
33
  end
@@ -29,8 +29,23 @@ module Railscope
29
29
 
30
30
  initializer "railscope.migrations" do |app|
31
31
  unless app.root.to_s.match?(root.to_s)
32
- config.paths["db/migrate"].expanded.each do |expanded_path|
33
- app.config.paths["db/migrate"] << expanded_path
32
+ if railscope_separate_database?
33
+ # Separate database: copy migrations to db/railscope_migrate/
34
+ # so they only run against the railscope database
35
+ target_dir = app.root.join("db", "railscope_migrate")
36
+ config.paths["db/migrate"].expanded.each do |source_dir|
37
+ Dir[File.join(source_dir, "*.rb")].each do |migration|
38
+ target = target_dir.join(File.basename(migration))
39
+ next if target.exist?
40
+
41
+ FileUtils.mkdir_p(target_dir)
42
+ FileUtils.cp(migration, target)
43
+ end
44
+ end
45
+ else
46
+ config.paths["db/migrate"].expanded.each do |expanded_path|
47
+ app.config.paths["db/migrate"] << expanded_path
48
+ end
34
49
  end
35
50
  end
36
51
  end
@@ -84,5 +99,14 @@ module Railscope
84
99
  # Subscribe to rake tasks after they're loaded
85
100
  Railscope::Subscribers::CommandSubscriber.subscribe
86
101
  end
102
+
103
+ private
104
+
105
+ def railscope_separate_database?
106
+ configs = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env)
107
+ configs.any? { |c| c.name == "railscope" }
108
+ rescue StandardError
109
+ false
110
+ end
87
111
  end
88
112
  end
@@ -5,11 +5,13 @@ module Railscope
5
5
  class RedisBuffer < Base
6
6
  BUFFER_KEY = "railscope:buffer"
7
7
  UPDATES_KEY = "railscope:buffer:updates"
8
+ BUFFER_TTL = 4.hours.to_i
8
9
 
9
10
  # WRITE → Redis (fast, ~0.1ms)
10
11
  def write(attributes)
11
12
  entry = build_entry(attributes)
12
13
  redis.rpush(BUFFER_KEY, entry.to_json)
14
+ redis.expire(BUFFER_KEY, BUFFER_TTL)
13
15
  entry
14
16
  end
15
17
 
@@ -21,6 +23,7 @@ module Railscope
21
23
  payload_updates: payload_updates
22
24
  }
23
25
  redis.rpush(UPDATES_KEY, update.to_json)
26
+ redis.expire(UPDATES_KEY, BUFFER_TTL)
24
27
  end
25
28
 
26
29
  # READ → Database (source of truth)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Railscope
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.4"
5
5
  end
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.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Phelipe Tussolini