callstacking-rails 0.1.2 → 0.1.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +621 -0
  3. data/README.md +49 -6
  4. data/app/assets/config/callstacking_rails_manifest.js +1 -0
  5. data/app/assets/stylesheets/{checkpoint → callstacking}/rails/application.css +0 -0
  6. data/app/controllers/{checkpoint → callstacking}/rails/application_controller.rb +1 -1
  7. data/app/helpers/{checkpoint → callstacking}/rails/application_helper.rb +1 -1
  8. data/app/jobs/{checkpoint → callstacking}/rails/application_job.rb +1 -1
  9. data/app/mailers/{checkpoint → callstacking}/rails/application_mailer.rb +1 -1
  10. data/app/models/{checkpoint → callstacking}/rails/application_record.rb +1 -1
  11. data/config/routes.rb +1 -1
  12. data/exe/callstacking-rails +32 -0
  13. data/lib/{checkpoint → callstacking}/rails/client/authenticate.rb +2 -1
  14. data/lib/{checkpoint → callstacking}/rails/client/base.rb +3 -3
  15. data/lib/{checkpoint → callstacking}/rails/client/trace.rb +3 -1
  16. data/lib/callstacking/rails/engine.rb +49 -0
  17. data/lib/{checkpoint → callstacking}/rails/env.rb +3 -1
  18. data/lib/callstacking/rails/instrument.rb +100 -0
  19. data/lib/callstacking/rails/loader.rb +29 -0
  20. data/lib/{checkpoint → callstacking}/rails/settings.rb +13 -5
  21. data/lib/callstacking/rails/setup.rb +115 -0
  22. data/lib/callstacking/rails/spans.rb +53 -0
  23. data/lib/callstacking/rails/trace.rb +126 -0
  24. data/lib/{checkpoint → callstacking}/rails/traces_helper.rb +20 -7
  25. data/lib/callstacking/rails/version.rb +5 -0
  26. data/lib/callstacking/rails.rb +7 -0
  27. metadata +32 -40
  28. data/MIT-LICENSE +0 -20
  29. data/app/assets/config/checkpoint_rails_manifest.js +0 -1
  30. data/app/controllers/checkpoint/rails/traces_controller.rb +0 -6
  31. data/lib/checkpoint/rails/engine.rb +0 -34
  32. data/lib/checkpoint/rails/setup.rb +0 -93
  33. data/lib/checkpoint/rails/traceable.rb +0 -210
  34. data/lib/checkpoint/rails/version.rb +0 -5
  35. data/lib/checkpoint/rails.rb +0 -7
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Checkpoint::Rails
1
+ # Callstacking::Rails
2
2
 
3
3
  Callstacking is a rolling, checkpoint debugger for Rails. It records all of the critical method calls within your app, along with their important context (param/argument/return/local variable values).
4
4
 
@@ -17,13 +17,13 @@ For method returns ↳, the final values of the local variables will be listed w
17
17
 
18
18
  Subsequent calls within a method are visibly nested.
19
19
 
20
- Checkpoint is a Rails engine that you mount within your Rails app.
20
+ Callstacking is a Rails engine that you mount within your Rails app.
21
21
 
