rails_spotlight 0.2.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -5
  3. data/lib/rails_spotlight/app_notifications.rb +3 -1
  4. data/lib/rails_spotlight/channels/handlers/live_console_handler.rb +64 -0
  5. data/lib/rails_spotlight/channels/handlers/logs_handler.rb +38 -0
  6. data/lib/rails_spotlight/channels/handlers.rb +29 -0
  7. data/lib/rails_spotlight/channels/silence_action_cable_broadcaster_logging.rb +26 -0
  8. data/lib/rails_spotlight/channels/spotlight_channel.rb +71 -0
  9. data/lib/rails_spotlight/channels.rb +2 -2
  10. data/lib/rails_spotlight/configuration.rb +21 -4
  11. data/lib/rails_spotlight/event.rb +2 -1
  12. data/lib/rails_spotlight/log_interceptor.rb +37 -29
  13. data/lib/rails_spotlight/middlewares/handlers/base_action_handler.rb +34 -2
  14. data/lib/rails_spotlight/middlewares/handlers/code_analysis_action_handler.rb +89 -0
  15. data/lib/rails_spotlight/middlewares/handlers/console_action_handler.rb +5 -23
  16. data/lib/rails_spotlight/middlewares/handlers/directory_index_action_handler.rb +112 -0
  17. data/lib/rails_spotlight/middlewares/handlers/file_action_handler.rb +18 -4
  18. data/lib/rails_spotlight/middlewares/handlers/meta_action_handler.rb +0 -1
  19. data/lib/rails_spotlight/middlewares/handlers/sql_action_handler.rb +6 -15
  20. data/lib/rails_spotlight/middlewares/handlers/verify_action_handler.rb +6 -2
  21. data/lib/rails_spotlight/middlewares/request_completed.rb +14 -10
  22. data/lib/rails_spotlight/middlewares/request_handler.rb +5 -1
  23. data/lib/rails_spotlight/railtie.rb +12 -4
  24. data/lib/rails_spotlight/render_view_reporter.rb +3 -1
  25. data/lib/rails_spotlight/utils.rb +2 -0
  26. data/lib/rails_spotlight/version.rb +1 -1
  27. data/lib/tasks/init.rake +75 -6
  28. metadata +9 -4
  29. data/lib/rails_spotlight/channels/live_console_channel.rb +0 -62
  30. data/lib/rails_spotlight/channels/request_completed_channel.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e36512b12e263c5853279d061fe2760ad7fff28c754c3362f8c848867845477
4
- data.tar.gz: cc4696e37e250be0413f0286503f8a63fa18a2c43353114720e9580fa4fc646e
3
+ metadata.gz: a5160149fb0234c097be7b6facb6373139c39fbba56bd83e70cb6d2cb94091ff
4
+ data.tar.gz: 5b0f8be5ccdac8e2bd47c9cb6150ee4e13578fbc9807ef8429a8bd4ce2255d06
5
5
  SHA512:
6
- metadata.gz: dd8e249fcfe8ced528c2f60225b00e8142e93989736d77090b9cc3ad0a9cf8c50dcbc85b9f76f196168ab1e6946f85d594c48935fe0751e8675d612f2d3fffa1
7
- data.tar.gz: 925e741a5c6b755deed002c3833da4487a24a530d025177373fa86fca3a5ad2ef84d1730497c7c2ad128a2c9bf526127166252793b056e587ed43ab6a585dbea
6
+ metadata.gz: 0aec7be167fc2047ccc941d66acc2eb6ebb9b04b2539614fa708b1a89feb5d380f8edda60d7391a612a3e72ff6d5c6ada0ade3f1c920c6c62724ba839cd254df
7
+ data.tar.gz: b4b99dacc64215beb48c5957decc907b45d1aba46a0f0faac1a4734502dbba4d51bb013bc0be8359247c50aa4c9581d585713f82457e81ae33cafa761b6c1841
data/README.md CHANGED
@@ -39,15 +39,21 @@ file will be created in `config/rails_spotlight.yml`
39
39
  MIDDLEWARE_SKIPPED_PATHS: []
40
40
  NOT_ENCODABLE_EVENT_VALUES:
41
41
  SKIP_RENDERED_IVARS: []
