rails_spotlight 0.5.1 → 0.5.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: ed7177dae7290c4108d3f82dbe6705449358e3b8805219b6780e93924547419e
4
- data.tar.gz: e4c0c873e82299d1637e43ef0c844c4444150677b24881ab570e508023f154e2
3
+ metadata.gz: 1a1028b180c27405b30fc503ed2885835496d23cbbe20043b83ca5f1ec0c0363
4
+ data.tar.gz: b5eb35ba72c5fc178a3bff967bfc6424af3548899dbaa121642716e2c91a96db
5
5
  SHA512:
6
- metadata.gz: 0d04a55bf3d180539e32f12dd183ee910e94a543ce81c416dd14b8d9d027ee271bbb099071c8c3b9e921303d14e3d4e4b026619e73ef44b17f762c4e045b54a4
7
- data.tar.gz: d0dd1ccb93add3a50cb773d9cc4019aac268363f673a688a8b1c6aea39b33eceea61eee51e8f656a49798e9213b8a2d1c76661a0b2ffe3d3d7301e681149b36d
6
+ metadata.gz: d2818a4e5b2564c846ae3cdf85765c95e8dbfb1b00088a9491fa21664603ad9255e477c8857dc00466750a4adbfd0daba97fb8bef877e4d7b8d9844c565bd98b
7
+ data.tar.gz: e69963faff65bfe0ef59c56cd6674ded77261c190d87713f82e0518bbd994910cc8184e284f18bf0e36411aea63bd59b9355b9864c6bec92cc6c98879141bcbb
data/README.md CHANGED
@@ -25,6 +25,89 @@ group :development do
25
25
  end