22
22
  Here's a sample debugging sessions recorded from a Jumpstart Rails based app I've been working on. This is a request for the main page ( https://smartk.id/ ).
23
23
 
24
24
  ![image](https://user-images.githubusercontent.com/4600/190882432-58092e38-7ee2-4138-b13a-f45ff2b09227.png)
25
25
 
26
- Checkpoint Rails records all of the critical method calls within your app, along with their important context (param/argument/return/local variable values).
26
+ Callstacking Rails records all of the critical method calls within your app, along with their important context (param/argument/return/local variable values).
27
27
 
28
28
  All in a rolling panel, so that you can debug your call chains from any point in the stack.
29
29
 
@@ -42,11 +42,54 @@ And then execute:
42
42
  ```bash
43
43
  $ bundle
44
44
  ```
45
+
46
+ Register an account at Callstacking.com
47
+ ```bash
48
+ callstacking-rails register
49
+ ```
50
+
51
+ Authenticate to your newly created account.
52
+
53
+ ```bash
54
+ callstacking-rails setup
55
+ ```
56
+
57
+ You're now ready to start tracing.
45
58
 
46
59
  ## Usage
47
- When you open a page for your app, once the page has rendered, you will see an arrow on the right hand side.
60
+ Usage:
61
+
62
+ > callstacking-rails enable
63
+
64
+ Enables the callstacking tracing.
65
+
66
+ > callstacking-rails disable
67
+
68
+ Disables the callstacking tracing.
69
+
70
+ > callstacking-rails register
71
+
72
+ Opens a browser window to register as a callstacking.com user.
73
+
74
+ > callstacking-rails setup
75
+
76
+ Interactively prompts you for your callstacking.com username/password.
77
+ Stores auth details in `~/.callstacking`.
78
+
79
+ You can have multiple environments.
80
+ The default is `development`.
81
+
82
+ The `development:` section in the `~/.callstacking` config contains your credentials.
83
+
84
+ By setting the RAILS_ENV environment you can maintain multiple settings.
85
+
86
+ Questions? Create an issue: https://github.com/callstacking/callstacking-rails/issues
87
+
88
+
89
+ ## Trace Output
90
+ When you open a page for your app, once the page has rendered, you will see an `<<` arrow on the right hand side.
48
91
 
49
- Click the arrow, and observe the full callstack context.
92
+ Click the arrows, and observe the full callstack context.
50
93
 
51
94
  ## License
52
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
95
+ The gem is available as open source under the terms of the [GPLv3 License](https://www.gnu.org/licenses/gpl-3.0.en.html).
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/callstacking/rails .css
@@ -1,4 +1,4 @@
1
- module Checkpoint
1
+ module Callstacking
2
2
  module Rails
3
3
  class ApplicationController < ActionController::Base
4
4
  end
@@ -1,4 +1,4 @@
1
- module Checkpoint
1
+ module Callstacking
2
2
  module Rails
3
3
  module ApplicationHelper
4
4
  end
@@ -1,4 +1,4 @@
1
- module Checkpoint
1
+ module Callstacking
2
2
  module Rails
3
3
  class ApplicationJob < ActiveJob::Base
4
4
  end
@@ -1,4 +1,4 @@
1
- module Checkpoint
1
+ module Callstacking
2
2
  module Rails
3
3
  class ApplicationMailer < ActionMailer::Base
4
4
  default from: "from@example.com"
@@ -1,4 +1,4 @@
1
- module Checkpoint
1
+ module Callstacking
2
2
  module Rails
3
3
  class ApplicationRecord < ActiveRecord::Base
4
4
  self.abstract_class = true
data/config/routes.rb CHANGED
@@ -1,4 +1,4 @@
1
- Checkpoint::Rails::Engine.routes.draw do
1
+ Callstacking::Rails::Engine.routes.draw do
2
2
  resources :traces
3
3
  root to: "traces#index"
4
4
  end
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "callstacking/rails/setup"
5
+ require "callstacking/rails/settings"
6
+
7
+ include Callstacking::Rails::Settings
8
+
9
+ action = ARGV[0]&.downcase&.strip
10
+
11
+ if action.nil?
12
+ Callstacking::Rails::Setup.instructions
13
+ exit!(1)
14
+ end
15
+
16
+ read_settings
17
+
18
+ case action
19
+ when 'register'
20
+ `open #{settings.url || Callstacking::Rails::Settings::PRODUCTION_URL}/users/sign_up`
21
+
22
+ when 'setup'
23
+ Callstacking::Rails::Setup.new.start
24
+
25
+ when 'enable'
26
+ Callstacking::Rails::Setup.new.enable_disable
27
+ puts "Callstacking tracing enabled (#{Callstacking::Rails::Env.environment})"
28
+
29
+ when 'disable'
30
+ Callstacking::Rails::Setup.new.enable_disable(enabled: false)
31
+ puts "Callstacking tracing disabled (#{Callstacking::Rails::Env.environment})"
32
+ end
@@ -1,6 +1,7 @@
1
1
  require 'json'
2
+ require "callstacking/rails/client/base"
2
3
 
3
- module Checkpoint
4
+ module Callstacking
4
5
  module Rails
5
6
  module Client
6
7
  class Error < StandardError; end
@@ -1,14 +1,14 @@
1
1
  require 'faraday'
2
2
  require 'faraday/follow_redirects'
3
- require "checkpoint/rails/settings"
3
+ require "callstacking/rails/settings"
4
4
 
5
- module Checkpoint
5
+ module Callstacking
6
6
  module Rails
7
7
  module Client
8
8
  class Error < StandardError; end
9
9
 
10
10
  class Base
11
- include Checkpoint::Rails::Settings
11
+ include Callstacking::Rails::Settings
12
12
 
13
13
  def initialize
14
14
  read_settings
@@ -1,4 +1,6 @@
1
- module Checkpoint
1
+ require "callstacking/rails/client/base"
2
+
3
+ module Callstacking
2
4
  module Rails
3
5
  module Client
4
6
  class Trace < Base
@@ -0,0 +1,49 @@
1
+ require "rails"
2
+ require "callstacking/rails/trace"
3
+ require "callstacking/rails/instrument"
4
+ require 'callstacking/rails/spans'
5
+ require "callstacking/rails/setup"
6
+ require "callstacking/rails/settings"
7
+ require "callstacking/rails/loader"
8
+ require "callstacking/rails/client/base"
9
+ require "callstacking/rails/client/authenticate"
10
+ require "callstacking/rails/client/trace"
11
+ require "callstacking/rails/traces_helper"
12
+
13
+ module Callstacking
14
+ module Rails
15
+ class Engine < ::Rails::Engine
16
+ include Settings
17
+ isolate_namespace Callstacking::Rails
18
+
19
+ read_settings
20
+
21
+ spans = Spans.new
22
+ trace = Trace.new(spans)
23
+
24
+ initializer "engine_name.assets.precompile" do |app|
25
+ app.config.assets.precompile << "checkpoint_rails_manifest.js"
26
+ end
27
+
28
+ initializer 'local_helper.action_controller' do
29
+ ActiveSupport.on_load :action_controller do
30
+ helper Callstacking::Rails::TracesHelper
31
+ include Callstacking::Rails::TracesHelper
32
+ end
33
+
34
+ Callstacking::Rails::Loader.new.on_load(spans) if enabled?
35
+ end
36
+
37
+ initializer :append_before_action do
38
+ ActionController::Base.send :after_action, :inject_hud
39
+ end
40
+
41
+ if enabled?
42
+ puts "Callstacking enabled (#{Callstacking::Rails::Env.environment})"
43
+ trace.tracing
44
+ else
45
+ puts "Callstacking disabled (#{Callstacking::Rails::Env.environment})"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,4 +1,6 @@
1
- module Checkpoint
1
+ require "active_support/inflector"
2
+
3
+ module Callstacking
2
4
  module Rails
3
5
  class Env
4
6
  DEFAULT_ENVIRONMENT = "development"
@@ -0,0 +1,100 @@
1
+ require 'rails'
2
+
3
+ # https://stackoverflow.com/q/52932516
4
+ module Callstacking
5
+ module Rails
6
+ class Instrument
7
+ attr_accessor :spans, :klass
8
+ attr_reader :root
9
+
10
+ def initialize(spans, klass)
11
+ @spans = spans
12
+ @klass = klass
13
+ @root = Regexp.new(::Rails.root.to_s)
14
+ end
15
+
16
+ def instrument_method(method_name, application_level: true)
17
+ method_path = (klass.instance_method(method_name).source_location.first rescue nil) ||
18
+ (klass.method(method_name).source_location.first rescue nil)
19
+
20
+ # Application level method definitions
21
+ return unless method_path =~ root if application_level
22
+
23
+ tmp_module = find_or_initialize_module
24
+
25
+ return if tmp_module.instance_methods.include?(method_name) ||
26
+ tmp_module.singleton_methods.include?(method_name)
27
+
28
+ tmp_module.define_method(method_name) do |*args, &block|
29
+ method_name = __method__
30
+
31
+ path = method(__method__).super_method.source_location.first
32
+ line_no = method(__method__).super_method.source_location.last
33
+
34
+ p, l = caller.find { |c| c.to_s =~ /#{::Rails.root.to_s}/}&.split(':')
35
+
36
+ spans = tmp_module.instance_variable_get(:@spans)
37
+ klass = tmp_module.instance_variable_get(:@klass)
38
+
39
+ arguments = Callstacking::Rails::Instrument.arguments_for(method(__method__).super_method, args)
40
+
41
+ spans.call_entry(klass, method_name, arguments, p || path, l || line_no)
42
+ return_val = super(*args, &block)
43
+ spans.call_return(klass, method_name, p || path, l || line_no, return_val)
44
+
45
+ return_val
46
+ end
47
+ end
48
+
49
+ def find_or_initialize_module
50
+ module_name = "#{klass.name.gsub('::', '')}Span"
51
+ module_index = klass.ancestors.map(&:to_s).index(module_name)
52
+
53
+ unless module_index
54
+ new_module = Object.const_set(module_name, Module.new)
55
+
56
+ new_module.instance_variable_set("@klass", klass)
57
+ new_module.instance_variable_set("@spans", spans)
58
+
59
+ klass.prepend new_module
60
+ klass.singleton_class.prepend new_module if klass.class == Module
61
+
62
+ return find_or_initialize_module
63
+ end
64
+
65
+ klass.ancestors[module_index]
66
+ end
67
+
68
+ def instrument_klass(application_level: true)
69
+ relevant = all_methods - filtered
70
+ relevant.each { |method| instrument_method(method, application_level: application_level) }
71
+ end
72
+
73
+ def self.arguments_for(m, args)
74
+ param_names = m.parameters&.map(&:last)
75
+ return {} if param_names.nil?
76
+
77
+ param_names.map.with_index do |param, index|
78
+ next if [:&, :*, :**].include?(param)
79
+ [param, args[index]]
80
+ end.compact.to_h
81
+ end
82
+
83
+ private
84
+
85
+ def all_methods
86
+ @all_methods ||= (klass.instance_methods +
87
+ klass.private_instance_methods(false) +
88
+ klass.protected_instance_methods(false) +
89
+ klass.methods +
90
+ klass.singleton_methods).uniq
91
+ end
92
+
93
+ def filtered
94
+ @filtered ||= (Object.instance_methods + Object.private_instance_methods +
95
+ Object.protected_instance_methods + Object.methods(false)).uniq
96
+ end
97
+
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,29 @@
1
+ require "rails"
2
+
3
+ module Callstacking
4
+ module Rails
5
+ class Loader
6
+ attr_accessor :loader, :root, :once
7
+ def initialize
8
+ @root = Regexp.new(::Rails.root.to_s)
9
+ end
10
+
11
+ def on_load(spans)
12
+ trace = TracePoint.new(:end) do |tp|
13
+ klass = tp.self
14
+ path = tp.path
15
+
16
+ Instrument.new(spans, klass).instrument_klass if path =~ root
17
+ end
18
+
19
+ trace.enable
20
+
21
+ Instrument.new(spans, ActionView::PartialRenderer).instrument_method(:render,
22
+ application_level: false)
23
+
24
+ Instrument.new(spans, ActionView::TemplateRenderer).instrument_method( :render,
25
+ application_level: false)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,8 +1,8 @@
1
1
  require "active_support/concern"
2
2
  require "active_support/core_ext/class/attribute_accessors"
3
- require "checkpoint/rails/env"
3
+ require "callstacking/rails/env"
4
4
 
5
- module Checkpoint
5
+ module Callstacking
6
6
  module Rails
7
7
  module Settings
8
8
  extend ActiveSupport::Concern
@@ -11,7 +11,7 @@ module Checkpoint
11
11
  attr_accessor :settings
12
12
  end
13
13
 
14
- SETTINGS_FILE = "#{Dir.home}/.callstacking-rails"
14
+ SETTINGS_FILE = "#{Dir.home}/.callstacking"
15
15
  PRODUCTION_URL = "https://callstacking.com"
16
16
 
17
17
  def url
@@ -30,8 +30,16 @@ module Checkpoint
30
30
  File.write(SETTINGS_FILE, new_settings.to_yaml)
31
31
  end
32
32
 
33
+ def enabled?
34
+ settings[:enabled]
35
+ end
36
+
37
+ def disabled?
38
+ !enabled?
39
+ end
40
+
33
41
  def read_settings
34
- @@settings = @settings = complete_settings.dig(::Checkpoint::Rails::Env.environment, :settings)
42
+ @@settings = @settings = complete_settings.dig(::Callstacking::Rails::Env.environment, :settings)
35
43
  rescue StandardError => e
36
44
  puts e.full_message
37
45
  puts e.backtrace.join("\n")
@@ -39,7 +47,7 @@ module Checkpoint
39
47
  end
40
48
 
41
49
  def complete_settings
42
- YAML.load(File.read(Checkpoint::Rails::Client::Base::SETTINGS_FILE)) rescue {}
50
+ YAML.load(File.read(SETTINGS_FILE)) rescue {}
43
51
  end
44
52
  end
45
53
  end
@@ -0,0 +1,115 @@
1
+ require 'yaml'
2
+ require "callstacking/rails/settings"
3
+ require "callstacking/rails/client/authenticate"
4
+ require "callstacking/rails/env"
5
+
6
+ module Callstacking
7
+ module Rails
8
+ class Setup
9
+ include ::Callstacking::Rails::Settings
10
+ extend ::Callstacking::Rails::Settings
11
+
12
+ attr_accessor :client
13
+
14
+ def initialize
15
+ read_settings
16
+ end
17
+
18
+ def client
19
+ @client = Callstacking::Rails::Client::Authenticate.new
20
+ end
21
+
22
+ def start
23
+ email = prompt("Enter email:")
24
+ password = prompt("Enter password:")
25
+
26
+ url = if Callstacking::Rails::Env.production? && ENV['CHECKPOINT_RAILS_LOCAL_TEST'].nil?
27
+ PRODUCTION_URL
28
+ else
29
+ prompt("Enter URL for #{Callstacking::Rails::Env.environment} API calls [#{PRODUCTION_URL}]:") || PRODUCTION_URL
30
+ end
31
+
32
+ save(email, password, url)
33
+
34
+ puts "Authentication successful."
35
+ puts "Settings saved to #{SETTINGS_FILE}"
36
+ rescue StandardError => e
37
+ puts "Problem authenticating: #{e.message}"
38
+ end
39
+
40
+ def enable_disable(enabled: true)
41
+ settings[:enabled] = enabled
42
+
43
+ props = { Callstacking::Rails::Env.environment => {
44
+ settings: settings
45
+ } }
46
+
47
+ write_settings(complete_settings.merge(props))
48
+ end
49
+
50
+ def prompt(label)
51
+ puts label
52
+ value = STDIN.gets.chomp
53
+ puts
54
+
55
+ return nil if value == ''
56
+ value
57
+ end
58
+
59
+ def token(email, password)
60
+ client.login(email, password)
61
+ end
62
+
63
+ def save(email, password, url)
64
+ props = { auth_token: '',
65
+ url: url,
66
+ enabled: true
67
+ }
68
+
69
+ props = { Callstacking::Rails::Env.environment => {
70
+ settings: props
71
+ } }
72
+
73
+ write_settings(complete_settings.merge(props))
74
+
75
+ props[Callstacking::Rails::Env.environment][:settings][:auth_token] = token(email, password)
76
+
77
+ write_settings(complete_settings.merge(props))
78
+
79
+ read_settings
80
+ end
81
+
82
+ def self.instructions
83
+ read_settings
84
+ puts "loading environment #{Callstacking::Rails::Env.environment}"
85
+ puts
86
+ puts "Usage: "
87
+ puts
88
+ puts " > callstacking-rails enable"
89
+ puts
90
+ puts " Enables the callstacking tracing."
91
+ puts
92
+ puts " > callstacking-rails disable"
93
+ puts
94
+ puts " Disables the callstacking tracing."
95
+ puts
96
+ puts " > callstacking-rails register"
97
+ puts
98
+ puts " Opens a browser window to register as a callstacking.com user."
99
+ puts
100
+ puts " > callstacking-rails setup"
101
+ puts
102
+ puts " Interactively prompts you for your callstacking.com username/password."
103
+ puts " Stores auth details in #{SETTINGS_FILE}"
104
+ puts
105
+ puts " You can have multiple environments."
106
+ puts " The default is #{Callstacking::Rails::Env::DEFAULT_ENVIRONMENT}."
107
+ puts
108
+ puts " The #{Callstacking::Rails::Env.environment}: section in the #{SETTINGS_FILE} contains your credentials."
109
+ puts " By setting the RAILS_ENV environment you can maintain multiple settings."
110
+ puts
111
+ puts "Questions? Create an issue: https://github.com/callstacking/callstacking-rails/issues"
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,53 @@
1
+ module Callstacking
2
+ module Rails
3
+ class Spans
4
+ attr_accessor :order_num, :nesting_level, :previous_entry
5
+ attr_accessor :call_entry_callback, :call_return_callback
6
+
7
+ def initialize
8
+ @nesting_level = -1
9
+ @order_num = -1
10
+ @previous_entry = nil
11
+ end
12
+
13
+ def increment_order_num
14
+ @order_num+=1
15
+ @order_num
16
+ end
17
+
18
+ def increment_nesting_level
19
+ @nesting_level+=1
20
+ @nesting_level
21
+ end
22
+
23
+ def call_entry(klass, method_name, arguments, path, line_no)
24
+ @nesting_level+=1
25
+ @previous_entry = previous_event(klass, method_name)
26
+ @call_entry_callback.call(@nesting_level, increment_order_num, klass, method_name, arguments, path, line_no)
27
+ end
28
+
29
+ def call_return(klass, method_name, path, line_no, return_val)
30
+ @call_return_callback.call(coupled_callee(klass, method_name), @nesting_level,
31
+ increment_order_num, klass, method_name, path, line_no, return_val)
32
+ @nesting_level-=1
33
+ end
34
+
35
+ def on_call_entry(&block)
36
+ @call_entry_callback = block
37
+ end
38
+
39
+ def on_call_return(&block)
40
+ @call_return_callback = block
41
+ end
42
+
43
+ private
44
+ def previous_event(klass, method_name)
45
+ "#{klass}:#{method_name}"
46
+ end
47
+
48
+ def coupled_callee(klass, method_name)
49
+ previous_entry == previous_event(klass, method_name)
50
+ end
51
+ end
52
+ end
53
+ end