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.
- checksums.yaml +4 -4
- data/README.md +60 -5
- data/lib/rails_spotlight/app_notifications.rb +3 -1
- data/lib/rails_spotlight/channels/handlers/live_console_handler.rb +64 -0
- data/lib/rails_spotlight/channels/handlers/logs_handler.rb +38 -0
- data/lib/rails_spotlight/channels/handlers.rb +29 -0
- data/lib/rails_spotlight/channels/silence_action_cable_broadcaster_logging.rb +26 -0
- data/lib/rails_spotlight/channels/spotlight_channel.rb +71 -0
- data/lib/rails_spotlight/channels.rb +2 -2
- data/lib/rails_spotlight/configuration.rb +21 -4
- data/lib/rails_spotlight/event.rb +2 -1
- data/lib/rails_spotlight/log_interceptor.rb +37 -29
- data/lib/rails_spotlight/middlewares/handlers/base_action_handler.rb +34 -2
- data/lib/rails_spotlight/middlewares/handlers/code_analysis_action_handler.rb +89 -0
- data/lib/rails_spotlight/middlewares/handlers/console_action_handler.rb +5 -23
- data/lib/rails_spotlight/middlewares/handlers/directory_index_action_handler.rb +112 -0
- data/lib/rails_spotlight/middlewares/handlers/file_action_handler.rb +18 -4
- data/lib/rails_spotlight/middlewares/handlers/meta_action_handler.rb +0 -1
- data/lib/rails_spotlight/middlewares/handlers/sql_action_handler.rb +6 -15
- data/lib/rails_spotlight/middlewares/handlers/verify_action_handler.rb +6 -2
- data/lib/rails_spotlight/middlewares/request_completed.rb +14 -10
- data/lib/rails_spotlight/middlewares/request_handler.rb +5 -1
- data/lib/rails_spotlight/railtie.rb +12 -4
- data/lib/rails_spotlight/render_view_reporter.rb +3 -1
- data/lib/rails_spotlight/utils.rb +2 -0
- data/lib/rails_spotlight/version.rb +1 -1
- data/lib/tasks/init.rake +75 -6
- metadata +9 -4
- data/lib/rails_spotlight/channels/live_console_channel.rb +0 -62
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a5160149fb0234c097be7b6facb6373139c39fbba56bd83e70cb6d2cb94091ff
|
4
|
+
data.tar.gz: 5b0f8be5ccdac8e2bd47c9cb6150ee4e13578fbc9807ef8429a8bd4ce2255d06
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
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.
|
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
|
-
|
6
|
-
autoload(:
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
26
|
+
_push_event(SEVERITY_MAP[severity], message, progname)
|
27
|
+
super(severity, message, progname) if defined?(super)
|
28
|
+
true
|
13
29
|
end
|
14
30
|
|
15
|
-
|
16
|
-
push_event(:warn, message)
|
17
|
-
super
|
18
|
-
end
|
31
|
+
private
|
19
32
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
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
|
-
|
26
|
-
push_event(:fatal, message)
|
27
|
-
super
|
37
|
+
message.include?(::RailsSpotlight::Channels::SPOTLIGHT_CHANNEL)
|
28
38
|
end
|
29
39
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
45
|
+
return unless ::RailsSpotlight.config.live_console_enabled?
|
46
|
+
return if message.blank?
|
36
47
|
|
37
|
-
|
38
|
-
|
39
|
-
|
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' =>
|
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
|