26
26
  ```
27
27
 
28
+ ## Using gems locally with Gemfile.local (No Git Pollution)
29
+
30
+ This guide shows you how to use any gem **locally in development** without modifying your app’s main `Gemfile`. It’s perfect for plugin development, debugging, or testing gems privately.
31
+
32
+ The setup also works with **`puma-dev`** out of the box and ensures your local changes stay out of Git.
33
+
34
+ ---
35
+
36
+ ### 1. Add a bundle Wrapper to Your Shell
37
+
38
+ Paste this function into your `~/.zshrc`, `~/.bashrc`, or `~/.profile`:
39
+
40
+ ```bash
41
+ bundle() {
42
+ local gemfile_local="Gemfile.local"
43
+ local lockfile_local="Gemfile.local.lock"
44
+ local lockfile_default="Gemfile.lock"
45
+
46
+ if [[ "$1" == "install" ]]; then
47
+ echo "[bundle] Running standard install with Gemfile"
48
+ command bundle install "${@:2}"
49
+
50
+ if [ -f "$gemfile_local" ]; then
51
+ echo "[bundle] Removing $lockfile_local if it exists"
52
+ rm -f "$lockfile_local"
53
+
54
+ echo "[bundle] Running install with Gemfile.local"
55
+ BUNDLE_GEMFILE="$gemfile_local" command bundle install "${@:2}"
56
+ fi
57
+ else
58
+ if [ -f "$gemfile_local" ]; then
59
+ BUNDLE_GEMFILE="$gemfile_local" command bundle "$@"
60
+ else
61
+ command bundle "$@"
62
+ fi
63
+ fi
64
+ }
65
+ ```
66
+
67
+ ### 2. Add the setup script
68
+
69
+ Paste this function into your `~/.zshrc`, `~/.bashrc`, or `~/.profile`:
70
+ ```bash
71
+ setup_local_gemfile() {
72
+ echo 'export BUNDLE_GEMFILE=Gemfile.local' > .pumaenv
73
+
74
+ cat > Gemfile.local <<'RUBY'
75
+ gemfile = File.join(File.dirname(__FILE__), 'Gemfile')
76
+ if File.readable?(gemfile)
77
+ puts "Loading #{gemfile}..." if $DEBUG
78
+ instance_eval(File.read(gemfile))
79
+ end
80
+ RUBY
81
+
82
+ {
83
+ echo .pumaenv
84
+ echo Gemfile.local
85
+ echo Gemfile.local.lock
86
+ } >> .git/info/exclude
87
+
88
+ echo "[setup] Local Gemfile environment ready!"
89
+ }
90
+ ```
91
+
92
+ ### 3. Go to your app folder modify Gemfile.local
93
+
94
+ Just add your gem like this
95
+
96
+ ```ruby
97
+ group :development do
98
+ gem 'rails_spotlight'
99
+ end
100
+ ```
101
+
102
+ ### 4. Install all and restart puma.dev if needed
103
+ ```bash
104
+ # if you didn't reload you bash/zsh just load rc or profile file here by source {my_file}
105
+ setup_local_gemfile
106
+ bundle install
107
+ puma-dev -stop
108
+ ```
109
+
110
+
28
111
  ## Configuration
29
112
 
30
113
  Generate configuration file by running:
@@ -51,16 +134,21 @@ file will be created in `config/rails_spotlight.yml`
51
134
  RAILS_SPOTLIGHT_PROJECT:
52
135
 
53
136
  # Prevent from processing and sending some data to the extension
54
- MIDDLEWARE_SKIPPED_PATHS: []
137
+ MIDDLEWARE_SKIPPED_PATHS: ['/rails']
55
138
  NOT_ENCODABLE_EVENT_VALUES:
56
139
  SKIP_RENDERED_IVARS: []
57
140
 
58
141
  # Features
142
+ LOGS_ENABLED: true
143
+ SIDEKIQ_LOGS_ENABLED: false
59
144
  FILE_MANAGER_ENABLED: true
60
145
  RUBOCOP_ENABLED: true
61
146
  SQL_CONSOLE_ENABLED: true
62
147
  IRB_CONSOLE_ENABLED: true
63
148
 
149
+ # Disable ActiveSupport subscriptions
150
+ DISABLE_ACTIVE_SUPPORT_SUBSCRIPTIONS: []
151
+
64
152
  # File manager configuration
65
153
  BLOCK_EDITING_FILES: false
66
154
  BLOCK_EDITING_FILES_OUTSIDE_OF_THE_PROJECT: true
@@ -148,7 +236,36 @@ You can add to your Initializers `config/initializers/rails_spotlight.rb` file w
148
236
 
149
237
  Known issue:
150
238
 
151
- Authentication error when using:
239
+ ### Stack too deep:
240
+
241
+ Usually happens when you have a lot of nested partials or locals containing complex objects.
242
+
243
+ Solution:
244
+
245
+ - first try to add `render_partial.action_view` and `render_template.action_view` to the `DISABLE_ACTIVE_SUPPORT_SUBSCRIPTIONS` in the config file.
246
+ - if it doesn't help, try to disable more of the subscriptions full list:
247
+ * sql.active_record
248
+ * sql.sequel
249
+ * render_partial.action_view
250
+ * render_template.action_view
251
+ * process_action.action_controller.exception
252
+ * process_action.action_controller
253
+ * cache_read.active_support
254
+ * cache_generate.active_support
255
+ * cache_fetch_hit.active_support
256
+ * cache_write.active_support
257
+ * cache_delete.active_support
258
+ * cache_exist?.active_support
259
+ * render_view.locals
260
+ - last you can disable `LOGS_ENABLED` or entire gem via `ENABLED` flag
261
+ - If you are sure what in you app can cause issue you can add it to the `NOT_ENCODABLE_EVENT_VALUES` list in the config file like this:
262
+ ```yaml
263
+ NOT_ENCODABLE_EVENT_VALUES:
264
+ Lookbook:
265
+ - Lookbook::Param
266
+ ```
267
+
268
+ ### Authentication error when using:
152
269
  - Specific authentication method and action cable
153
270
  - AUTO_MOUNT_CABLE: true
154
271
 
@@ -188,4 +305,3 @@ Gem is created for the Chrome extension [Rails Spotlight](https://chrome.google.
188
305
  ## License
189
306
 
190
307
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
191
-
@@ -21,6 +21,9 @@
21
21
  RUBOCOP_ENABLED: true
22
22
  SQL_CONSOLE_ENABLED: true
23
23
  IRB_CONSOLE_ENABLED: true
24
+
25
+ # Disable ActiveSupport subscriptions
26
+ DISABLE_ACTIVE_SUPPORT_SUBSCRIPTIONS: []
24
27
 
25
28
  # File manager configuration
26
29
  BLOCK_EDITING_FILES: false
@@ -25,7 +25,7 @@ module RailsSpotlight
25
25
  payload[:options][k] = payload.delete(k) unless k.in? CACHE_KEY_COLUMNS
26
26
  end
27
27
 
28
- callsite = ::RailsSpotlight::Utils.dev_callsite(caller)
28
+ callsite = payload[:original_callsite] || ::RailsSpotlight::Utils.dev_callsite(caller_locations)
29
29
  payload.merge!(callsite) if callsite
30
30
 
31
31
  Event.new(name, start, ending, transaction_id, payload)
@@ -46,7 +46,7 @@ module RailsSpotlight
46
46
 
47
47
  SQL_BLOCK = proc { |*args|
48
48
  _name, start, ending, transaction_id, payload = args
49
- callsite = ::RailsSpotlight::Utils.dev_callsite(caller)
49
+ callsite = payload[:original_callsite] || ::RailsSpotlight::Utils.dev_callsite(caller_locations)
50
50
  payload.merge!(callsite) if callsite
51
51
 
52
52
  Event.new(SQL_EVENT_NAME, start, ending, transaction_id, payload)
@@ -59,13 +59,30 @@ module RailsSpotlight
59
59
  Event.new(name, start, ending, transaction_id, payload)
60
60
  }
61
61
 
62
+ CONTROLLER_BLOCK = proc { |*args|
63
+ name, start, ending, transaction_id, payload = args
64
+ payload[:identifier] = ::RailsSpotlight::Utils.sub_source_path(payload[:identifier])
65
+ # Payload of redirect_to
66
+ # { status: 302, location: "http://localhost:3000/posts/new", request: <ActionDispatch::Request:0x00007ff1cb9bd7b8> }
67
+ # Payload of process_action
68
+ # { controller: "PostsController", action: "index", params: {"action" => "index", "controller" => "posts"}, format: :html, method: "GET", path: "/posts",
69
+ # headers: #<ActionDispatch::Http::Headers:0x0055a67a519b88>, request: #<ActionDispatch::Request:0x00007ff1cb9bd7b8>, response: #<ActionDispatch::Response:0x00007f8521841ec8>,
70
+ # status: 200, view_runtime: 46.848, db_runtime: 0.157
71
+ # }
72
+ # Payload of send_stream.action_controller
73
+ # { filename: "subscribers.csv", type: "text/csv", disposition: "attachment" }
74
+
75
+ Event.new(name, start, ending, transaction_id, payload)
76
+ }
77
+
78
+
62
79
  # Subscribe to all relevant events
63
80
  def self.subscribe
64
81
  # Skip RailsSpotlight subscriptions during migrations
65
82
  return if migrating?
66
83
 
67
84
  new
68
- # .subscribe('rsl.notification.log') # We do not publish events to this channel for now
85
+ .subscribe('rsl.notification.log') # We do not publish events to this channel for now
69
86
  .subscribe('sql.active_record', &SQL_BLOCK)
70
87
  .subscribe('sql.sequel', &SQL_BLOCK)
71
88
  .subscribe('render_partial.action_view', &VIEW_BLOCK)
@@ -83,6 +100,9 @@ module RailsSpotlight
83
100
  .subscribe('cache_delete.active_support', &CACHE_BLOCK)
84
101
  .subscribe('cache_exist?.active_support', &CACHE_BLOCK)
85
102
  .subscribe('render_view.locals', &VIEW_LOCALS_BLOCK)
103
+ # .subscribe('start_processing.action_controller', &CONTROLLER_BLOCK)
104
+ # .subscribe('redirect_to.action_controller', &CONTROLLER_BLOCK)
105
+ # .subscribe('send_file.action_controller', &CONTROLLER_BLOCK)
86
106
 
87
107
  # TODO: Consider adding these events
88
108
  # start_processing.action_controller: Triggered when a controller action starts processing a request.
@@ -96,12 +116,15 @@ module RailsSpotlight
96
116
 
97
117
  def self.migrating?
98
118
  defined?(Rake) && Rake.application.top_level_tasks.any? do |task|
99
- task.start_with?('db:migrate', 'db:schema:load', 'db:rollback')
119
+ task.start_with?('db:')
100
120
  end
101
121
  end
102
122
 
103
123
  def subscribe(event_name)
124
+ # Look for details about instrumentation => https://guides.rubyonrails.org/active_support_instrumentation.html#railties
104
125
  ActiveSupport::Notifications.subscribe(event_name) do |*args|
126
+ next if ::RailsSpotlight.config.disable_active_support_subscriptions.include?(event_name)
127
+
105
128
  event = block_given? ? yield(*args) : Event.new(*args)
106
129
  AppRequest.current.events << event if AppRequest.current
107
130
  end
@@ -16,6 +16,8 @@ module RailsSpotlight
16
16
  'ActionDispatch' => ['ActionDispatch::Request', 'ActionDispatch::Response']
17
17
  }.freeze
18
18
 
19
+ MAXIMUM_EVENT_VALUE_SIZE = 100000
20
+
19
21
  DEFAULT_DIRECTORY_INDEX_IGNORE = %w[
20
22
  /.git **/*.lock **/.DS_Store /app/assets/images/** /app/assets/fonts/** /app/assets/builds/** **/.keep
21
23
  ].freeze
@@ -41,13 +43,15 @@ module RailsSpotlight
41
43
  ].freeze
42
44
 
43
45
  attr_reader :enabled, :project_name, :source_path, :logger, :storage_path, :storage_pool_size, :middleware_skipped_paths,
44
- :not_encodable_event_values, :cable_mount_path, :logs_enabled,
46
+ :not_encodable_event_values, :cable_mount_path, :logs_enabled, :sidekiq_logs_enabled,
45
47
  :file_manager_enabled, :block_editing_files, :block_editing_files_outside_of_the_project, :skip_rendered_ivars,
46
48
  :directory_index_ignore, :rubocop_enabled, :rubocop_config_path, :use_cable, :default_rs_src,
47
- :form_js_execution_token, :sql_console_enabled, :irb_console_enabled, :data_access_token
49
+ :form_js_execution_token, :sql_console_enabled, :irb_console_enabled, :data_access_token,
50
+ :disable_active_support_subscriptions, :maximum_event_value_size
48
51
 
49
52
  def initialize(opts = {}) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
50
53
  @enabled = bool_val(:enabled, opts, default: false)
54
+ @sidekiq_logs_enabled = bool_val(:logs_enabled, opts, default: false)
51
55
  @logs_enabled = bool_val(:logs_enabled, opts, default: true)
52
56
  @project_name = opts[:project_name] || detect_project_name
53
57
  @source_path = opts[:source_path] || self.class.rails_root
@@ -65,7 +69,7 @@ module RailsSpotlight
65
69
  @block_editing_files_outside_of_the_project = bool_val(:block_editing_files_outside_of_the_project, opts, default: true)
66
70
  @file_manager_enabled = bool_val(:file_manager_enabled, opts, default: true)
67
71
  @skip_rendered_ivars = SKIP_RENDERED_IVARS + (opts[:skip_rendered_ivars] || []).map(&:to_sym)
68
- @directory_index_ignore = opts[:directory_index_ignore] || DEFAULT_DIRECTORY_INDEX_IGNORE
72
+ @directory_index_ignore = Array(opts[:directory_index_ignore] || DEFAULT_DIRECTORY_INDEX_IGNORE)
69
73
  @rubocop_enabled = bool_val(:rubocop_enabled, opts, default: true)
70
74
  @rubocop_config_path = opts[:rubocop_config_path] ? File.join(self.class.rails_root, opts[:rubocop_config_path]) : nil
71
75
  @cable_logs_enabled = bool_val(:cable_logs_enabled, opts)
@@ -74,6 +78,8 @@ module RailsSpotlight
74
78
  @sql_console_enabled = bool_val(:sql_console_enabled, opts, default: true)
75
79
  @irb_console_enabled = bool_val(:irb_console_enabled, opts, default: true)
76
80
  @data_access_token = opts[:data_access_token].present? ? opts[:data_access_token] : nil
81
+ @disable_active_support_subscriptions = Array(opts[:disable_active_support_subscriptions] || [])
82
+ @maximum_event_value_size = opts[:maximum_event_value_size] || MAXIMUM_EVENT_VALUE_SIZE
77
83
  end
78
84
 
79
85
  def cable_console_enabled = @cable_console_enabled && use_cable && action_cable_present?
@@ -82,8 +88,8 @@ module RailsSpotlight
82
88
  def auto_mount_cable = @auto_mount_cable && use_cable && action_cable_present?
83
89
  def action_cable_present? = defined?(ActionCable) && true
84
90
 
85
- alias enabled? enabled
86
91
  alias logs_enabled? logs_enabled
92
+ alias sidekiq_logs_enabled? sidekiq_logs_enabled
87
93
  alias cable_console_enabled? cable_console_enabled
88
94
  alias cable_logs_enabled? cable_logs_enabled
89
95
  alias use_cable? use_cable
@@ -94,6 +100,13 @@ module RailsSpotlight
94
100
  alias sql_console_enabled? sql_console_enabled
95
101
  alias irb_console_enabled? irb_console_enabled
96
102
 
103
+ # We do not recommend using Rails Spotlight in production. However, if you still want to do it, a data_access_token is required
104
+ def enabled?
105
+ return enabled unless Rails.env.production?
106
+
107
+ enabled && data_access_token.present?
108
+ end
109
+
97
110
  def self.load_config
98
111
  config_file = File.join(rails_root, 'config', 'rails_spotlight.yml')
99
112
  return new unless File.exist?(config_file)
@@ -6,14 +6,17 @@ require 'active_support/core_ext'
6
6
 
7
7
  module RailsSpotlight
8
8
  # Subclass of ActiveSupport Event that is JSON encodable
9
- #
10
9
  class Event < ActiveSupport::Notifications::Event
11
10
  NOT_JSON_ENCODABLE = 'Not JSON Encodable'
11
+ NON_SERIALIZABLE_CLASSES = [Proc, Binding, Method, UnboundMethod, Thread, IO, Class, Module].freeze
12
12
 
13
- attr_reader :duration
13
+ attr_reader :duration, :seen_not_encodable
14
14
 
15
15
  def initialize(name, start, ending, transaction_id, payload)
16
- super(name, start, ending, transaction_id, json_encodable(payload))
16
+ @seen_not_encodable = Set.new
17
+ raw_payload = json_encodable(payload)
18
+ raw_payload.merge!(raw_payload[:original_callsite]) if raw_payload[:original_callsite].present? && raw_payload[:filename].blank?
19
+ super(name, start, ending, transaction_id, raw_payload)
17
20
  @duration = 1000.0 * (ending - start)
18
21
  rescue # rubocop:disable Lint/RedundantCopDisableDirective, Style/RescueStandardError
19
22
  @duration = 0
@@ -36,30 +39,85 @@ module RailsSpotlight
36
39
 
37
40
  private
38
41
 
39
- def json_encodable(payload) # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
42
+ def json_encodable(payload)
40
43
  return {} unless payload.is_a?(Hash)
41
44
 
42
45
  transform_hash(payload, deep: true) do |hash, key, value|
43
- if value.class.to_s == 'ActionDispatch::Http::Headers'
44
- value = value.to_h.select { |k, _| k.upcase == k }
45
- elsif value.is_a?(Array) && defined?(ActiveRecord::Relation::QueryAttribute) && value.first.is_a?(ActiveRecord::Relation::QueryAttribute)
46
- value = value.map(&method(:map_relation_query_attribute))
47
- elsif !value.respond_to?(:to_json) || not_encodable?(value)
48
- value = NOT_JSON_ENCODABLE
49
- end
50
-
51
- begin
52
- value.to_json(methods: [:duration])
53
- new_value = value
54
- rescue # rubocop:disable Lint/RedundantCopDisableDirective, Style/RescueStandardError
55
- new_value = NOT_JSON_ENCODABLE
56
- end
57
- hash[key] = new_value # encode_value(value)
46
+ hash[key] = encode_json_safe_value(value)
58
47
  end.with_indifferent_access
59
- rescue # rubocop:disable Lint/RedundantCopDisableDirective, Style/RescueStandardError
48
+ rescue
60
49
  {}
61
50
  end
62
51
 
52
+ def not_json_encodable_and_seen(value)
53
+ seen_not_encodable.add(value.__id__)
54
+ NOT_JSON_ENCODABLE
55
+ end
56
+
57
+ def encode_json_safe_value(value)
58
+ return not_json_encodable_and_seen(value) if not_encodable?(value)
59
+ return NOT_JSON_ENCODABLE unless value.respond_to?(:to_json)
60
+
61
+ case value
62
+ when ActionDispatch::Http::Headers
63
+ value = value.to_h.select { |k, _| k.upcase == k }
64
+ when Array
65
+ value = value.map { |v| encode_json_safe_value(v) rescue NOT_JSON_ENCODABLE }
66
+ when Hash
67
+ value = transform_hash(value, deep: true) { |h, k, v| h[k] = encode_json_safe_value(v) }
68
+ when ActiveRecord::Relation::QueryAttribute
69
+ map_relation_query_attribute(value)
70
+ # when ActionController::Parameters
71
+ # value = value.to_unsafe_h rescue value.to_h
72
+ else
73
+ if defined?(ActiveRecord::Relation::QueryAttribute) && value.is_a?(ActiveRecord::Relation::QueryAttribute)
74
+ value = map_relation_query_attribute(value)
75
+ end
76
+ end
77
+
78
+ begin
79
+ value.to_json(methods: [:duration])
80
+ value
81
+ rescue
82
+ NOT_JSON_ENCODABLE
83
+ end
84
+ end
85
+
86
+ def transform_hash(original, options = {}, &block)
87
+ options[:safe_descent] ||= {}.compare_by_identity
88
+
89
+ return cached_transformation(original, options) if already_transformed?(original, options)
90
+
91
+ new_hash = {}
92
+ cache_transformation(original, new_hash, options)
93
+
94
+ original.each do |key, value|
95
+ value = deep_transform_value(value, options, &block) if options[:deep]
96
+ block.call(new_hash, key, value)
97
+ end
98
+
99
+ new_hash
100
+ end
101
+
102
+ def already_transformed?(object, options)
103
+ options[:safe_descent].key?(object)
104
+ end
105
+
106
+ def cached_transformation(object, options)
107
+ options[:safe_descent][object]
108
+ end
109
+
110
+ def cache_transformation(original, transformed, options)
111
+ options[:safe_descent][original] = transformed
112
+ end
113
+
114
+ def deep_transform_value(value, options, &block)
115
+ return value unless value.is_a?(Hash)
116
+ return cached_transformation(value, options) if already_transformed?(value, options)
117
+
118
+ transform_hash(value, options, &block)
119
+ end
120
+
63
121
  # ActiveRecord::Relation::QueryAttribute implementation changed in Rails 7.1 it getting binds need to be manually added
64
122
  def map_relation_query_attribute(attr)
65
123
  {
@@ -74,52 +132,34 @@ module RailsSpotlight
74
132
  end
75
133
 
76
134
  def not_encodable?(value)
135
+ return true if value_too_large?(value)
136
+ return true if NON_SERIALIZABLE_CLASSES.any? { |klass| value.is_a?(klass) }
137
+
77
138
  ::RailsSpotlight.config.not_encodable_event_values.any? do |module_name, class_names|
78
- next unless safe_constantize(module_name)
139
+ next true unless safe_constantize(module_name)
79
140
 
80
141
  class_names.any? do |class_name|
81
142
  klass = safe_constantize(class_name)
82
- next false unless klass
83
-
84
- value.is_a?(klass)
143
+ klass && value.is_a?(klass)
85
144
  end
86
145
  rescue # rubocop:disable Style/RescueStandardError
87
146
  true
88
147
  end
89
148
  end
90
149
 
150
+ def value_too_large?(value)
151
+ return false unless defined?(ObjectSpace) && defined?(ObjectSpace.memsize_of)
152
+ return false unless RailsSpotlight.config.maximum_event_value_size
153
+
154
+ (ObjectSpace.memsize_of(value) > RailsSpotlight.config.maximum_event_value_size)
155
+ rescue
156
+ false
157
+ end
158
+
91
159
  def safe_constantize(name)
92
160
  name.constantize
93
161
  rescue NameError
94
162
  nil
95
163
  end
96
-
97
- def transform_hash(original, options = {}, &block)
98
- options[:safe_descent] ||= {}.compare_by_identity
99
-
100
- # Check if the hash has already been transformed to prevent infinite recursion.
101
- return options[:safe_descent][original] if options[:safe_descent].key?(original)
102
-
103
- # Create a new hash to store the transformed values.
104
- new_hash = {}
105
- # Store the new hash in safe_descent using the original's object_id to mark it as processed.
106
- options[:safe_descent][original] = new_hash
107
-
108
- # Iterate over each key-value pair in the original hash.
109
- original.each do |key, value|
110
- # If deep transformation is required and the value is a hash,
111
- # recursively transform it, unless it's already been transformed.
112
- if options[:deep] && Hash === value # rubocop:disable Style/CaseEquality
113
- value = options[:safe_descent].fetch(value) do
114
- transform_hash(value, options, &block)
115
- end
116
- end
117
- # Apply the transformation block to the current key-value pair.
118
- block.call(new_hash, key, value)
119
- end
120
-
121
- # Return the transformed hash.
122
- new_hash
123
- end
124
164
  end
125
165
  end
@@ -62,7 +62,7 @@ module RailsSpotlight
62
62
 
63
63
  return unless message.is_a?(String)
64
64
 
65
- callsite = Utils.dev_callsite(caller.drop(1))
65
+ callsite = Utils.dev_callsite(caller_locations.drop(1))
66
66
  name = progname.is_a?(String) || progname.is_a?(Symbol) ? progname : nil
67
67
 
68
68
  AppRequest.current.events << Event.new('rsl.notification.log', 0, 0, 0, (callsite || {}).merge(message:, level: severity, progname: name)) if AppRequest.current
@@ -3,7 +3,7 @@
3
3
  module RailsSpotlight
4
4
  module Middlewares
5
5
  module SkipRequestPaths
6
- PATHS_TO_SKIP = %w[/__better_errors /__rails_spotlight /__meta_request /rails].freeze
6
+ PATHS_TO_SKIP = %w[/__better_errors /__rails_spotlight /__meta_request].freeze
7
7
 
8
8
  private
9
9
 
@@ -27,7 +27,7 @@ module RailsSpotlight
27
27
 
28
28
  attr_reader :app, :app_config
29
29
 
30
- def default_skip_paths = %w[/__better_errors /__meta_request /rails]
30
+ def default_skip_paths = %w[/__better_errors /__meta_request]
31
31
  end
32
32
  end
33
33
  end
@@ -0,0 +1,14 @@
1
+ module RailsSpotlight
2
+ module NotificationExtension
3
+ def instrument(name, payload = {}, &block)
4
+ if payload.is_a?(Hash) && !payload.key?(:original_callsite)
5
+ callsite = ::RailsSpotlight::Utils.dev_callsite(caller_locations)
6
+ if callsite && callsite[:filename].present?
7
+ payload[:original_callsite] = callsite
8
+ end
9
+ end
10
+
11
+ super(name, payload, &block)
12
+ end
13
+ end
14
+ end
@@ -16,7 +16,7 @@ module RailsSpotlight
16
16
  next unless ::RailsSpotlight.config.logs_enabled?
17
17
 
18
18
  Rails.logger&.extend(LogInterceptor)
19
- defined?(Sidekiq::Logger) && Sidekiq.logger&.extend(LogInterceptor)
19
+ ::RailsSpotlight.config.sidekiq_logs_enabled? && defined?(Sidekiq::Logger) && Sidekiq.logger&.extend(LogInterceptor)
20
20
  end
21
21
 
22
22
  initializer 'rails_spotlight.subscribe_to_notifications' do
@@ -4,16 +4,14 @@ module RailsSpotlight
4
4
  module Utils
5
5
  module_function
6
6
 
7
- def dev_callsite(caller)
8
- app_line = caller.detect { |c| c.start_with? RailsSpotlight.config.rails_root }
9
- return nil unless app_line
10
-
11
- _, filename, _, line, _, method = app_line.split(/^(.*?)(:(\d+))(:in `(.*)')?$/)
7
+ def dev_callsite(caller_locations)
8
+ loc = caller_locations.detect { |c| c.path.start_with? RailsSpotlight.config.rails_root }
9
+ return nil unless loc
12
10
 