42
+ BLOCK_EDITING_FILES: false
43
+ BLOCK_EDITING_FILES_OUTSIDE_OF_THE_PROJECT: true
44
+ DIRECTORY_INDEX_IGNORE: ['/.git', '**/*.lock', '**/.DS_Store', '/app/assets/images/**', '/app/assets/fonts/**', '/app/assets/builds/**']
45
+ RUBOCOP_CONFIG_PATH: '.rubocop.yml'
42
46
  # Rest of the configuration is required for ActionCable. It will be disabled automatically in when ActionCable is not available.
43
- # LIVE_CONSOLE_ENABLED from version 0.2.3 do not require ActionCable to be enabled.
47
+ AUTO_MOUNT_ACTION_CABLE: false
48
+ ACTION_CABLE_MOUNT_PATH: /cable
49
+ # Required for all action cable features
50
+ USE_ACTION_CABLE: false
44
51
  LIVE_CONSOLE_ENABLED: false
45
52
  # Experimental feature.
46
53
  REQUEST_COMPLETED_BROADCAST_ENABLED: false
47
- AUTO_MOUNT_ACTION_CABLE: false
48
- ACTION_CABLE_MOUNT_PATH: /cable
49
- BLOCK_EDITING_FILES: false
50
- BLOCK_EDITING_FILES_OUTSIDE_OF_THE_PROJECT: true
54
+ LIVE_LOGS_ENABLED: false
55
+ DEFAULT_RS_SRC: default
56
+ FORM_JS_EXECUTION_TOKEN: <%= Digest::MD5.hexdigest(Rails.application.class.respond_to?(:module_parent_name) ? Rails.application.class.module_parent_name : Rails.application.class.parent_name)%>
51
57
  ```
52
58
 
53
59
  ## Additional metrics
@@ -60,6 +66,55 @@ To enable additional rendering metrics like local variables, instance variables,
60
66
  <% end %>
61
67
  ```
62
68
 
69
+ ## Experimental features
70
+ Live logs requires action cable to be enabled.
71
+
72
+ USE_ACTION_CABLE: true
73
+ LIVE_LOGS_ENABLED: true
74
+
75
+ When you want to use different sources for the cable logs you need to update `cable.yml` file.
76
+
77
+ ```yaml
78
+ development:
79
+ adapter: redis
80
+ url: redis://localhost:6379/1
81
+ ```
82
+ **Note:** Redis is required for live logs. so just add it to your Gemfile and run `bundle install`
83
+
84
+ modify yor `development.rb` file
85
+
86
+ ```ruby
87
+ config.action_cable.allowed_request_origins = ['chrome-extension://chjfnpmbgdbipfogflkhleaceacndaop' ]
88
+ aeblfdkhhhdcdjpifhhbdiojplfjncoa
89
+ chjfnpmbgdbipfogflkhleaceacndaop
90
+ ```
91
+
92
+ Now just run your servers with environment variable RS_SRC=my_source_name
93
+ like `RS_SRC=sidekiq bundle exec sidekiq -C config/sidekiq.yml`
94
+ or `RS_SRC=web bundle exec puma -C config/puma.rb`
95
+
96
+ ---
97
+
98
+ Forms (Filling forms with scenarios)
99
+
100
+ To be able use full potential of the forms you need to generate partial that helps with js code injection.
101
+
102
+ Just use `rails rails_spotlight:inject_js_partial` and inject partial to your layout file as instructed in past generation message.
103
+ setup your `FORM_JS_EXECUTION_TOKEN` or use pregnerated one in your extension settings.
104
+
105
+ # Advanced log configuration
106
+ You can add to your Initializers `config/initializers/rails_spotlight.rb` file with the additional configuration for the logger.
107
+
108
+ ```ruby
109
+ unless Rails.env.production?
110
+ # Publish logs to the spotlight (only when implementation is based on the Rails.logger)
111
+ defined?(Logger) && Logger&.extend(RailsSpotlight::LogInterceptor)
112
+ # Publish Hutch logs to the spotlight
113
+ defined?(Hutch::Logging) && Hutch::Logging.logger&.extend(RailsSpotlight::LogInterceptor)
114
+ end
115
+ ```
116
+
117
+
63
118
  ## Troubleshooting
