railscope 0.1.9 → 0.1.11

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: 259570e86e958b91b577337b9b1dd6f57f667f72999589a3976b9a86a3450c78
4
- data.tar.gz: bbf6d0de9fdce6901383c8d60c39c07310a3da0d64593904d55a85412deba2b1
3
+ metadata.gz: c7b44de114284ee8955d48c35c4966b45e8b798b1624d6a8abefb2212b9e4cd5
4
+ data.tar.gz: 44ba0061f1987733edf5a5dde93ce591fc9eb2ff63f3aae59854051313b59d84
5
5
  SHA512:
6
- metadata.gz: 2d358d6d57809e3b6d30bebfab973060d8c3086a929c8d47dd3fc230be5ad72f0cca7ed84f0431466b80f3b5d863e8f1d37c0d26eb6df14301d7ef307fcc05a5
7
- data.tar.gz: 41b79ac66a21145f5ca9fe18a07a6706bf06751a87eb26bd1739a816526d7e851c43d784146cb2bf6391ce47e7e73bbfe91443e883e8f868d45a039e30b66dec
6
+ metadata.gz: 3b054e8e101d09dcd88c954eaf82452c9e18629398eb2270d2eddcd89769562eac8325a0c55c77505d5a1f2ecf5f2c7fa644d65e9eec318045c37832dbc3d92f
7
+ data.tar.gz: 9c7feb08613c99ef5fb206170dedb9e841a846e6d7077f4e06c8fa0673f19764f0ea5acff74da590e4879f7e78cb72213b9ceea35e76990f6b38e1aebabef26f
data/README.md CHANGED
@@ -65,6 +65,15 @@ Railscope.configure do |config|
65
65
  # Paths to ignore (defaults: /railscope, /assets, /packs, /cable)
66
66
  config.add_ignore_paths("/health", "/ping", "/metrics")
67
67
 
68
+ # Jobs to ignore (class names or regex patterns)
69
+ config.add_ignore_jobs("SomeFrequentJob", "Turbo::Streams::.*")
70
+
71
+ # Rake tasks to ignore (task names or regex patterns)
72
+ config.add_ignore_commands("db:.*", "assets:.*", "tmp:.*")
73
+
74
+ # Use an existing Redis connection (useful for SSL/connection sharing)
75
+ # config.redis = $redis
76
+
68
77
  # Additional sensitive keys to filter
69
78
  config.add_sensitive_keys(:cpf, :ssn, :bank_account)
70
79
  end
@@ -208,6 +217,51 @@ Railscope::PurgeJob.perform_now
208
217
  Railscope::PurgeJob.perform_later