13
11
  {
14
- filename: sub_source_path(filename),
15
- line: line.to_i,
16
- method:
12
+ filename: sub_source_path(loc.path),
13
+ line: loc.lineno,
14
+ method: loc.label
17
15
  }
18
16
  rescue # rubocop:disable Style/RescueStandardError, Lint/SuppressedException
19
17
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsSpotlight
4
- VERSION = '0.5.1'
4
+ VERSION = '0.5.3'
5
5
  end
@@ -1,16 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsSpotlight
4
- autoload :VERSION, 'rails_spotlight/version'
5
- autoload :Configuration, 'rails_spotlight/configuration'
6
- autoload :Storage, 'rails_spotlight/storage'
7
- autoload :Event, 'rails_spotlight/event'
8
- autoload :AppRequest, 'rails_spotlight/app_request'
9
- autoload :Middlewares, 'rails_spotlight/middlewares'
10
- autoload :LogInterceptor, 'rails_spotlight/log_interceptor'
11
- autoload :AppNotifications, 'rails_spotlight/app_notifications'
12
- autoload :Utils, 'rails_spotlight/utils'
13
- autoload :RenderViewReporter, 'rails_spotlight/render_view_reporter'
4
+ autoload :VERSION, 'rails_spotlight/version'
5
+ autoload :Configuration, 'rails_spotlight/configuration'
6
+ autoload :Storage, 'rails_spotlight/storage'
7
+ autoload :Event, 'rails_spotlight/event'
8
+ autoload :AppRequest, 'rails_spotlight/app_request'
9
+ autoload :Middlewares, 'rails_spotlight/middlewares'
10
+ autoload :LogInterceptor, 'rails_spotlight/log_interceptor'
11
+ autoload :NotificationExtension, 'rails_spotlight/notification_extension'
12
+ autoload :AppNotifications, 'rails_spotlight/app_notifications'
13
+ autoload :Utils, 'rails_spotlight/utils'
14
+ autoload :RenderViewReporter, 'rails_spotlight/render_view_reporter'
15
+
14
16
 
