callstacking-rails 0.1.19 → 0.1.21
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 +4 -4
- data/README.md +1 -0
- data/exe/callstacking-rails +5 -23
- data/lib/callstacking/rails/cli.rb +50 -0
- data/lib/callstacking/rails/client/authenticate.rb +0 -1
- data/lib/callstacking/rails/client/base.rb +5 -6
- data/lib/callstacking/rails/client/trace.rb +5 -3
- data/lib/callstacking/rails/engine.rb +36 -12
- data/lib/callstacking/rails/instrument.rb +44 -30
- data/lib/callstacking/rails/loader.rb +15 -9
- data/lib/callstacking/rails/settings.rb +62 -9
- data/lib/callstacking/rails/setup.rb +15 -55
- data/lib/callstacking/rails/time_based_uuid.rb +25 -0
- data/lib/callstacking/rails/trace.rb +63 -47
- data/lib/callstacking/rails/traces_helper.rb +5 -11
- data/lib/callstacking/rails/version.rb +1 -1
- metadata +32 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1da66ff58acecfefd9f52e259cb9d69eb54e8328a4fee36ed130e26f968d61a2
|
4
|
+
data.tar.gz: 2d8bc0b54633d9a17e607ee1c7006d7d728e999c5a5d5c6bfc93dd9090a1a19e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 737cf8e5ddf9467a6ce6ca2ae1ebeee369f5e9200b7893440fb923ecbe21638ac0ec0430ab909cddd29910b5a4bc83d04436fd2cf304109b88d7d64768232789
|
7
|
+
data.tar.gz: 8f8afdfc52b192dd8e688e5f28dc8f93306241bc4347cc3c90144d7a6b7b9f210b0d9eb6037b288e6bbc4c68554e71456261fd736e5ea5e5fde6402c29051d53
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# Callstacking::Rails
|
2
|
+
[](https://github.com/callstacking/callstacking-rails/actions/workflows/ci.yml)
|
2
3
|
|
3
4
|
Call Stacking 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
5
|
|
data/exe/callstacking-rails
CHANGED
@@ -1,33 +1,15 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
require "bundler/setup"
|
4
|
-
require "callstacking/rails
|
5
|
-
require "callstacking/rails/settings"
|
4
|
+
require "callstacking/rails"
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
action = ARGV[0]&.downcase&.strip
|
6
|
+
action = Callstacking::Rails::Cli.action(ARGV)
|
7
|
+
settings = Callstacking::Rails::Settings.new
|
10
8
|
|
11
9
|
if action.nil?
|
12
10
|
Callstacking::Rails::Setup.instructions
|
13
11
|
exit!(1)
|
14
12
|
end
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
case action
|
19
|
-
when 'register'
|
20
|
-
puts "Open the following URL to register:\n\n"
|
21
|
-
puts " #{settings[:url]}/users/sign_up\n\n"
|
22
|
-
|
23
|
-
when 'setup'
|
24
|
-
Callstacking::Rails::Setup.new.start
|
25
|
-
|
26
|
-
when 'enable'
|
27
|
-
Callstacking::Rails::Setup.new.enable_disable
|
28
|
-
puts "Call Stacking tracing enabled (#{Callstacking::Rails::Env.environment})"
|
29
|
-
|
30
|
-
when 'disable'
|
31
|
-
Callstacking::Rails::Setup.new.enable_disable(enabled: false)
|
32
|
-
puts "Call Stacking tracing disabled (#{Callstacking::Rails::Env.environment})"
|
33
|
-
end
|
14
|
+
cli = Callstacking::Rails::Cli.new(action, settings)
|
15
|
+
cli.run
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Callstacking
|
2
|
+
module Rails
|
3
|
+
class Cli
|
4
|
+
REGISTER = 'register'
|
5
|
+
SETUP = 'setup'
|
6
|
+
ENABLE = 'enable'
|
7
|
+
DISABLE = 'disable'
|
8
|
+
|
9
|
+
attr_reader :action, :settings
|
10
|
+
|
11
|
+
def initialize(action, settings)
|
12
|
+
@action = action
|
13
|
+
@settings = settings
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
parse_options
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.action(args)
|
21
|
+
args[0]&.downcase&.strip
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def parse_options
|
27
|
+
case action
|
28
|
+
when REGISTER
|
29
|
+
puts "Open the following URL to register:\n\n"
|
30
|
+
puts " #{settings.url}/users/sign_up\n\n"
|
31
|
+
REGISTER
|
32
|
+
|
33
|
+
when SETUP
|
34
|
+
Callstacking::Rails::Setup.new.start
|
35
|
+
SETUP
|
36
|
+
|
37
|
+
when ENABLE
|
38
|
+
settings.enable_disable
|
39
|
+
puts "Call Stacking tracing enabled (#{Callstacking::Rails::Env.environment})"
|
40
|
+
ENABLE
|
41
|
+
|
42
|
+
when DISABLE
|
43
|
+
settings.enable_disable(enabled: false)
|
44
|
+
puts "Call Stacking tracing disabled (#{Callstacking::Rails::Env.environment})"
|
45
|
+
DISABLE
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'faraday'
|
2
2
|
require 'faraday/follow_redirects'
|
3
|
-
require "callstacking/rails/settings"
|
4
3
|
|
5
4
|
module Callstacking
|
6
5
|
module Rails
|
@@ -8,12 +7,12 @@ module Callstacking
|
|
8
7
|
class Error < StandardError; end
|
9
8
|
|
10
9
|
class Base
|
11
|
-
|
10
|
+
attr_reader :url, :auth_token
|
12
11
|
|
13
|
-
def initialize
|
14
|
-
|
12
|
+
def initialize(url, auth_token)
|
13
|
+
@url = url
|
14
|
+
@auth_token = auth_token
|
15
15
|
end
|
16
|
-
|
17
16
|
def connection
|
18
17
|
# https://github.com/lostisland/awesome-faraday
|
19
18
|
@connection ||= Faraday.new(url) do |c|
|
@@ -25,7 +24,7 @@ module Callstacking
|
|
25
24
|
c.request :json # This will set the "Content-Type" header to application/json and call .to_json on the body
|
26
25
|
c.adapter Faraday.default_adapter
|
27
26
|
|
28
|
-
if auth_token?
|
27
|
+
if auth_token.present?
|
29
28
|
c.request :authorization, :Bearer, auth_token
|
30
29
|
end
|
31
30
|
end
|
@@ -7,16 +7,18 @@ module Callstacking
|
|
7
7
|
CREATE_URL = "/api/v1/traces.json"
|
8
8
|
UPDATE_URL = "/api/v1/traces/:id.json"
|
9
9
|
|
10
|
-
def create(method_name, klass, action_name, format_name, root_path, url,
|
10
|
+
def create(request_id, tuid, method_name, klass, action_name, format_name, root_path, url, headers, params)
|
11
11
|
resp = post(CREATE_URL,
|
12
12
|
{},
|
13
|
-
{
|
13
|
+
{
|
14
|
+
request_id: request_id,
|
15
|
+
tuid: tuid,
|
16
|
+
method_name: method_name,
|
14
17
|
klass: klass,
|
15
18
|
action_name: action_name,
|
16
19
|
format_name: format_name,
|
17
20
|
root_path: root_path,
|
18
21
|
url: url,
|
19
|
-
request_id: request_id,
|
20
22
|
h: headers.to_h,
|
21
23
|
p: params.to_h,
|
22
24
|
})
|
@@ -1,4 +1,6 @@
|
|
1
1
|
require "rails"
|
2
|
+
require "active_support/cache"
|
3
|
+
require "callstacking/rails/env"
|
2
4
|
require "callstacking/rails/trace"
|
3
5
|
require "callstacking/rails/instrument"
|
4
6
|
require 'callstacking/rails/spans'
|
@@ -8,20 +10,23 @@ require "callstacking/rails/loader"
|
|
8
10
|
require "callstacking/rails/client/base"
|
9
11
|
require "callstacking/rails/client/authenticate"
|
10
12
|
require "callstacking/rails/client/trace"
|
13
|
+
require "callstacking/rails/cli"
|
11
14
|
require "callstacking/rails/traces_helper"
|
15
|
+
require "callstacking/rails/time_based_uuid"
|
12
16
|
|
13
17
|
module Callstacking
|
14
18
|
module Rails
|
15
19
|
class Engine < ::Rails::Engine
|
16
|
-
|
20
|
+
EXCLUDED_TEST_CLASSES = ['test/dummy/app/models/salutation.rb'].freeze
|
21
|
+
|
22
|
+
cattr_accessor :spans, :trace, :settings, :instrumenter, :loader
|
17
23
|
|
18
|
-
include Settings
|
19
24
|
isolate_namespace Callstacking::Rails
|
20
25
|
|
21
|
-
|
22
|
-
|
26
|
+
@@settings||=Callstacking::Rails::Settings.new
|
23
27
|
@@spans||=Spans.new
|
24
28
|
@@trace||=Trace.new(@@spans)
|
29
|
+
@@instrumenter||=Instrument.new(@@spans)
|
25
30
|
|
26
31
|
initializer "engine_name.assets.precompile" do |app|
|
27
32
|
app.config.assets.precompile << "checkpoint_rails_manifest.js"
|
@@ -31,19 +36,38 @@ module Callstacking
|
|
31
36
|
ActiveSupport.on_load :action_controller do
|
32
37
|
include Callstacking::Rails::TracesHelper
|
33
38
|
end
|
39
|
+
end
|
40
|
+
|
41
|
+
config.after_initialize do
|
42
|
+
if @@settings.enabled?
|
43
|
+
puts "Call Stacking enabled (#{Callstacking::Rails::Env.environment})"
|
44
|
+
|
45
|
+
ActionController::Base.send :after_action do
|
46
|
+
inject_hud(@@settings)
|
47
|
+
end
|
34
48
|
|
35
|
-
|
49
|
+
@@loader = Callstacking::Rails::Loader.new(@@instrumenter,
|
50
|
+
excluded: @@settings.excluded + EXCLUDED_TEST_CLASSES)
|
51
|
+
@@loader.on_load
|
52
|
+
|
53
|
+
@@trace.request_tracing
|
54
|
+
else
|
55
|
+
puts "Call Stacking disabled (#{Callstacking::Rails::Env.environment})"
|
56
|
+
end
|
36
57
|
end
|
37
58
|
|
38
|
-
|
39
|
-
|
59
|
+
def self.start_tracing
|
60
|
+
return false if @@settings.disabled?
|
61
|
+
|
62
|
+
@@instrumenter.enable!(@@loader.klasses)
|
63
|
+
true
|
40
64
|
end
|
41
65
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
66
|
+
def self.stop_tracing
|
67
|
+
return false if @@settings.disabled?
|
68
|
+
|
69
|
+
@@instrumenter.disable!
|
70
|
+
true
|
47
71
|
end
|
48
72
|
end
|
49
73
|
end
|
@@ -4,16 +4,17 @@ require 'rails'
|
|
4
4
|
module Callstacking
|
5
5
|
module Rails
|
6
6
|
class Instrument
|
7
|
-
attr_accessor :spans
|
8
|
-
attr_reader :root
|
7
|
+
attr_accessor :spans
|
8
|
+
attr_reader :root, :settings, :span_modules
|
9
9
|
|
10
|
-
def initialize(spans
|
10
|
+
def initialize(spans)
|
11
11
|
@spans = spans
|
12
|
-
@
|
12
|
+
@span_modules = Set.new
|
13
|
+
@settings = Callstacking::Rails::Settings.new
|
13
14
|
@root = Regexp.new(::Rails.root.to_s)
|
14
15
|
end
|
15
16
|
|
16
|
-
def instrument_method(method_name, application_level: true)
|
17
|
+
def instrument_method(klass, method_name, application_level: true)
|
17
18
|
method_path = (klass.instance_method(method_name).source_location.first rescue nil) ||
|
18
19
|
(klass.method(method_name).source_location.first rescue nil)
|
19
20
|
|
@@ -22,7 +23,7 @@ module Callstacking
|
|
22
23
|
|
23
24
|
return if method_path =~ /initializer/i
|
24
25
|
|
25
|
-
tmp_module = find_or_initialize_module
|
26
|
+
tmp_module = find_or_initialize_module(klass)
|
26
27
|
|
27
28
|
return if tmp_module.nil? ||
|
28
29
|
tmp_module.instance_methods.include?(method_name) ||
|
@@ -76,31 +77,22 @@ module Callstacking
|
|
76
77
|
new_method
|
77
78
|
end
|
78
79
|
|
79
|
-
def
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
new_module.instance_variable_set("@klass", klass)
|
90
|
-
new_module.instance_variable_set("@spans", spans)
|
91
|
-
|
92
|
-
klass.prepend new_module
|
93
|
-
klass.singleton_class.prepend new_module if klass.class == Module
|
94
|
-
|
95
|
-
return find_or_initialize_module
|
80
|
+
def enable!(klasses)
|
81
|
+
klasses.each do |klass|
|
82
|
+
instrument_klass(klass, application_level: true)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
def disable!
|
86
|
+
span_modules.each do |mod|
|
87
|
+
mod.instance_methods.each do |method_name|
|
88
|
+
mod.remove_method(method_name)
|
89
|
+
end
|
96
90
|
end
|
97
|
-
|
98
|
-
klass.ancestors[module_index]
|
99
91
|
end
|
100
92
|
|
101
|
-
def instrument_klass(application_level: true)
|
102
|
-
relevant = all_methods - filtered
|
103
|
-
relevant.each { |method| instrument_method(method, application_level: application_level) }
|
93
|
+
def instrument_klass(klass, application_level: true)
|
94
|
+
relevant = all_methods(klass) - filtered
|
95
|
+
relevant.each { |method| instrument_method(klass, method, application_level: application_level) }
|
104
96
|
end
|
105
97
|
|
106
98
|
def self.arguments_for(m, args)
|
@@ -118,9 +110,31 @@ module Callstacking
|
|
118
110
|
end
|
119
111
|
|
120
112
|
private
|
113
|
+
def find_or_initialize_module(klass)
|
114
|
+
name = klass&.name rescue nil
|
115
|
+
return if name.nil?
|
116
|
+
|
117
|
+
module_name = "#{klass.name.gsub('::', '')}Span"
|
118
|
+
module_index = klass.ancestors.map(&:to_s).index(module_name)
|
119
|
+
|
120
|
+
unless module_index
|
121
|
+
new_module = Object.const_set(module_name, Module.new)
|
122
|
+
span_modules << new_module
|
123
|
+
|
124
|
+
new_module.instance_variable_set("@klass", klass)
|
125
|
+
new_module.instance_variable_set("@spans", spans)
|
126
|
+
|
127
|
+
klass.prepend new_module
|
128
|
+
klass.singleton_class.prepend new_module if klass.class == Module
|
129
|
+
|
130
|
+
return find_or_initialize_module(klass)
|
131
|
+
end
|
132
|
+
|
133
|
+
klass.ancestors[module_index]
|
134
|
+
end
|
121
135
|
|
122
|
-
def all_methods
|
123
|
-
|
136
|
+
def all_methods(klass)
|
137
|
+
(klass.instance_methods +
|
124
138
|
klass.private_instance_methods(false) +
|
125
139
|
klass.protected_instance_methods(false) +
|
126
140
|
klass.methods +
|
@@ -3,26 +3,32 @@ require "rails"
|
|
3
3
|
module Callstacking
|
4
4
|
module Rails
|
5
5
|
class Loader
|
6
|
-
attr_accessor :
|
7
|
-
def initialize
|
6
|
+
attr_accessor :root, :spans, :instrumenter, :klasses, :excluded
|
7
|
+
def initialize(instrumenter, excluded: [])
|
8
8
|
@root = Regexp.new(::Rails.root.to_s)
|
9
|
+
@spans = spans
|
10
|
+
@excluded = excluded
|
11
|
+
@instrumenter = instrumenter
|
12
|
+
@klasses = Set.new
|
9
13
|
end
|
10
14
|
|
11
|
-
def on_load
|
15
|
+
def on_load
|
12
16
|
trace = TracePoint.new(:end) do |tp|
|
13
17
|
klass = tp.self
|
14
18
|
path = tp.path
|
15
19
|
|
16
|
-
|
20
|
+
exclude = excluded.any? { |ex| path =~ /#{ex}/ }
|
21
|
+
|
22
|
+
if path =~ root && !exclude
|
23
|
+
instrumenter.instrument_klass(klass)
|
24
|
+
klasses << klass
|
25
|
+
end
|
17
26
|
end
|
18
27
|
|
19
28
|
trace.enable
|
20
29
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
Instrument.new(spans, ActionView::TemplateRenderer).instrument_method( :render,
|
25
|
-
application_level: false)
|
30
|
+
instrumenter.instrument_method(ActionView::PartialRenderer, :render, application_level: false)
|
31
|
+
instrumenter.instrument_method(ActionView::TemplateRenderer, :render, application_level: false)
|
26
32
|
end
|
27
33
|
end
|
28
34
|
end
|
@@ -1,18 +1,22 @@
|
|
1
|
+
require "active_support/cache"
|
1
2
|
require "active_support/concern"
|
2
3
|
require "active_support/core_ext/class/attribute_accessors"
|
3
|
-
require "callstacking/rails/env"
|
4
4
|
|
5
5
|
module Callstacking
|
6
6
|
module Rails
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
included do |base|
|
11
|
-
attr_accessor :settings
|
12
|
-
end
|
7
|
+
class Settings
|
8
|
+
attr_accessor :settings
|
9
|
+
attr_reader :client
|
13
10
|
|
14
11
|
SETTINGS_FILE = "#{Dir.home}/.callstacking"
|
15
12
|
PRODUCTION_URL = "https://callstacking.com"
|
13
|
+
ENV_KEY = 'CALLSTACKING_ENABLED'
|
14
|
+
CACHE_KEY = :callstacking_enabled
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
read_settings
|
18
|
+
@client = Callstacking::Rails::Client::Authenticate.new(url, auth_token)
|
19
|
+
end
|
16
20
|
|
17
21
|
def url
|
18
22
|
settings[:url] || PRODUCTION_URL
|
@@ -30,18 +34,67 @@ module Callstacking
|
|
30
34
|
File.write(SETTINGS_FILE, new_settings.to_yaml)
|
31
35
|
end
|
32
36
|
|
37
|
+
def self.enable!
|
38
|
+
::Rails.cache.write(CACHE_KEY, true)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.disable!
|
42
|
+
::Rails.cache.write(CACHE_KEY, false)
|
43
|
+
end
|
44
|
+
|
33
45
|
def enabled?
|
46
|
+
return ::Rails.cache.read(CACHE_KEY) unless ::Rails.cache.read(CACHE_KEY).nil?
|
47
|
+
return false if ENV[ENV_KEY] == 'false'
|
34
48
|
return false if settings.nil?
|
35
|
-
|
49
|
+
|
36
50
|
settings[:enabled]
|
37
51
|
end
|
38
52
|
|
53
|
+
def excluded
|
54
|
+
settings[:excluded] || []
|
55
|
+
end
|
56
|
+
|
39
57
|
def disabled?
|
40
58
|
!enabled?
|
41
59
|
end
|
42
60
|
|
61
|
+
def save(email, password, url)
|
62
|
+
props = { auth_token: '',
|
63
|
+
url: url,
|
64
|
+
enabled: true
|
65
|
+
}
|
66
|
+
|
67
|
+
props = { Callstacking::Rails::Env.environment => {
|
68
|
+
settings: props
|
69
|
+
} }
|
70
|
+
|
71
|
+
write_settings(complete_settings.merge(props))
|
72
|
+
|
73
|
+
props[Callstacking::Rails::Env.environment][:settings][:auth_token] = token(email, password)
|
74
|
+
|
75
|
+
write_settings(complete_settings.merge(props))
|
76
|
+
|
77
|
+
read_settings
|
78
|
+
end
|
79
|
+
|
80
|
+
def enable_disable(enabled: true)
|
81
|
+
settings[:enabled] = enabled
|
82
|
+
|
83
|
+
props = { Callstacking::Rails::Env.environment => {
|
84
|
+
settings: settings
|
85
|
+
} }
|
86
|
+
|
87
|
+
write_settings(complete_settings.merge(props))
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def token(email, password)
|
93
|
+
client.login(email, password)
|
94
|
+
end
|
95
|
+
|
43
96
|
def read_settings
|
44
|
-
|
97
|
+
@settings = complete_settings.dig(::Callstacking::Rails::Env.environment, :settings) || {}
|
45
98
|
rescue StandardError => e
|
46
99
|
puts e.full_message
|
47
100
|
puts e.backtrace.join("\n")
|
@@ -1,53 +1,33 @@
|
|
1
1
|
require 'yaml'
|
2
|
-
require "callstacking/rails/settings"
|
3
|
-
require "callstacking/rails/client/authenticate"
|
4
|
-
require "callstacking/rails/env"
|
5
2
|
require 'io/console'
|
6
3
|
|
7
4
|
module Callstacking
|
8
5
|
module Rails
|
9
6
|
class Setup
|
10
|
-
|
11
|
-
extend ::Callstacking::Rails::Settings
|
12
|
-
|
13
|
-
attr_accessor :client
|
7
|
+
attr_accessor :settings
|
14
8
|
|
15
9
|
def initialize
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
def client
|
20
|
-
@client = Callstacking::Rails::Client::Authenticate.new
|
10
|
+
@settings = Callstacking::Rails::Settings.new
|
21
11
|
end
|
22
12
|
|
23
13
|
def start
|
24
14
|
puts "Login to callstacking.com"
|
25
15
|
puts
|
26
16
|
|
27
|
-
email = prompt("Enter email:")
|
17
|
+
email = prompt("Enter email:", echo: true)
|
28
18
|
password = prompt("Enter password:", echo: false)
|
29
19
|
|
30
|
-
save(email, password, url)
|
20
|
+
settings.save(email, password, url)
|
31
21
|
|
32
22
|
puts "Authentication successful."
|
33
|
-
puts "Settings saved to #{SETTINGS_FILE}"
|
23
|
+
puts "Settings saved to #{Callstacking::Rails::Settings::SETTINGS_FILE}"
|
34
24
|
true
|
35
25
|
rescue StandardError => e
|
36
|
-
puts "
|
26
|
+
puts "Error authenticating: #{e.message}"
|
37
27
|
puts e.backtrace.join("\n")
|
38
28
|
false
|
39
29
|
end
|
40
30
|
|
41
|
-
def enable_disable(enabled: true)
|
42
|
-
settings[:enabled] = enabled
|
43
|
-
|
44
|
-
props = { Callstacking::Rails::Env.environment => {
|
45
|
-
settings: settings
|
46
|
-
} }
|
47
|
-
|
48
|
-
write_settings(complete_settings.merge(props))
|
49
|
-
end
|
50
|
-
|
51
31
|
def prompt(label, echo: true)
|
52
32
|
puts label
|
53
33
|
|
@@ -58,31 +38,7 @@ module Callstacking
|
|
58
38
|
value
|
59
39
|
end
|
60
40
|
|
61
|
-
def token(email, password)
|
62
|
-
client.login(email, password)
|
63
|
-
end
|
64
|
-
|
65
|
-
def save(email, password, url)
|
66
|
-
props = { auth_token: '',
|
67
|
-
url: url,
|
68
|
-
enabled: true
|
69
|
-
}
|
70
|
-
|
71
|
-
props = { Callstacking::Rails::Env.environment => {
|
72
|
-
settings: props
|
73
|
-
} }
|
74
|
-
|
75
|
-
write_settings(complete_settings.merge(props))
|
76
|
-
|
77
|
-
props[Callstacking::Rails::Env.environment][:settings][:auth_token] = token(email, password)
|
78
|
-
|
79
|
-
write_settings(complete_settings.merge(props))
|
80
|
-
|
81
|
-
read_settings
|
82
|
-
end
|
83
|
-
|
84
41
|
def self.instructions
|
85
|
-
read_settings
|
86
42
|
puts "loading environment #{Callstacking::Rails::Env.environment}"
|
87
43
|
puts
|
88
44
|
puts "Usage: "
|
@@ -94,7 +50,7 @@ module Callstacking
|
|
94
50
|
puts " > callstacking-rails setup"
|
95
51
|
puts
|
96
52
|
puts " Interactively prompts you for your callstacking.com username/password."
|
97
|
-
puts " Stores auth details in #{SETTINGS_FILE}"
|
53
|
+
puts " Stores auth details in #{Callstacking::Rails::Settings::SETTINGS_FILE}"
|
98
54
|
puts
|
99
55
|
puts " > callstacking-rails enable"
|
100
56
|
puts
|
@@ -107,19 +63,23 @@ module Callstacking
|
|
107
63
|
puts " You can have multiple environments."
|
108
64
|
puts " The default is #{Callstacking::Rails::Env::DEFAULT_ENVIRONMENT}."
|
109
65
|
puts
|
110
|
-
puts " The #{Callstacking::Rails::Env.environment}: section in the #{SETTINGS_FILE} contains your credentials."
|
66
|
+
puts " The #{Callstacking::Rails::Env.environment}: section in the #{Callstacking::Rails::Settings::SETTINGS_FILE} contains your credentials."
|
111
67
|
puts " By setting the RAILS_ENV environment you can maintain multiple settings."
|
112
68
|
puts
|
113
69
|
puts "Questions? Create an issue: https://github.com/callstacking/callstacking-rails/issues"
|
70
|
+
|
71
|
+
:instructions
|
114
72
|
end
|
115
73
|
|
116
74
|
private
|
117
75
|
|
118
76
|
def url
|
119
|
-
if Callstacking::Rails::Env.production?
|
120
|
-
|
77
|
+
if (Callstacking::Rails::Env.production? || ::Rails.env.test?) &&
|
78
|
+
ENV['CALLSTACKING_RAILS_LOCAL_TEST'].nil?
|
79
|
+
Callstacking::Rails::Settings::PRODUCTION_URL
|
121
80
|
else
|
122
|
-
prompt("Enter URL for #{Callstacking::Rails::Env.environment} API calls [#{PRODUCTION_URL}]:") ||
|
81
|
+
prompt("Enter URL for #{Callstacking::Rails::Env.environment} API calls [#{Callstacking::Rails::Settings::PRODUCTION_URL}]:") ||
|
82
|
+
Callstacking::Rails::Settings::PRODUCTION_URL
|
123
83
|
end
|
124
84
|
end
|
125
85
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
class TimeBasedUUID
|
4
|
+
EPOCH_OFFSET = 1468418800000 # A custom epoch, it could be the UNIX timestamp when the application was created (in milliseconds)
|
5
|
+
MAX_INT8_VALUE = 9223372036854775807
|
6
|
+
|
7
|
+
def self.generate
|
8
|
+
# Get the current time in milliseconds
|
9
|
+
current_time = (Time.now.to_f * 1000).to_i
|
10
|
+
|
11
|
+
# Subtract the custom epoch to reduce the timestamp size
|
12
|
+
timestamp = current_time - EPOCH_OFFSET
|
13
|
+
|
14
|
+
# Generate a random 64-bit number using SecureRandom
|
15
|
+
random_bits = SecureRandom.random_number(1 << 64)
|
16
|
+
|
17
|
+
# Combine the timestamp and the random bits
|
18
|
+
uuid = (timestamp << 64) | random_bits
|
19
|
+
|
20
|
+
# Ensure the UUID fits into a PostgreSQL int8 column
|
21
|
+
uuid = uuid % MAX_INT8_VALUE if uuid > MAX_INT8_VALUE
|
22
|
+
|
23
|
+
uuid
|
24
|
+
end
|
25
|
+
end
|
@@ -1,57 +1,72 @@
|
|
1
1
|
require "rails"
|
2
2
|
require "active_support/concern"
|
3
|
-
require "callstacking/rails/client/base"
|
4
|
-
require "callstacking/rails/client/authenticate"
|
5
|
-
require "callstacking/rails/client/trace"
|
6
|
-
require "callstacking/rails/settings"
|
7
3
|
|
8
4
|
module Callstacking
|
9
5
|
module Rails
|
10
6
|
class Trace
|
11
|
-
include Callstacking::Rails::Settings
|
12
|
-
|
13
7
|
attr_accessor :spans, :client, :lock
|
14
|
-
|
8
|
+
attr_reader :settings
|
9
|
+
cattr_accessor :current_trace_id
|
10
|
+
cattr_accessor :current_tuid
|
11
|
+
|
12
|
+
ICON = '💥'
|
13
|
+
MAX_TRACE_ENTRIES = 3000
|
15
14
|
|
16
15
|
def initialize(spans)
|
17
16
|
@traces = []
|
18
17
|
@spans = spans
|
18
|
+
@settings = Callstacking::Rails::Settings.new
|
19
19
|
|
20
20
|
@lock = Mutex.new
|
21
|
-
@client = Callstacking::Rails::Client::Trace.new
|
22
|
-
end
|
21
|
+
@client = Callstacking::Rails::Client::Trace.new(settings.url, settings.auth_token)
|
23
22
|
|
24
|
-
|
25
|
-
|
23
|
+
init_uuids(nil, nil)
|
24
|
+
init_callbacks(nil)
|
25
|
+
end
|
26
26
|
|
27
|
+
def request_tracing
|
28
|
+
tuid = nil
|
27
29
|
trace_id = nil
|
28
|
-
max_trace_entries = nil
|
29
30
|
|
30
|
-
ActiveSupport::Notifications.subscribe("start_processing.action_controller") do |
|
31
|
-
trace_id,
|
32
|
-
|
33
|
-
payload[:request]&.original_url || payload[:path],
|
34
|
-
payload[:headers], payload[:params])
|
35
|
-
end
|
31
|
+
ActiveSupport::Notifications.subscribe("start_processing.action_controller") do |_name, _start, _finish, _id, payload|
|
32
|
+
trace_id, tuid = init_uuids(payload[:request]&.request_id || SecureRandom.uuid, TimeBasedUUID.generate)
|
33
|
+
init_callbacks(tuid)
|
36
34
|
|
37
|
-
|
38
|
-
|
35
|
+
start_request(trace_id, tuid,
|
36
|
+
payload[:method], payload[:controller],
|
37
|
+
payload[:action], payload[:format], ::Rails.root,
|
38
|
+
payload[:request]&.original_url || payload[:path],
|
39
|
+
payload[:headers], payload[:params])
|
39
40
|
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
ActiveSupport::Notifications.subscribe("process_action.action_controller") do |name, start, finish, id, payload|
|
46
|
-
complete_request(payload[:method], payload[:controller],
|
42
|
+
ActiveSupport::Notifications.subscribe("process_action.action_controller") do |_name, _start, _finish, _id, payload|
|
43
|
+
complete_request(trace_id, tuid,
|
44
|
+
payload[:method], payload[:controller],
|
47
45
|
payload[:action], payload[:format],
|
48
46
|
payload[:request]&.original_url || payload[:path],
|
49
|
-
|
47
|
+
@traces, MAX_TRACE_ENTRIES)
|
50
48
|
end
|
51
49
|
end
|
52
50
|
|
53
51
|
private
|
54
52
|
|
53
|
+
def init_callbacks(tuid)
|
54
|
+
@spans.on_call_entry do |nesting_level, order_num, klass, method_name, arguments, path, line_no|
|
55
|
+
create_call_entry(tuid, nesting_level, order_num, klass, method_name, arguments, path, line_no, @traces)
|
56
|
+
end
|
57
|
+
|
58
|
+
@spans.on_call_return do |coupled_callee, nesting_level, order_num, klass, method_name, path, line_no, return_val|
|
59
|
+
create_call_return(tuid, coupled_callee, nesting_level, order_num, klass, method_name, path, line_no, return_val, @traces)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def init_uuids(trace_id, tuid)
|
64
|
+
Callstacking::Rails::Trace.current_trace_id = trace_id
|
65
|
+
Callstacking::Rails::Trace.current_tuid = tuid
|
66
|
+
|
67
|
+
return trace_id, tuid
|
68
|
+
end
|
69
|
+
|
55
70
|
def completed_request_message(method, controller, action, format)
|
56
71
|
"Completed request: #{method} #{controller}##{action} as #{format}"
|
57
72
|
end
|
@@ -60,9 +75,10 @@ module Callstacking
|
|
60
75
|
"Started request: #{method} #{controller}##{action} as #{format}"
|
61
76
|
end
|
62
77
|
|
63
|
-
def create_call_return(coupled_callee, nesting_level, order_num, klass, method_name, path, line_no, return_val, traces)
|
78
|
+
def create_call_return(tuid, coupled_callee, nesting_level, order_num, klass, method_name, path, line_no, return_val, traces)
|
64
79
|
lock.synchronize do
|
65
|
-
traces << {
|
80
|
+
traces << { tuid: tuid,
|
81
|
+
type: 'TraceCallReturn',
|
66
82
|
order_num: order_num,
|
67
83
|
nesting_level: nesting_level,
|
68
84
|
local_variables: {},
|
@@ -78,9 +94,10 @@ module Callstacking
|
|
78
94
|
end
|
79
95
|
end
|
80
96
|
|
81
|
-
def create_call_entry(nesting_level, order_num, klass, method_name, arguments, path, line_no, traces)
|
97
|
+
def create_call_entry(tuid, nesting_level, order_num, klass, method_name, arguments, path, line_no, traces)
|
82
98
|
lock.synchronize do
|
83
|
-
traces << {
|
99
|
+
traces << { tuid: tuid,
|
100
|
+
type: 'TraceCallEntry',
|
84
101
|
order_num: order_num,
|
85
102
|
nesting_level: nesting_level,
|
86
103
|
args: arguments,
|
@@ -96,9 +113,10 @@ module Callstacking
|
|
96
113
|
end
|
97
114
|
end
|
98
115
|
|
99
|
-
def create_message(message, order_num, traces)
|
116
|
+
def create_message(tuid, message, order_num, traces)
|
100
117
|
lock.synchronize do
|
101
|
-
traces << {
|
118
|
+
traces << { tuid: tuid,
|
119
|
+
type: 'TraceMessage',
|
102
120
|
order_num: order_num,
|
103
121
|
nesting_level: 0,
|
104
122
|
message: message,
|
@@ -118,30 +136,28 @@ module Callstacking
|
|
118
136
|
lock.synchronize do
|
119
137
|
return if traces.empty?
|
120
138
|
|
121
|
-
client.upsert(trace_id,
|
139
|
+
client.upsert(trace_id,
|
140
|
+
{ trace_entries: traces })
|
122
141
|
traces.clear
|
123
142
|
end
|
124
143
|
end
|
125
|
-
def start_request(
|
144
|
+
def start_request(trace_id, tuid, method, controller, action, format, path, original_url, headers, params)
|
126
145
|
return if do_not_track_request?(original_url, format)
|
127
|
-
|
128
|
-
request_id = request_id || SecureRandom.uuid
|
129
|
-
Callstacking::Rails::Trace.current_request_id = request_id
|
130
146
|
|
131
|
-
|
132
|
-
|
133
|
-
|
147
|
+
client.create(trace_id, tuid,
|
148
|
+
method, controller,
|
149
|
+
action, format,
|
150
|
+
path, original_url,
|
151
|
+
headers, params)
|
134
152
|
|
135
153
|
print_trace_url(trace_id)
|
136
154
|
|
137
|
-
create_message(start_request_message(method, controller, action, format),
|
155
|
+
create_message(tuid, start_request_message(method, controller, action, format),
|
138
156
|
spans.increment_order_num, @traces)
|
139
|
-
|
140
|
-
return trace_id, max_trace_entries
|
141
157
|
end
|
142
158
|
|
143
159
|
def print_trace_url(trace_id)
|
144
|
-
url = "#{settings
|
160
|
+
url = "#{settings.url}/traces/#{trace_id}"
|
145
161
|
|
146
162
|
puts "*" * url.size
|
147
163
|
puts url
|
@@ -150,13 +166,13 @@ module Callstacking
|
|
150
166
|
url
|
151
167
|
end
|
152
168
|
|
153
|
-
def complete_request(method, controller, action, format, original_url,
|
169
|
+
def complete_request(trace_id, tuid, method, controller, action, format, original_url, traces, max_trace_entries)
|
154
170
|
if do_not_track_request?(original_url, format)
|
155
171
|
traces.clear
|
156
172
|
return
|
157
173
|
end
|
158
174
|
|
159
|
-
create_message(completed_request_message(method, controller, action, format),
|
175
|
+
create_message(tuid, completed_request_message(method, controller, action, format),
|
160
176
|
spans.increment_order_num, traces)
|
161
177
|
|
162
178
|
send_traces!(trace_id, traces[0..max_trace_entries])
|
@@ -7,12 +7,9 @@ module Callstacking
|
|
7
7
|
include ActionView::Helpers::TagHelper
|
8
8
|
include ActionView::Helpers::JavaScriptHelper
|
9
9
|
include ActionView::Context
|
10
|
-
include Callstacking::Rails::Settings
|
11
10
|
|
12
|
-
def hud
|
13
|
-
|
14
|
-
|
15
|
-
frame_url = "#{url || Callstacking::Rails::Settings::PRODUCTION_URL}/traces/#{Callstacking::Rails::Trace.current_request_id}/print"
|
11
|
+
def hud(url)
|
12
|
+
frame_url = "#{url || Callstacking::Rails::Settings::PRODUCTION_URL}/traces/#{Callstacking::Rails::Trace.current_trace_id}/print"
|
16
13
|
|
17
14
|
body = []
|
18
15
|
body << (content_tag( :div, data: { turbo:false },
|
@@ -20,7 +17,7 @@ module Callstacking
|
|
20
17
|
padding: 0px; position: fixed; height: 50px; width: 40px; cursor: pointer;',
|
21
18
|
onclick: 'document.getElementById("callstacking-debugger").style.display = "unset";
|
22
19
|
document.getElementById("callstacking-close").style.display = "unset";') do
|
23
|
-
"<span title='ctrl-d'><center
|
20
|
+
"<span title='ctrl-d'><center>#{Callstacking::Rails::Trace::ICON}</center></span>".html_safe
|
24
21
|
end)
|
25
22
|
|
26
23
|
body << (content_tag(:iframe, src: frame_url, id: 'callstacking-debugger', data: { turbo:false },
|
@@ -45,11 +42,8 @@ module Callstacking
|
|
45
42
|
body.join
|
46
43
|
end
|
47
44
|
|
48
|
-
def inject_hud
|
49
|
-
|
50
|
-
return unless enabled?
|
51
|
-
|
52
|
-
response.body = response.body.sub(/<\/body>/i, "#{hud}</body>")
|
45
|
+
def inject_hud(settings)
|
46
|
+
response.body = response.body.sub(/<\/body>/i, "#{hud(settings.url)}</body>")
|
53
47
|
end
|
54
48
|
end
|
55
49
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: callstacking-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.21
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jim Jones
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-03-
|
11
|
+
date: 2023-03-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -52,6 +52,34 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mocha
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest-silence
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
55
83
|
description: Quickly visualize which methods call which, their parameters, and return
|
56
84
|
values.
|
57
85
|
email:
|
@@ -74,6 +102,7 @@ files:
|
|
74
102
|
- config/routes.rb
|
75
103
|
- exe/callstacking-rails
|
76
104
|
- lib/callstacking/rails.rb
|
105
|
+
- lib/callstacking/rails/cli.rb
|
77
106
|
- lib/callstacking/rails/client/authenticate.rb
|
78
107
|
- lib/callstacking/rails/client/base.rb
|
79
108
|
- lib/callstacking/rails/client/trace.rb
|
@@ -84,6 +113,7 @@ files:
|
|
84
113
|
- lib/callstacking/rails/settings.rb
|
85
114
|
- lib/callstacking/rails/setup.rb
|
86
115
|
- lib/callstacking/rails/spans.rb
|
116
|
+
- lib/callstacking/rails/time_based_uuid.rb
|
87
117
|
- lib/callstacking/rails/trace.rb
|
88
118
|
- lib/callstacking/rails/traces_helper.rb
|
89
119
|
- lib/callstacking/rails/version.rb
|