209
218
  ```
210
219
 
220
+ ## Filtering Entries
221
+
222
+ ### Ignore Paths
223
+
224
+ Requests matching these path prefixes will not be recorded:
225
+
226
+ ```ruby
227
+ # Defaults: /railscope, /assets, /packs, /cable
228
+ config.add_ignore_paths("/health", "/ping", "/metrics", "/up")
229
+ ```
230
+
231
+ ### Ignore Jobs
232
+
233
+ Background jobs matching these patterns will not be recorded (enqueue, perform, or exceptions). Accepts exact class names or regex patterns:
234
+
235
+ ```ruby
236
+ # Exact match
237
+ config.add_ignore_jobs("HeartbeatJob")
238
+
239
+ # Regex patterns
240
+ config.add_ignore_jobs("SolidQueue::.*", "Turbo::Streams::.*", "ActionMailbox::.*")
241
+ ```
242
+
243
+ ### Ignore Commands
244
+
245
+ Rake tasks matching these patterns will not be instrumented. Accepts exact task names or regex patterns:
246
+
247
+ ```ruby
248
+ # Exact match
249
+ config.add_ignore_commands("db:migrate")
250
+
251
+ # Regex patterns — ignore entire namespaces
252
+ config.add_ignore_commands("db:.*", "assets:.*", "tmp:.*", "log:.*")
253
+ ```
254
+
255
+ ### Custom Redis Connection
256
+
257
+ By default, Railscope creates its own Redis connection from `RAILSCOPE_REDIS_URL` or `REDIS_URL`. If your app already has a configured Redis instance (e.g., with SSL on Heroku), you can pass it directly:
258
+
259
+ ```ruby
260
+ config.redis = $redis
261
+ ```
262
+
263
+ This avoids SSL certificate issues and shares the existing connection configuration.
264
+
211
265
  ## Filtered Parameters
212
266
 
213
267
  Railscope automatically filters sensitive data:
@@ -279,8 +333,11 @@ Railscope is designed to have minimal impact:
279
333
  For high-traffic production environments, consider:
280
334
  - Using `:redis` backend for lower request latency
281
335
  - Shorter retention periods
282
- - Adding high-traffic paths to ignore list
336
+ - Ignoring noisy paths (`/health`, `/ping`, polling endpoints)
337
+ - Ignoring high-frequency jobs (`SolidQueue::.*`, `Turbo::Streams::.*`)
338
+ - Ignoring routine rake tasks (`db:.*`, `assets:.*`)
283
339
  - Running purge job more frequently
340
+ - Using `config.redis = $redis` to share the app's existing Redis connection
284
341
 
285
342
  ## License
286
343
 
@@ -49,6 +49,28 @@ Railscope.configure do |config|
49
49
  #
50
50
  # config.add_ignore_paths("/health", "/ping", "/metrics")
51
51
 
52
+ # Ignored Jobs
53
+ # ------------
54
+ # Jobs matching these patterns will not be recorded.
55
+ # Accepts class names or regex patterns.
56
+ #
57
+ # config.add_ignore_jobs("SomeFrequentJob", "Turbo::Streams::.*")
58
+
59
+ # Ignored Commands
60
+ # ----------------
61
+ # Rake tasks matching these patterns will not be recorded.
62
+ # Accepts task names or regex patterns.
63
+ #
64
+ # config.add_ignore_commands("db:.*", "assets:.*", "tmp:.*")
65
+
66
+ # Redis Connection
67
+ # ----------------
68
+ # By default, Railscope creates its own Redis connection from
69
+ # RAILSCOPE_REDIS_URL or REDIS_URL. You can pass an existing
70
+ # Redis instance instead (useful for SSL/connection sharing):
71
+ #
72
+ # config.redis = $redis
73
+
52
74
  # Sensitive Keys
53
75
  # --------------
54
76
  # Additional parameter names to filter from payloads.
@@ -87,5 +87,30 @@ module Railscope
87
87
  end
88
88
 
89
89
  delegate :empty?, to: :@store
90
+
91
+ # Conditional recording: buffer entries until a trigger fires
92
+
93
+ def pending_entries
94
+ self[:pending_entries] ||= []
95
+ end
96
+
97
+ def triggered?
98
+ self[:triggered] == true
99
+ end
100
+
101
+ def trigger!
102
+ self[:triggered] = true
103
+ end
104
+
105
+ def buffer_entry(entry_data)
106
+ pending_entries << entry_data
107
+ end
108
+
109
+ def flush_pending!
110
+ pending_entries.each do |entry_data|
111
+ Railscope.storage.write(**entry_data)
112
+ end
113
+ pending_entries.clear
114
+ end
90
115
  end
91
116
  end
@@ -18,17 +18,20 @@ module Railscope
18
18
  # Capture response body for recording
19
19
  context = Context.current
20
20
  if context[:recording]
21
- # Read body from env (where Rails stores the response)
22
- body_content = extract_body_from_env(env)
23
-
24
- context_data = {
25
- batch_id: context.batch_id,
26
- env: env,
27
- headers: headers
28
- }
29
-
30
- # Update entry with response data
31
- update_entry_async(context_data, body_content)
21
+ # In conditional mode, skip persistence if trigger never fired
22
+ unless Railscope.conditional_recording? && !context.triggered?
23
+ # Read body from env (where Rails stores the response)
24
+ body_content = extract_body_from_env(env)
25
+
26
+ context_data = {
27
+ batch_id: context.batch_id,
28
+ env: env,
29
+ headers: headers
30
+ }
31
+
32
+ # Update entry with response data
33
+ update_entry_async(context_data, body_content)
34
+ end
32
35
  end
33
36
 
34
37
  [status, headers, response]
@@ -29,7 +29,7 @@ module Railscope
29
29
 
30
30
  filtered_payload = Railscope.filter(payload.merge(context_payload))
31
31
 
32
- Railscope.storage.write(
32
+ entry_data = {
33
33
  entry_type: entry_type,
34
34
  batch_id: context.batch_id,
35
35
  family_hash: family_hash,
@@ -37,7 +37,13 @@ module Railscope
37
37
  payload: filtered_payload,
38
38
  tags: (tags + context_tags).uniq,
39
39
  occurred_at: Time.current
40
- )
40
+ }
41
+
42
+ if Railscope.conditional_recording? && !context.triggered?
43
+ context.buffer_entry(entry_data)
44
+ else
45
+ Railscope.storage.write(**entry_data)
46
+ end
41
47
  end
42
48
 
43
49
  # Generate a family hash for grouping similar entries
@@ -23,6 +23,7 @@ module Railscope
23
23
 
24
24
  def instrument_task(task)
25
25
  return if task.name.start_with?("railscope:")
26
+ return if Railscope.ignore_command?(task.name)
26
27
  return if instrumented_tasks.include?(task.name)
27
28
 
28
29
  instrumented_tasks << task.name
@@ -76,7 +76,12 @@ module Railscope
76
76
  # Also create a separate exception entry if job failed
77
77
  create_exception_entry!(job, exception_object) if exception_object
78
78
 
79
- # Clear context after job completes
79
+ # In conditional mode: flush if triggered, otherwise entries are discarded with context
80
+ if Railscope.conditional_recording? && context.triggered?
81
+ # Response update for jobs is already in the entry payload, nothing extra needed
82
+ end
83
+
84
+ # Clear context after job completes (discards any unflushed buffer)
80
85
  Railscope::Context.clear!
81
86
  rescue StandardError => e
82
87
  Rails.logger.error("[Railscope] Failed to record job perform: #{e.message}")
@@ -199,7 +204,7 @@ module Railscope
199
204
  end
200
205
 
201
206
  def ignore_job?(job)
202
- job.class.name.start_with?("Railscope::")
207
+ job.class.name.start_with?("Railscope::") || Railscope.ignore_job?(job.class.name)
203
208
  end
204
209
 
205
210
  def setup_job_context(job)
@@ -42,6 +42,7 @@ module Railscope
42
42
  model_name = model.class.name
43
43
  return if model_name.nil?
44
44
  return if IGNORED_MODEL_PREFIXES.any? { |prefix| model_name.start_with?(prefix) }
45
+ return unless Railscope.should_track_model?(model_name)
45
46
 
46
47
  create_entry!(
47
48
  entry_type: "model",
@@ -50,6 +51,13 @@ module Railscope
50
51
  family_hash: build_family_hash(action, model),
51
52
  should_display_on_index: true
52
53
  )
54
+
55
+ # Conditional recording: flush buffered entries when trigger matches
56
+ if Railscope.conditional_recording? && !context.triggered? && Railscope.matches_trigger?(model_name, action)
57
+ context.trigger!
58
+ context.flush_pending!
59
+ end
60
+
53
61
  Rails.logger.debug("[Railscope] ModelSubscriber - entry created for #{model_name}")
54
62
  rescue StandardError => e
55
63
  Rails.logger.error("[Railscope] Failed to record model event: #{e.class}: #{e.message}")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Railscope
4
- VERSION = "0.1.9"
4
+ VERSION = "0.1.11"
5
5
  end
data/lib/railscope.rb CHANGED
@@ -27,7 +27,8 @@ module Railscope
27
27
  STORAGE_REDIS = :redis
28
28
 
29
29
  class << self
30
- attr_writer :retention_days, :redis, :storage_backend, :ignore_paths
30
+ attr_writer :retention_days, :redis, :storage_backend, :ignore_paths, :ignore_jobs, :ignore_commands,
31
+ :ignore_models
31
32
  attr_accessor :authenticate_with
32
33
 
33
34
  def enabled=(value)
@@ -99,6 +100,67 @@ module Railscope
99
100
  @ignore_paths = ignore_paths.concat(paths.flatten).uniq
100
101
  end
101
102
 
103
+ def ignore_jobs
104
+ @ignore_jobs ||= []
105
+ end
106
+
107
+ def add_ignore_jobs(*job_classes)
108
+ @ignore_jobs = ignore_jobs.concat(job_classes.flatten.map(&:to_s)).uniq
109
+ end
110
+
111
+ def ignore_job?(job_class_name)
112
+ ignore_jobs.any? { |pattern| job_class_name.match?(pattern) }
113
+ end
114
+
115
+ def ignore_commands
116
+ @ignore_commands ||= []
117
+ end
118
+
119
+ def add_ignore_commands(*commands)
120
+ @ignore_commands = ignore_commands.concat(commands.flatten.map(&:to_s)).uniq
121
+ end
122
+
123
+ def ignore_command?(command_name)
124
+ ignore_commands.any? { |pattern| command_name.match?(pattern) }
125
+ end
126
+
127
+ # Model filtering (blocklist)
128
+
129
+ def ignore_models
130
+ @ignore_models ||= []
131
+ end
132
+
133
+ def add_ignore_models(*model_names)
134
+ @ignore_models = ignore_models.concat(model_names.flatten.map(&:to_s)).uniq
135
+ end
136
+
137
+ def should_track_model?(model_name)
138
+ return false if model_name.nil?
139
+
140
+ ignore_models.none? { |pattern| model_name.match?(pattern) }
141
+ end
142
+
143
+ # Conditional recording: only persist a batch when a watched model event fires
144
+
145
+ def watch_triggers
146
+ @watch_triggers ||= []
147
+ end
148
+
149
+ def add_watch_trigger(model_name, on:)
150
+ actions = Array(on).map(&:to_s)
151
+ @watch_triggers = watch_triggers.push({ model: model_name.to_s, on: actions }).uniq
152
+ end
153
+
154
+ def conditional_recording?
155
+ watch_triggers.any?
156
+ end
157
+
158
+ def matches_trigger?(model_name, action)
159
+ watch_triggers.any? do |trigger|
160
+ model_name.match?(trigger[:model]) && trigger[:on].include?(action.to_s)
161
+ end
162
+ end
163
+
102
164
  def context
103
165
  Context.current
104
166
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: railscope
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Phelipe Tussolini
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
10
+ date: 2026-02-18 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -155,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
155
155
  - !ruby/object:Gem::Version
156
156
  version: '0'
157
157
  requirements: []
158
- rubygems_version: 3.6.9
158
+ rubygems_version: 3.6.2
159
159
  specification_version: 4
160
160
  summary: A debug assistant for Rails applications inspired by Laravel Telescope
161
161
  test_files: []