15
17
  class << self
16
18
  def config
data/lib/tasks/init.rake CHANGED
@@ -33,6 +33,9 @@ namespace :rails_spotlight do # rubocop:disable Metrics/BlockLength
33
33
  RUBOCOP_ENABLED: true
34
34
  SQL_CONSOLE_ENABLED: true
35
35
  IRB_CONSOLE_ENABLED: true
36
+
37
+ # Disable ActiveSupport subscriptions
38
+ DISABLE_ACTIVE_SUPPORT_SUBSCRIPTIONS: []
36
39
 
37
40
  # File manager configuration
38
41
  BLOCK_EDITING_FILES: false
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_spotlight
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pawel Niemczyk
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-04-14 00:00:00.000000000 Z
10
+ date: 2025-06-20 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rack-contrib
@@ -265,6 +265,7 @@ files:
265
265
  - lib/rails_spotlight/middlewares/main_request_handler.rb
266
266
  - lib/rails_spotlight/middlewares/request_completed.rb
267
267
  - lib/rails_spotlight/middlewares/request_handler.rb
268
+ - lib/rails_spotlight/notification_extension.rb
268
269
  - lib/rails_spotlight/rails_command_executor.rb
269
270
  - lib/rails_spotlight/railtie.rb
270
271
  - lib/rails_spotlight/render_view_reporter.rb