64
119
 
65
120
  Known issue:
@@ -87,7 +87,9 @@ module RailsSpotlight
87
87
  # send_file.action_controller: Triggered when a file is sent as a response.
88
88
  # redirect_to.action_controller: Triggered when a redirect response is sent.
89
89
  # halted_callback.action_controller: Triggered when a filter or callback halts the request.
90
- # render_collection.action_view: This event is triggered when a collection is rendered using a partial. It includes details about the collection being rendered, such as the collection name and the partial being used to render each item.
90
+ # render_collection.action_view: This event is triggered when a collection is rendered using a partial.
91
+ # It includes details about the collection being rendered,
92
+ # such as the collection name and the partial being used to render each item.
91
93
  end
92
94
 
93
95
  def subscribe(event_name)
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../rails_command_executor'
4
+
5
+ module RailsSpotlight
6
+ module Channels
7
+ module Handlers
8
+ class LiveConsoleHandler
9
+ TYPE = 'console'
10
+
11
+ def initialize(data)
12
+ @data = data
13
+ end
14
+
15
+ attr_reader :data
16
+
17
+ def call
18
+ return unless ::RailsSpotlight.config.live_console_enabled?
19
+ return unless data['type'] == TYPE
20
+
21
+ command = data['command']
22
+ inspect_types = data['inspect_types']
23
+ for_project = Array(data['project'])
24
+
25
+ raise_project_mismatch_error!(for_project) if for_project.present? && !for_project.include?(project)
26
+
27
+ execute_command(command, inspect_types)
28
+ end
29
+
30
+ def executor
31
+ @executor ||= ::RailsSpotlight::RailsCommandExecutor.new
32
+ end
33
+
34
+ def raise_project_mismatch_error!(for_project)
35
+ raise ::RailsSpotlight::Channels::Handlers::ResponseError.new(
36
+ "Project mismatch, The command was intended for the #{for_project} project. This is #{project} project",
37
+ code: :project_mismatch
38
+ )
39
+ end
40
+
41
+ def execute_command(command, inspect_types)
42
+ RailsSpotlight.config.logger && RailsSpotlight.config.logger.info("Executing command: #{command}") # rubocop:disable Style/SafeNavigation
43
+
44
+ executor.execute(command)
45
+ if executor.execution_successful?
46
+ {
47
+ payload: { result: executor.result_as_json(inspect_types: inspect_types) },
48
+ }
49
+ else
50
+ {
51
+ payload: { failed: executor.result_as_json }
52
+ }
53
+ end
54
+ rescue => e # rubocop:disable Style/RescueStandardError
55
+ { error: e.message }
56
+ end
57
+
58
+ def project
59
+ ::RailsSpotlight.config.project_name
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsSpotlight
4
+ module Channels
5
+ module Handlers
6
+ class LogsHandler
7
+ TYPE = 'logs'
8
+
9
+ def initialize(data)
10
+ @data = data
11
+ end
12
+
13
+ attr_reader :data
14
+
15
+ def call
16
+ return unless ::RailsSpotlight.config.live_console_enabled?
17
+ return unless data['type'] == TYPE
18
+
19
+ for_project = Array(data['project'])
20
+ raise_project_mismatch_error!(for_project) if for_project.present? && !for_project.include?(project)
21
+
22
+ { payload: data[:payload] }
23
+ end
24
+
25
+ def raise_project_mismatch_error!(for_project)
26
+ raise ::RailsSpotlight::Channels::Handlers::ResponseError.new(
27
+ "Project mismatch, Logs from #{for_project} project cannot be forwarded in #{project} project",
28
+ code: :project_mismatch
29
+ )
30
+ end
31
+
32
+ def project
33
+ ::RailsSpotlight.config.project_name
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'handlers/live_console_handler'
4
+ require_relative 'handlers/logs_handler'
5
+
6
+ module RailsSpotlight
7
+ module Channels
8
+ module Handlers
9
+ ResponseError = Class.new(StandardError) do
10
+ def initialize(message, code: :error)
11
+ @code = code
12
+ super(message)
13
+ end
14
+
15
+ attr_reader :code
16
+ end
17
+ TYPES = [LiveConsoleHandler::TYPE, LogsHandler::TYPE].freeze
18
+
19
+ def self.handle(data)
20
+ case data['type']
21
+ when LiveConsoleHandler::TYPE then LiveConsoleHandler.new(data).call
22
+ # when LogsHandler::TYPE then LogsHandler.new(data).call
23
+ else
24
+ nil
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsSpotlight
4
+ module Channels
5
+ module SilenceActionCableBroadcasterLogging
6
+ refine ActionCable::Server::Broadcasting::Broadcaster do
7
+ def broadcast(message)
8
+ if broadcasting == RailsSpotlight::Channels::SpotlightChannel::SPOTLIGHT_CHANNEL
9
+ original_logger = server.logger
10
+ begin
11
+ # Replace the logger with a no-op logger to silence the log
12
+ server.logger = ActiveSupport::Logger.new(nil)
13
+ super(message)
14
+ ensure
15
+ # Restore the original logger after broadcasting
16
+ server.logger = original_logger
17
+ end
18
+ else
19
+ super(message)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'handlers'
4
+ # require_relative 'silence_action_cable_broadcaster_logging'
5
+
6
+ module RailsSpotlight
7
+ module Channels
8
+ class SpotlightChannel < ActionCable::Channel::Base
9
+ def self.broadcast(attrs = {})
10
+ broadcasting = ::RailsSpotlight::Channels::SPOTLIGHT_CHANNEL
11
+ message = {
12
+ type: attrs[:type],
13
+ code: attrs[:code] || 'ok',
14
+ project: ::RailsSpotlight.config.project_name,
15
+ version: ::RailsSpotlight::VERSION,
16
+ payload: attrs[:payload] || {}
17
+ }
18
+ coder = ::ActiveSupport::JSON
19
+ encoded = coder ? coder.encode(message) : message
20
+ ActionCable.server.pubsub.broadcast(broadcasting, encoded)
21
+ # We do not use the following code because it is triggering logs and can cause an infinite loop
22
+ # ActionCable.server.broadcast(
23
+ # ::RailsSpotlight::Channels::SPOTLIGHT_CHANNEL,
24
+ # {
25
+ # type: attrs[:type],
26
+ # code: attrs[:code] || 'ok',
27
+ # project: ::RailsSpotlight.config.project_name,
28
+ # version: ::RailsSpotlight::VERSION,
29
+ # payload: attrs[:payload] || {}
30
+ # }
31
+ # )
32
+ rescue StandardError => e
33
+ RailsSpotlight.config.logger.fatal("#{e.message}\n #{e.backtrace.join("\n ")}")
34
+ end
35
+
36
+ def subscribed
37
+ stream_from ::RailsSpotlight::Channels::SPOTLIGHT_CHANNEL
38
+ publish({ message: "Your #{project} project is now connected to the spotlight channel.", code: :connected, type: :info })
39
+ end
40
+
41
+ def unsubscribed
42
+ # Any cleanup needed when channel is unsubscribed
43
+ end
44
+
45
+ def receive(data)
46
+ return publish({ message: 'Unknown type of request', code: :unknown_type, type: :error }) unless Handlers::TYPES.include?(data['type'])
47
+
48
+ result = Handlers.handle(data)
49
+ publish({ payload: result[:payload], code: result[:code] || :ok, type: data['type'] }) if result[:payload]
50
+ rescue ::RailsSpotlight::Channels::Handlers::ResponseError => e
51
+ publish({ message: e.message, code: e.code, type: :error })
52
+ end
53
+
54
+ private
55
+
56
+ def publish(data)
57
+ connection.transmit identifier: @identifier, message: data.merge(project: project, version: version)
58
+ # we do not use transmit because it is triggering logs and can cause an infinite loop
59
+ # transmit(data.merge(project: project, version: version))
60
+ end
61
+
62
+ def project
63
+ ::RailsSpotlight.config.project_name
64
+ end
65
+
66
+ def version
67
+ ::RailsSpotlight::VERSION
68
+ end
69
+ end
70
+ end
71
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module RailsSpotlight
4
4
  module Channels
5
- autoload(:LiveConsoleChannel, 'rails_spotlight/channels/live_console_channel') if defined?(ActionCable)
6
- autoload(:RequestCompletedChannel, 'rails_spotlight/channels/request_completed_channel') if defined?(ActionCable)
5
+ SPOTLIGHT_CHANNEL = 'RailsSpotlight::Channels::SpotlightChannel'.freeze
6
+ autoload(:SpotlightChannel, 'rails_spotlight/channels/spotlight_channel') if defined?(ActionCable)
7
7
  end
8
8
  end
@@ -10,6 +10,10 @@ module RailsSpotlight
10
10
  'ActionDispatch' => ['ActionDispatch::Request', 'ActionDispatch::Response']
11
11
  }.freeze
12
12
 
13
+ DEFAULT_DIRECTORY_INDEX_IGNORE = %w[
14
+ /.git **/*.lock **/.DS_Store /app/assets/images/** /app/assets/fonts/** /app/assets/builds/** **/.keep
15
+ ].freeze
16
+
13
17
  SKIP_RENDERED_IVARS = %i[
14
18
  @_routes
15
19
  @_config
@@ -32,7 +36,9 @@ module RailsSpotlight
32
36
 
33
37
  attr_reader :project_name, :source_path, :logger, :storage_path, :storage_pool_size, :middleware_skipped_paths,
34
38
  :not_encodable_event_values, :action_cable_mount_path,
35
- :block_editing_files, :block_editing_files_outside_of_the_project, :skip_rendered_ivars
39
+ :block_editing_files, :block_editing_files_outside_of_the_project, :skip_rendered_ivars,
40
+ :directory_index_ignore, :rubocop_config_path, :use_action_cable, :default_rs_src,
41
+ :form_js_execution_token
36
42
 
37
43
  def initialize(opts = {}) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
38
44
  @project_name = opts[:project_name] || detect_project_name
@@ -44,27 +50,38 @@ module RailsSpotlight
44
50
  @request_completed_broadcast_enabled = opts[:request_completed_broadcast_enabled].nil? ? false : true?(opts[:request_completed_broadcast_enabled])
45
51
  @middleware_skipped_paths = opts[:middleware_skipped_paths] || []
46
52
  @not_encodable_event_values = DEFAULT_NOT_ENCODABLE_EVENT_VALUES.merge(opts[:not_encodable_event_values] || {})
53
+ @use_action_cable = opts[:use_action_cable].nil? ? false : true?(opts[:use_action_cable])
47
54
  @auto_mount_action_cable = opts[:auto_mount_action_cable].nil? ? false : true?(opts[:auto_mount_action_cable])
48
55
  @action_cable_mount_path = opts[:action_cable_mount_path] || '/cable'
49
56
  @block_editing_files = opts[:block_editing_files].nil? ? false : true?(opts[:block_editing_files])
50
57
  @block_editing_files_outside_of_the_project = opts[:block_editing_files_outside_of_the_project].nil? ? true : true?(opts[:block_editing_files_outside_of_the_project])
51
58
  @skip_rendered_ivars = SKIP_RENDERED_IVARS + (opts[:skip_rendered_ivars] || []).map(&:to_sym)
59
+ @directory_index_ignore = opts[:directory_index_ignore] || DEFAULT_DIRECTORY_INDEX_IGNORE
60
+ @rubocop_config_path = opts[:rubocop_config_path] ? File.join(self.class.rails_root, opts[:rubocop_config_path]) : nil
61
+ @live_logs_enabled = opts[:live_logs_enabled].nil? ? false : true?(opts[:live_logs_enabled])
62
+ @default_rs_src = opts[:default_rs_src] || 'default'
63
+ @form_js_execution_token = opts[:form_js_execution_token] || Digest::MD5.hexdigest(detect_project_name)
52
64
  end
53
65
 
54
66
  def live_console_enabled
55
- @live_console_enabled && action_cable_present?
67
+ @live_console_enabled && use_action_cable && action_cable_present?
68
+ end
69
+
70
+ def live_logs_enabled
71
+ @live_logs_enabled && use_action_cable && action_cable_present?
56
72
  end
57
73
 
58
74
  alias live_console_enabled? live_console_enabled
75
+ alias live_logs_enabled? live_logs_enabled
59
76
 
60
77
  def request_completed_broadcast_enabled
61
- @request_completed_broadcast_enabled && action_cable_present?
78
+ @request_completed_broadcast_enabled && use_action_cable && action_cable_present?
62
79
  end
63
80
 
64
81
  alias request_completed_broadcast_enabled? request_completed_broadcast_enabled
65
82
 
66
83
  def auto_mount_action_cable
67
- @auto_mount_action_cable && action_cable_present?
84
+ @auto_mount_action_cable && use_action_cable && action_cable_present?
68
85
  end
69
86
 
70
87
  alias auto_mount_action_cable? auto_mount_action_cable
@@ -57,8 +57,9 @@ module RailsSpotlight
57
57
  def not_encodable?(value)
58
58
  ::RailsSpotlight.config.not_encodable_event_values.any? do |module_name, class_names|
59
59
  next unless defined?(module_name.constantize)
60
-
61
60
  class_names.any? { |class_name| value.is_a?(class_name.constantize) }
61
+ rescue => e
62
+ puts "Error in not_encodable? method: #{e.message}"
62
63
  end
63
64
  end
64
65
 
@@ -1,45 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'channels'
4
+
3
5
  module RailsSpotlight
4
6
  module LogInterceptor
5
- def debug(message = nil, *args)
6
- push_event(:debug, message)
7
- super
8
- end
7
+ SEVERITY = %w[debug info warn error fatal unknown].freeze
8
+ SEVERITY_MAP = { 0 => 'debug', 1 => 'info', 2 => 'warn', 3 => 'error', 4 => 'fatal', 5 => 'unknown' }.freeze
9
+
10
+ def add(severity, message = nil, progname = nil)
11
+ severity ||= 5
12
+ return true if @logdev.nil? || severity < level
13
+
14
+ progname = @progname if progname.nil?
15
+
16
+ if message.nil?
17
+ if block_given?
18
+ message = yield
19
+ else
20
+ message = progname
21
+ progname = @progname
22
+ end
23
+ end
24
+ return true if _skip_logging?(message)
9
25
 
10
- def info(message = nil, *args)
11
- push_event(:info, message)
12
- super
26
+ _push_event(SEVERITY_MAP[severity], message, progname)
27
+ super(severity, message, progname) if defined?(super)
28
+ true
13
29
  end
14
30
 
15
- def warn(message = nil, *args)
16
- push_event(:warn, message)
17
- super
18
- end
31
+ private
19
32
 
20
- def error(message = nil, *args)
21
- push_event(:error, message)
22
- super
23
- end
33
+ def _skip_logging?(message)
34
+ return false unless ::RailsSpotlight.config.live_console_enabled?
35
+ return false unless message.is_a?(String)
24
36
 
25
- def fatal(message = nil, *args)
26
- push_event(:fatal, message)
27
- super
37
+ message.include?(::RailsSpotlight::Channels::SPOTLIGHT_CHANNEL)
28
38
  end
29
39
 
30
- def unknown(message = nil, *args)
31
- push_event(:unknown, message)
32
- super
33
- end
40
+ def _push_event(level, message, progname = nil)
41
+ callsite = Utils.dev_callsite(caller.drop(1))
42
+ name = progname.is_a?(String) || progname.is_a?(Symbol) ? progname : nil
43
+ AppRequest.current.events << Event.new('rsl.notification.log', 0, 0, 0, callsite.merge(message: message, level: level, progname: name)) if AppRequest.current && callsite
34
44
 
35
- private
45
+ return unless ::RailsSpotlight.config.live_console_enabled?
46
+ return if message.blank?
36
47
 
37
- def push_event(level, message)
38
- callsite = AppRequest.current && Utils.dev_callsite(caller.drop(1))
39
- if callsite
40
- payload = callsite.merge(message: message, level: level)
41
- AppRequest.current.events << Event.new('rsl.notification.log', 0, 0, 0, payload)
42
- end
48
+ id = AppRequest.current ? AppRequest.current.id : nil
49
+ payload = (callsite || {}).merge(msg: message, src: ENV['RS_SRC'] || ::RailsSpotlight.config.default_rs_src, l: level, dt: Time.now.to_f, id: id, pg: name)
50
+ ::RailsSpotlight::Channels::SpotlightChannel.broadcast(type: 'logs', payload: payload)
43
51
  rescue StandardError => e
44
52
  RailsSpotlight.config.logger.fatal("#{e.message}\n #{e.backtrace.join("\n ")}")
45
53
  end
@@ -7,6 +7,7 @@ module RailsSpotlight
7
7
  IncorrectResponseContentType = Class.new(StandardError)
8
8
  NotFound = Class.new(StandardError)
9
9
  UnprocessableEntity = Class.new(StandardError)
10
+ Forbidden = Class.new(StandardError)
10
11
 
11
12
  def initialize(request_id, request, content_type)
12
13
  @request_id = request_id
@@ -17,12 +18,15 @@ module RailsSpotlight
17
18
  attr_reader :request_id, :request, :content_type
18
19
 
19
20
  def call
21
+ validate_project! unless skip_project_validation?
20
22
  execute
21
23
  response
22
24
  rescue NotFound => e
23
25
  not_found_response(e.message)
24
26
  rescue UnprocessableEntity => e
25
27
  unprocessed_response(e.message)
28
+ rescue Forbidden => e
29
+ forbidden_response(e.message)
26
30
  rescue => e # rubocop:disable Style/RescueStandardError
27
31
  internal_server_error_response(e.message)
28
32
  end
@@ -31,6 +35,10 @@ module RailsSpotlight
31
35
 
32
36
  attr_writer :status, :headers
33
37
 
38
+ def skip_project_validation?
39
+ false
40
+ end
41
+
34
42
  def headers
35
43
  @headers ||= {}
36
44
  end
@@ -57,6 +65,10 @@ module RailsSpotlight
57
65
  raise 'Invalid JSON'
58
66
  end
59
67
 
68
+ def body_fetch(*args)
69
+ json_request_body.fetch(*args)
70
+ end
71
+
60
72
  def internal_server_error_response(message)
61
73
  response(500, message_to_body(message))
62
74
  end
@@ -65,6 +77,10 @@ module RailsSpotlight
65
77
  response(422, message_to_body(message))
66
78
  end
67
79
 
80
+ def forbidden_response(message)
81
+ response(403, message_to_body(message))
82
+ end
83
+
68
84
  def not_found_response(message)
69
85
  response(404, message_to_body(message))
70
86
  end
@@ -76,7 +92,8 @@ module RailsSpotlight
76
92
  def response_headers(headers = {})
77
93
  {
78
94
  'Content-Type' => content_type == :json ? 'application/json; charset=utf-8' : 'text/plain; charset=utf-8',
79
- 'X-Rails-Spotlight' => '1.0.0',
95
+ 'X-Rails-Spotlight' => ::RailsSpotlight::VERSION,
96
+ 'X-Rails-Spotlight-Project' => ::RailsSpotlight.config.project_name,
80
97
  'X-Request-Id' => request_id
81
98
  }.merge(headers)
82
99
  end
@@ -85,10 +102,25 @@ module RailsSpotlight
85
102
  body = if overridden_body.present?
86
103
  content_type == :json ? overridden_body.to_json : overridden_body
87
104
  else
88
- content_type == :json ? json_response_body.to_json : text_response_body
105
+ content_type == :json ? json_response_body.merge({ project: ::RailsSpotlight.config.project_name }).to_json : text_response_body
89
106
  end
90
107
  [overridden_status.present? ? overridden_status : status, response_headers(headers), [body]]
91
108
  end
109
+
110
+ def request_spotlight_version
111
+ @request_spotlight_version ||= request.get_header('HTTP_X_RAILS_SPOTLIGHT')
112
+ end
113
+
114
+ def request_for_projects
115
+ @request_for_projects ||= (request.get_header('HTTP_X_FOR_PROJECTS') || '').split(',').map(&:strip)
116
+ end
117
+
118
+ def validate_project!
119
+ return if request_for_projects.blank?
120
+ return if request_for_projects.include?(::RailsSpotlight.config.project_name)
121
+
122
+ raise Forbidden, "Check your settings the current request is not allowed to be executed on the #{::RailsSpotlight.config.project_name} project"
123
+ end
92
124
  end
93
125
  end
94
126
  end